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

  1. Docker Engine and Docker Desktop: two different products
  2. Docker Desktop licensing in companies
  3. Prerequisites
  4. Installing Docker Engine on Ubuntu/Debian
  5. Post-installation steps on Linux
  6. Installing on Windows with the WSL 2 backend
  7. Installing on macOS (Intel and Apple Silicon)
  8. Verification: docker version, docker info and hello-world
  9. Uninstalling and cleaning up

  1. 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.

  1. 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.

  1. 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

  1. 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
done

This 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.asc

Let's break down each line:

  • apt update refreshes the list of available packages.
  • ca-certificates and curl are needed to download securely over HTTPS.
  • install -m 0755 -d /etc/apt/keyrings creates the directory where repository signing keys are stored, with the right permissions.
  • curl -fsSL ... -o ... downloads Docker's public GPG key. The options mean: -f fail silently on HTTP errors, -s silent mode, -S but do show errors, -L follow redirects. This key is what lets apt verify that the packages it downloads really come from Docker and have not been tampered with.
  • chmod a+r makes the key readable by all users, which is what apt needs.

If you are on Debian rather than Ubuntu, replace ubuntu with debian in 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 update

This command writes one line into /etc/apt/sources.list.d/docker.list. The dynamic parts are:

  • $(dpkg --print-architecture) inserts your machine's architecture (amd64 or arm64), 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 example noble on Ubuntu 24.04). If this piece comes back empty, replace it by hand with your distribution's codename.
  • sudo tee ... > /dev/null writes 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-plugin

Five 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.

  1. 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

sudo systemctl enable --now docker
sudo systemctl status docker
  • enable makes the service start automatically on every reboot.
  • --now also starts it right this moment, without waiting for a reboot.
  • status shows the state; you should see an Active: active (running) line. Leave the view with q.

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:

sudo usermod -aG docker $USER
newgrp docker
  • usermod -aG docker $USER adds (-a, append) your user to the (-G) docker group. Without -a you would wipe out your user's other groups, so never leave it out.
  • newgrp docker applies 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 the docker group is perfectly reasonable and is the norm.
  • On a shared server, putting someone in the docker group 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.

  1. 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 --install
wsl --set-default-version 2
wsl --update
  • wsl --install turns on the required Windows features (WSL and Virtual Machine Platform) and installs a default Linux distribution (usually Ubuntu).
  • wsl --set-default-version 2 sets WSL 2 as the default version. WSL 1 is no good for Docker: it has no real Linux kernel.
  • wsl --update updates 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.

  1. 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.

  1. Download the right .dmg for your processor. This matters:
    • Apple Silicon (M1, M2, M3, M4…) → arm64 version.
    • Intel → amd64 version. If you are not sure, go to the Apple menu → About This Mac, or run uname -m in the terminal (arm64 versus x86_64).
  2. Open the .dmg and drag Docker into the Applications folder.
  3. Launch Docker from Applications. It will ask for your administrator password the first time, in order to install privileged components.
  4. 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.

  1. Verification: docker version, docker info and hello-world

Whatever your operating system, these three checks are the acid test.

docker version

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.5

What matters here:

  • There are two blocks: Client and Server. The client is the command you just typed; the server is the daemon. Both showing up means communication works.
  • If you only get Client followed by a Cannot connect to the Docker daemon error, 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 containerd and runc: those are the low-level components we will explain in lesson 01-03.

docker info

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/docker

Here is what you should confirm on a fresh installation:

  • buildx and compose appear under Plugins. If they do not, you missed installing docker-buildx-plugin or docker-compose-plugin.
  • Storage Driver: overlay2 is the modern storage driver and the expected one.
  • Containers: 0 and Images: 0: normal, you have not done anything yet.
  • Docker Root Dir is 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:

docker run hello-world

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.

  1. 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/containerd
  • apt purge removes the packages and their configuration files.
  • The two rm -rf commands 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 docker on Linux; open Docker Desktop on Windows/macOS) or your user is not in the docker group. To tell them apart, try sudo docker version: if it works with sudo, it is a permissions problem, not a service problem.
  • Installing docker.io from your distribution's repositories. It works for the basics, but it usually ships an old version without the buildx and compose plugins, 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 is docker compose, no hyphen, as a subcommand of docker. Check it with docker compose version.
  • Forgetting -a in usermod. Typing sudo usermod -G docker $USER (without -a) removes your user from every other group, sudo included. 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 docker applies 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 /var lives on a small partition, take that into account from the start. In lesson 01-04 you will see docker system df and docker system prune to 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:

  1. Which Docker Engine version do you have on the client, and which on the server?
  2. Are the buildx and compose plugins available? At what version?
  3. Which storage driver does your installation use, and in which directory does it store data?
  4. Can you run docker without sudo?

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 denied

Answer: (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-world

A 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 example 28.1.1 / 28.1.1); a large gap between the two can cause API incompatibilities.
  • docker compose version and docker buildx version confirm the plugins are installed. If they answer docker: '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/docker on Linux).
  • The fact that docker run hello-world works without sudo answers 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:

sudo usermod -aG docker $USER
newgrp docker

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:latest

And 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

Module 2: Working with Docker Images

Module 3: Docker Containers

Module 4: Docker Compose

Module 5: Advanced Docker Concepts

Module 6: Docker in Production

Module 7: Docker Ecosystem and Tools

© Copyright 2026. All rights reserved