Installing Docker

Installing Docker gives your machine the tools needed to pull images, build images, and run containers. The important thing to understand is that the docker command is only the client; containers actually run through a Docker daemon, either on your Linux host or inside the lightweight Linux VM managed by Docker Desktop.

For most learners on macOS or Windows, Docker Desktop is the easiest install. On Linux servers, the usual choice is Docker Engine from Docker’s official package repository.

Overview: How Installation Works

A working Docker installation has several parts. The Docker CLI is the command you type, such as docker run or docker build. The Docker daemon, often called dockerd, listens for API requests from the CLI and does the real work: pulling image layers, creating container filesystems, wiring networks, attaching volumes, and starting processes. Docker also installs containerd and runc, lower-level components that help manage containers according to Open Container Initiative standards.

On native Linux, Docker Engine uses Linux kernel features directly. Namespaces isolate process IDs, networking, mounts, users, and hostnames. Control groups limit and account for CPU and memory. A union filesystem stacks read-only image layers and adds a thin writable layer for each container. This is why installing Docker Engine normally requires root privileges and a running system service.

On macOS and most Windows development machines, Docker cannot use the host kernel directly for Linux containers because the host kernel is not Linux. Docker Desktop solves that by running a small Linux VM. Your terminal still runs docker, but the CLI talks to a daemon inside that VM. Images, containers, named volumes, and Linux filesystems live inside Docker Desktop’s managed environment. Bind mounts connect selected host directories into the VM so containers can see your project files.

There are two main installation tracks. Docker Desktop bundles the Docker CLI, Docker Engine, Docker Compose V2, Buildx, a VM backend, automatic updates, and a graphical settings app. Docker Engine is the server-focused Linux installation: packages such as docker-ce, docker-ce-cli, containerd.io, docker-buildx-plugin, and docker-compose-plugin. Docker’s current Ubuntu instructions install from Docker’s official apt repository, not Ubuntu’s older docker.io package.

Docker Desktop licensing also matters in some workplaces. Docker Desktop is free for personal use, education, non-commercial open source, and small businesses under Docker’s published limits. Larger commercial organizations may need a paid subscription. Docker Engine on Linux is commonly used on servers and CI hosts, but you should still follow your organization’s policy.

Syntax

The installation itself depends on your operating system, but the verification commands are the same once Docker is installed:

docker --version
docker compose version
docker info
docker run --rm hello-world:latest
Command What it checks
docker --version Confirms the Docker CLI is installed and reachable from your PATH.
docker compose version Confirms Compose V2 is installed as the modern docker compose plugin.
docker info Confirms the CLI can talk to a running daemon and prints server details.
docker run --rm hello-world:latest Pulls a tiny test image, creates a container, runs it, prints a success message, and removes the stopped container.

Use docker compose with a space. The old docker-compose command was the standalone Compose V1 binary and should not be used for new lessons or projects.

Examples

Example 1: Verify Docker Desktop on macOS or Windows

After installing Docker Desktop from Docker’s official installer and starting the application, open a terminal. On Windows, PowerShell is fine. On macOS, Terminal, iTerm, or another shell is fine.

docker --version
docker compose version
docker info

Output:

Docker version 29.0.0, build example
Docker Compose version v2.40.0
Client:
 Context:    desktop-linux
Server:
 Containers: 0
 Images: 0

The exact versions will differ, but three things should be true: the CLI prints a Docker version, Compose prints a V2 version, and docker info includes a Server section. If docker info says it cannot connect to the Docker daemon, Docker Desktop is probably not running yet, or your terminal is using the wrong Docker context.

Example 2: Install Docker Engine on Ubuntu from Docker’s Repository

On Ubuntu, use Docker’s official repository so you get current Docker Engine packages and the Compose V2 plugin. These commands set up Docker’s signing key and repository source:

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
sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update

Then install Docker Engine, the CLI, container runtime, Buildx, and Compose:

sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl status docker

Output:

docker.service - Docker Application Container Engine
   Loaded: loaded
   Active: active (running)

This installs the official Docker packages and starts the docker systemd service on typical Ubuntu systems. If the service is not active, start it with sudo systemctl start docker. On servers, this service is the long-running daemon that receives requests from the CLI.

Example 3: Run the Test Image and a Real Web Container

The first run proves that the daemon can pull from a registry, create a container, run its command, and clean up afterward.

docker run --rm hello-world:latest

Output:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.

The hello-world image exits immediately after printing text, so it is only a smoke test. A more realistic test is a web server that keeps running and publishes a port:

docker run -d --name install-nginx -p 8080:80 nginx:1.27-alpine
docker ps --filter name=install-nginx
docker stop install-nginx
docker rm install-nginx

