You already know which problem Docker solves; now it is time to install it. This lesson walks you through the installation on the three usual operating systems, explaining at each step what you are doing and why, instead of asking you to copy commands blindly. You will first see the fundamental difference between Docker Engine and Docker Desktop —two different products that many people confuse— including the licensing question, which can matter if you work at a large company. Then you will do a complete Docker Engine installation on Ubuntu/Debian from the official repository, the essential post-installation steps on Linux (with a security warning you must not skip), and the installations on Windows with WSL 2 and on macOS. You will finish by verifying that everything works and decoding, line by line, the output of your first container.
Contents
- Docker Engine and Docker Desktop: two different products
- Docker Desktop licensing in companies
- Prerequisites
- Installing Docker Engine on Ubuntu/Debian
- Post-installation steps on Linux
- Installing on Windows with the WSL 2 backend
- Installing on macOS (Intel and Apple Silicon)
- Verification:
docker version,docker infoandhello-world - Uninstalling and cleaning up
- Docker Engine and Docker Desktop: two different products
This is the first point where people get lost, so let's take it slowly.
Docker Engine is the engine: the service that actually creates and runs containers, plus the docker command-line tool that talks to it. It runs natively on Linux only, because containers rely on Linux kernel features. It has no graphical interface. It is what runs on servers.
Docker Desktop is a desktop application for Windows, macOS and Linux that bundles Docker Engine inside an automatically managed Linux virtual machine, plus a graphical interface, plus extra tooling (one-click Kubernetes, extension management, vulnerability scanning). On Windows and macOS it is practically the only convenient way to work, because those systems have no Linux kernel: Docker Desktop provides one for them.
That is the conceptual key: when you run a Linux container on a Mac or on Windows, there is always a Linux virtual machine in the middle, even if you never see it.
| Aspect | Docker Engine | Docker Desktop |
|---|---|---|
| Systems | Native Linux | Windows, macOS, Linux |
| Graphical interface | No | Yes |
| How it works | Native processes on the host's kernel | Managed Linux VM + GUI |
| Resource usage | Minimal | Noticeable (the VM reserves CPU/RAM) |
| Updates | System package manager | The app's own updater |
| Built-in Kubernetes | No (installed separately) | Yes, with one click |
| License | Apache 2.0, always free | Free with conditions (see section 2) |
| Typical use | Servers, CI, development on Linux | Development on Windows/macOS laptops |
On Linux you have both options. The general recommendation for learning and for servers is Docker Engine: fewer layers, less overhead, and it is what you will find in production. Docker Desktop as a day-to-day tool is studied separately, in lesson 07-03; here we just install it.
- Docker Desktop licensing in companies
This is a practical detail worth knowing before you install anything on your company laptop.
- Docker Engine is free software under the Apache 2.0 license. It is free in any context, commercial use included.
- Docker Desktop is free for personal use, education, open source projects and small businesses, but it requires a paid subscription for professional use in organizations above a threshold (historically, more than 250 employees or more than 10 million dollars in annual revenue; the exact terms are set by Docker, Inc. and it is worth checking their website).
In practical terms: if you work at a large company, do not install Docker Desktop without checking with your IT department whether licenses exist. On Linux you can always use Docker Engine with no restrictions whatsoever, and there are desktop alternatives discussed in lesson 07-05.
- Prerequisites
| System | Requirement |
|---|---|
| Linux | 64-bit distribution with kernel 5.x or newer (any currently supported Ubuntu/Debian meets this) and systemd |
| Windows | 64-bit Windows 10/11, with WSL 2 enabled and virtualization turned on in the BIOS |
| macOS | The three latest major macOS releases; works on Intel and on Apple Silicon (M1 and later) |
| All | At least 4 GB of RAM (8 GB recommended) and administrator rights |
- Installing Docker Engine on Ubuntu/Debian
We are going to install from the official Docker repository, not from the packages your distribution ships. Why? Because Ubuntu's docker.io package is usually several versions behind and does not include the modern plugins (buildx, compose) that you will use throughout the course.
Step 1: remove old or conflicting packages
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
sudo apt remove -y $pkg
doneThis loop walks through a list of packages that may conflict with the official installation and uninstalls them. If you have none of them installed, apt will simply tell you the package is not present and carry on; that is not an error. Notice that docker-compose is removed: that is the old version of Compose (the hyphenated one), which we will replace with the modern plugin.
Step 2: prepare repository access and add the GPG key
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.ascLet's break down each line:
apt updaterefreshes the list of available packages.ca-certificatesandcurlare needed to download securely over HTTPS.install -m 0755 -d /etc/apt/keyringscreates the directory where repository signing keys are stored, with the right permissions.curl -fsSL ... -o ...downloads Docker's public GPG key. The options mean:-ffail silently on HTTP errors,-ssilent mode,-Sbut do show errors,-Lfollow redirects. This key is what letsaptverify that the packages it downloads really come from Docker and have not been tampered with.chmod a+rmakes the key readable by all users, which is whataptneeds.
If you are on Debian rather than Ubuntu, replace
ubuntuwithdebianin the key URL and in the repository URL in the next step.
Step 3: add the repository to apt's sources
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt updateThis command writes one line into /etc/apt/sources.list.d/docker.list. The dynamic parts are:
$(dpkg --print-architecture)inserts your machine's architecture (amd64orarm64), so that apt downloads the right binaries.signed-by=...ties this repository to the key you downloaded: only packages signed with it will be accepted.$(. /etc/os-release && echo "$VERSION_CODENAME")inserts your release's codename (for examplenobleon Ubuntu 24.04). If this piece comes back empty, replace it by hand with your distribution's codename.sudo tee ... > /dev/nullwrites the file with root permissions without dumping the content to the screen.
The final apt update re-reads the sources, now including Docker's.
Step 4: install the packages
sudo apt install -y \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-pluginFive packages, and it is worth knowing what each one does because they will show up again in lesson 01-03:
| Package | What it is |
|---|---|
docker-ce |
The dockerd daemon, the engine that creates and manages containers (CE = Community Edition) |
docker-ce-cli |
The docker command-line client, which sends orders to the daemon |
containerd.io |
The low-level container runtime that dockerd builds on |
docker-buildx-plugin |
The modern image builder: enables docker buildx build |
docker-compose-plugin |
Compose v2 as a subcommand: enables docker compose (no hyphen) |
The last two are CLI plugins: that is why the modern command is docker compose and not docker-compose. The hyphenated form is the old binary (Compose v1, written in Python), retired today; we will only mention it so you can recognize it in old documentation.
- Post-installation steps on Linux
The installation leaves Docker working, but only for root. These steps make it usable day to day.
Start the service and enable it at boot
enablemakes the service start automatically on every reboot.--nowalso starts it right this moment, without waiting for a reboot.statusshows the state; you should see anActive: active (running)line. Leave the view withq.
On most installations from the official repository the service is already enabled and started; running the command anyway does no harm and confirms the state for you.
Using Docker without sudo: the docker group
By default, the daemon listens on a Unix socket (/var/run/docker.sock) owned by the root user and the docker group. If your user is not in that group, every command will demand sudo:
usermod -aG docker $USERadds (-a, append) your user to the (-G)dockergroup. Without-ayou would wipe out your user's other groups, so never leave it out.newgrp dockerapplies the group membership in the current session without logging out. Alternatively, log out and log back in.
Check that it worked by running docker version (without sudo): if there is no permissions error, you are set.
Security warning: the docker group is equivalent to root
This is no minor detail; read it carefully.
Belonging to the docker group is equivalent to having root permissions on the machine. That is not an exaggeration: anyone who can talk to the daemon can ask it to start a privileged container that mounts the host's entire filesystem and modify it at will. The daemon runs as root and executes whatever you ask of it.
Practical consequences:
- On your development laptop, where you already have
sudo, adding yourself to thedockergroup is perfectly reasonable and is the norm. - On a shared server, putting someone in the
dockergroup is handing them root. Treat it with the same care as privileged access. - If you need to avoid that, Docker has a rootless mode, which runs the daemon as your unprivileged user. It has some limitations and is covered in lesson 05-03, alongside the rest of the security best practices.
- Installing on Windows with the WSL 2 backend
On Windows, Docker Desktop relies on WSL 2 (Windows Subsystem for Linux 2), which provides a real Linux kernel inside a lightweight virtual machine integrated into the system. That kernel is what runs your containers.
Step 1: enable WSL 2
Open PowerShell as administrator and run:
wsl --installturns on the required Windows features (WSL and Virtual Machine Platform) and installs a default Linux distribution (usually Ubuntu).wsl --set-default-version 2sets WSL 2 as the default version. WSL 1 is no good for Docker: it has no real Linux kernel.wsl --updateupdates the WSL kernel to the latest version.
Restart the machine when prompted. If wsl --install fails, it is almost always because virtualization is disabled in the BIOS/UEFI: look for it as Intel VT-x, AMD-V or SVM Mode and turn it on.
Step 2: install Docker Desktop
Download the installer from Docker's official website, run it and, in the options, make sure you leave the "Use WSL 2 instead of Hyper-V" box checked. When it finishes, start Docker Desktop and wait for the status bar indicator to turn green ("Engine running").
Step 3: configure integration with your distributions
Under Settings → Resources → WSL Integration, enable integration for the WSL distributions you use. That makes the docker command available inside your WSL Ubuntu terminal, talking to the same engine.
A performance tip that saves a great deal of suffering: keep your projects inside the Linux filesystem (for example \\wsl$\Ubuntu\home\your-user\projects), not in C:\Users\.... Container access to files living on the Windows disk goes through a translation layer and is noticeably slower.
You can work from PowerShell or from the WSL terminal interchangeably; the docker commands are the same.
- Installing on macOS (Intel and Apple Silicon)
On macOS the same thing happens as on Windows: there is no Linux kernel, so Docker Desktop spins up a lightweight Linux virtual machine (using Apple's virtualization framework) where the engine lives.
- Download the right
.dmgfor your processor. This matters:- Apple Silicon (M1, M2, M3, M4…) →
arm64version. - Intel →
amd64version. If you are not sure, go to the Apple menu → About This Mac, or rununame -min the terminal (arm64versusx86_64).
- Apple Silicon (M1, M2, M3, M4…) →
- Open the
.dmgand drag Docker into the Applications folder. - Launch Docker from Applications. It will ask for your administrator password the first time, in order to install privileged components.
- Wait until the whale icon in the menu bar stops animating.
An important note for Apple Silicon: your Mac is arm64, and some older images are only published for amd64. Docker can run them through emulation (Rosetta / QEMU), but it will be slower and occasionally unstable. You will see a warning like:
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8)It is not a failure: it is a notice that emulation is in play. Multi-platform images and how to build them are covered in lesson 05-05.
- Verification:
docker version, docker info and hello-world
docker version, docker info and hello-worldWhatever your operating system, these three checks are the acid test.
docker version
Output (abridged and annotated):
Client: Docker Engine - Community
Version: 28.1.1
API version: 1.49
Go version: go1.23.8
Context: default
Server: Docker Engine - Community
Engine:
Version: 28.1.1
API version: 1.49 (minimum version 1.24)
containerd:
Version: 1.7.27
runc:
Version: 1.2.5What matters here:
- There are two blocks:
ClientandServer. The client is the command you just typed; the server is the daemon. Both showing up means communication works. - If you only get
Clientfollowed by aCannot connect to the Docker daemonerror, either the engine is not running (Linux:sudo systemctl start docker) or your user has no permissions on the socket (review section 5). - You will also see
containerdandrunc: those are the low-level components we will explain in lesson 01-03.
docker info
Output (annotated extract):
Client:
Version: 28.1.1
Plugins:
buildx: Docker Buildx (Docker Inc.) v0.23.0
compose: Docker Compose (Docker Inc.) v2.35.1
Server:
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 28.1.1
Storage Driver: overlay2
Cgroup Driver: systemd
Cgroup Version: 2
Operating System: Ubuntu 24.04.2 LTS
OSType: linux
Architecture: x86_64
CPUs: 8
Total Memory: 15.35GiB
Docker Root Dir: /var/lib/dockerHere is what you should confirm on a fresh installation:
buildxandcomposeappear underPlugins. If they do not, you missed installingdocker-buildx-pluginordocker-compose-plugin.Storage Driver: overlay2is the modern storage driver and the expected one.Containers: 0andImages: 0: normal, you have not done anything yet.Docker Root Diris where the daemon stores images, containers and volumes. If that partition fills up, Docker stops working.
We will interpret this output in more depth in lesson 01-03.
docker run hello-world
This is the installation's final exam:
Full output:
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
e6590344b1a5: Pull complete
Digest: sha256:940c619fbd418f9b2b1b63e25d8861f9cc1b46e3fc8b018ccfe8b78f19b8cc4f
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(arm64v8)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.Let's go line by line, because each one teaches something:
| Line | What is happening |
|---|---|
Unable to find image 'hello-world:latest' locally |
The daemon looked for the image on disk and did not find it. This is not an error, it is information. Notice the :latest tag, added automatically because you did not specify one |
latest: Pulling from library/hello-world |
It is downloading from the default registry (Docker Hub). library/ is the namespace of official images |
e6590344b1a5: Pull complete |
One layer of the image has been downloaded. Images are made of layers; this one has only a single layer. That is the subject of lesson 01-05 |
Digest: sha256:940c... |
The exact, immutable cryptographic identifier of the downloaded image |
Status: Downloaded newer image |
Confirmation that the download finished |
Hello from Docker! |
From here on it is no longer Docker talking: this is the output of the program that ran inside the container |
| Steps 1 to 4 of the message | The message itself describes the architecture: client → daemon → registry → container → output back to your terminal. It is exactly the flow of lesson 01-03 |
If you see that greeting, you have Docker installed and working. The container, by the way, has already finished: it ran its single process, that process printed the text and died. It still exists in a "stopped" state, as you will confirm in lesson 01-04.
- Uninstalling and cleaning up
In case you need to start over on Ubuntu/Debian:
sudo apt purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerdapt purgeremoves the packages and their configuration files.- The two
rm -rfcommands delete the data: all your images, containers and volumes. This is irreversible, so be sure first.
On Windows and macOS, uninstall Docker Desktop like any other application; the app itself also offers a Troubleshoot → Clean / Purge data button to wipe the data without uninstalling.
Common Mistakes and Tips
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?This is error number one. Two causes: the service is not running (sudo systemctl start dockeron Linux; open Docker Desktop on Windows/macOS) or your user is not in thedockergroup. To tell them apart, trysudo docker version: if it works withsudo, it is a permissions problem, not a service problem.- Installing
docker.iofrom your distribution's repositories. It works for the basics, but it usually ships an old version without thebuildxandcomposeplugins, which means half the course will not work for you. Use the official repository. - Using hyphenated
docker-compose. If that command exists on your system, you have version 1 installed, which is retired. The correct command in 2026 isdocker compose, no hyphen, as a subcommand ofdocker. Check it withdocker compose version. - Forgetting
-ainusermod. Typingsudo usermod -G docker $USER(without-a) removes your user from every other group,sudoincluded. It is a quick way to lock yourself out of administrator rights. Always-aG. - Expecting Windows containers on Linux, or the other way round. The container uses the host's kernel (or the VM's). On Linux you run Linux containers, full stop.
- Tip: do not log out unnecessarily. After
usermod,newgrp dockerapplies the change in the current terminal. Other terminals that were already open will still need to be reopened. - Tip: keep an eye on disk space.
Docker Root Dir(by default/var/lib/docker) grows with every image you pull. If your/varlives on a small partition, take that into account from the start. In lesson 01-04 you will seedocker system dfanddocker system pruneto keep it in check.
Exercises
Exercise 1: verified installation
Install Docker on your system following the relevant section and answer, with the commands as evidence:
- Which Docker Engine version do you have on the client, and which on the server?
- Are the
buildxandcomposeplugins available? At what version? - Which storage driver does your installation use, and in which directory does it store data?
- Can you run
dockerwithoutsudo?
Exercise 2: diagnosing an error
A colleague has just installed Docker on Ubuntu and writes to you:
$ docker run hello-world
permission denied while trying to connect to the Docker daemon socket at
unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/_ping":
dial unix /var/run/docker.sock: connect: permission deniedAnswer: (a) what is the cause?, (b) which two commands fix it?, (c) why is it not a good idea to solve it by simply always running sudo docker ...?, and (d) what security implication does the solution you propose carry?
Exercise 3: read the output
Run docker run hello-world twice in a row and compare the two outputs. Which lines disappear on the second run, and why? Then, without using commands you do not know yet, reason about what must have happened to the containers created: how many are there? Are they still running?
Solutions
Solution to exercise 1
docker version --format '{{.Client.Version}} / {{.Server.Version}}'
docker compose version
docker buildx version
docker info --format 'Driver: {{.Driver}} | Root: {{.DockerRootDir}}'
docker run hello-worldA comment on each command:
docker version --format ...uses a Go template to pull out just the two fields of interest, instead of reading the whole output. Client and server should match (for example28.1.1 / 28.1.1); a large gap between the two can cause API incompatibilities.docker compose versionanddocker buildx versionconfirm the plugins are installed. If they answerdocker: 'compose' is not a docker command, the corresponding package is missing.docker info --format ...extracts the storage driver (expected:overlay2) and the data directory (expected:/var/lib/dockeron Linux).- The fact that
docker run hello-worldworks withoutsudoanswers the fourth question.
Solution to exercise 2
(a) The cause is that the user does not belong to the docker group and therefore has no read/write permission on the /var/run/docker.sock socket. Note that the error says permission denied and not Cannot connect: the socket exists and the daemon is running; this is purely a permissions problem.
(b) The commands:
The first adds the user to the group without stripping the others (-a); the second activates the membership in the current session. Logging out and back in is equivalent.
(c) Always using sudo docker is inconvenient and, above all, misleading: files created by Docker will be owned by root, the scripts and editor tooling that invoke docker without sudo will keep failing, and you gain no real security, because the daemon runs as root anyway.
(d) The implication matters: belonging to the docker group is equivalent to having root on the machine, because through the daemon you can start a privileged container that mounts and modifies the host's filesystem. On a personal laptop that is acceptable; on a shared server it must be treated as a grant of privileges, and rootless mode is worth considering (lesson 05-03).
Solution to exercise 3
On the second run these lines disappear:
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
e6590344b1a5: Pull complete
Digest: sha256:940c...
Status: Downloaded newer image for hello-world:latestAnd they are replaced directly by the Hello from Docker! greeting. The reason is that the image is already in the daemon's local cache after the first download: there is no need to contact Docker Hub, so it skips the whole download block and creates the container immediately. This is a first hint of something central to Docker: the image is downloaded once and reused as many times as you like.
As for the containers: two have been created, one per docker run (remember that run always creates a new container, it does not reuse the previous one). Neither is still running: the hello-world process prints its message and terminates, and when a container's main process terminates, the container moves to the Exited state. Both still exist on disk, taking up space, until you delete them. In lesson 01-04 you will see them with docker ps -a and learn how to remove them.
Conclusion
You now have Docker installed and verified. Along the way you have learned to distinguish Docker Engine (the native Linux engine, free and GUI-less) from Docker Desktop (the desktop application with a bundled VM for Windows, macOS and Linux, with licensing conditions at large companies), and you know that on Windows and macOS there is always a Linux virtual machine underneath, because containers need a Linux kernel.
You have installed the five packages that matter —docker-ce, docker-ce-cli, containerd.io, docker-buildx-plugin and docker-compose-plugin—, you have left the service enabled with systemctl enable --now docker, you have added your user to the docker group knowing that this is equivalent to granting root, and you have decoded the output of docker version, docker info and hello-world.
In that last output, Docker's own message gave you a preview of the next lesson's script: the client contacts the daemon, the daemon pulls the image from the registry, creates the container and returns the output. In Docker Architecture you are going to open that box: what exactly the daemon is, what roles containerd and runc play, what the /var/run/docker.sock socket is, and how all the pieces fit together in a simple docker run.
Docker: From Beginner to Advanced
Module 1: Introduction to Docker
- What Is Docker?
- Installing Docker
- Docker Architecture
- Basic Docker Commands
- Understanding Docker Images
- Creating Your First Docker Container
- The Course Project: The Aurora Libros Platform
Module 2: Working with Docker Images
- Docker Hub and Repositories
- Building Docker Images
- Dockerfile Basics
- Advanced Dockerfile Instructions
- Managing Docker Images
- Tagging and Publishing Images
Module 3: Docker Containers
- Running Containers
- Container Lifecycle
- Managing Containers
- Inspecting and Debugging Containers
- Docker Networking
- Data Persistence with Volumes
- Resource Limits and Restart Policies
Module 4: Docker Compose
- Introduction to Docker Compose
- Defining Services in Docker Compose
- Docker Compose Commands
- Multi-Container Applications
- Environment Variables in Docker Compose
- Profiles, Overrides and Multiple Environments
- Local Development with Docker Compose
Module 5: Advanced Docker Concepts
- Docker Networking Deep Dive
- Docker Storage Options
- Docker Security Best Practices
- Optimizing Docker Images
- Advanced Builds with BuildKit and Buildx
- Logging and Monitoring in Docker
- The Runtime Inside: Namespaces, Cgroups and Layers
Module 6: Docker in Production
- Preparing an Image for Production
- CI/CD with Docker
- Orchestrating Containers with Docker Swarm
- Introduction to Kubernetes
- Deploying Docker Containers in Kubernetes
- Scaling and Load Balancing
- Deployment Strategies and Rollback