Output:

8f3c2a1b4d5e
CONTAINER ID   IMAGE               STATUS          PORTS                  NAMES
8f3c2a1b4d5e   nginx:1.27-alpine   Up 3 seconds    0.0.0.0:8080->80/tcp   install-nginx
install-nginx
install-nginx

Visiting http://localhost:8080 should reach Nginx while the container is running. The -p 8080:80 flag is what publishes container port 80 to host port 8080. A Dockerfile’s EXPOSE instruction would only document a port; it would not publish it.

How It Works Step by Step

  1. You install Docker Desktop or Docker Engine. This places the docker CLI on your machine and configures a daemon endpoint.
  2. On Linux, the daemon usually runs as the docker systemd service. On macOS and Windows, Docker Desktop starts a managed Linux VM and runs the daemon there.
  3. When you run docker run --rm hello-world:latest, the CLI sends an API request to the daemon through the current Docker context.
  4. The daemon checks whether the image exists locally. If it does not, Docker contacts the configured registry, resolves the tag to a manifest, and downloads the missing layer blobs.
  5. Docker creates a container by mounting the image’s read-only layers and adding a thin writable layer. For hello-world, the container runs one short process and exits.
  6. Because --rm was used, Docker removes the stopped container. The image remains cached locally, so the next run is faster.
  7. For the Nginx example, Docker also creates network plumbing and host port forwarding because you supplied -p 8080:80.

This explains why installation problems often appear as connection errors, not syntax errors. If the CLI exists but the daemon is stopped, docker --version can succeed while docker run fails. If the daemon exists but your user cannot access its socket on Linux, commands may work with sudo but fail without it.

Common Mistakes

Installing an Unofficial or Old Package

sudo apt install docker.io docker-compose

This may install distribution-packaged Docker components that lag behind Docker’s official repository, and it may install the legacy standalone docker-compose binary. Prefer Docker’s official repository and install docker-compose-plugin so docker compose works.

Thinking the CLI Means the Daemon Is Running

docker --version
docker run --rm hello-world:latest

The first command only checks the client. The second requires the daemon. If it fails with a connection error, start Docker Desktop or run sudo systemctl start docker on a systemd-based Linux host.

Adding Yourself to the Docker Group Without Understanding the Risk

sudo usermod -aG docker "$USER"
newgrp docker

On Linux, this lets you run Docker without sudo, but membership in the docker group grants root-level control over the host through the Docker daemon. It is convenient for a developer workstation, but it is a serious security decision on shared machines and production servers.

Using latest as a Setup Habit

docker run --rm nginx:latest

This is acceptable for a quick experiment, but avoid building habits around latest. Tags can move. In examples, teams, and production notes, use a specific tag such as nginx:1.27-alpine so everyone is testing the same image version.

Best Practices

  • Install Docker Desktop on macOS and Windows unless you have a specific server or advanced Linux requirement.
  • Install Docker Engine from Docker’s official Linux repository on servers and Linux development hosts.
  • Verify both the client and daemon with docker --version, docker compose version, docker info, and docker run --rm hello-world:latest.
  • Use docker compose, not the legacy docker-compose command, for new Compose work.
  • Prefer specific image tags in examples and real projects. Treat latest as a moving label.
  • On Linux, consider the security impact before adding users to the docker group. Rootless Docker may be a better fit in locked-down environments.
  • Keep Docker Desktop running before using the CLI on macOS or Windows, and check the active context if commands unexpectedly target the wrong daemon.
  • Remember that Docker Desktop stores Linux containers and named volumes inside its managed VM, not as normal host folders you should edit directly.

Practice Exercises

  1. Install Docker for your operating system and run the four verification commands from the Syntax section. Expected end state: docker info shows a Server section and hello-world prints its success message.
  2. Start nginx:1.27-alpine named practice-install-web and publish it on host port 8090. Hint: use -d, --name, and -p.
  3. On a Linux test machine, decide whether your user should be added to the docker group. Write down the tradeoff: convenience without sudo versus root-level daemon access.

Summary

  • A complete Docker setup includes the CLI, a running daemon, container runtime components, Buildx, and modern Compose.
  • Docker Desktop is the usual path on macOS and Windows because Linux containers run inside a managed Linux VM.
  • Docker Engine is the standard Linux server installation and should come from Docker’s official package repository when possible.
  • docker --version checks the client; docker info and docker run prove the daemon is reachable.
  • hello-world verifies image pull, container creation, process execution, and cleanup.
  • Use docker compose and specific image tags as your default habits from the beginning.