Docker & ContainersIII · Installation & DaemonInstallation methods
Supported installation methods
What you'll learn
- Choose the right installation method for a given host
- Understand the trade-offs between the Docker repository, distro packages, binaries and the convenience script
- Remove the conflicting packages that make an install fail in confusing ways
- Recognise when to use rootless Docker, and what it costs you
- Verify an installation with a check that can fail
Prerequisites
None — start here.
Verified against Docker Engine 29.x · Docker Engine 28.x · Docker Compose 2.x · containerd 2.x · runc 1.2.x · BuildKit 0.20+ · Linux kernel 5.15+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-12
There are five ways to get Docker Engine onto a Linux host. The right one depends less on which is easiest today and more on a question you will be asked during your first CVE response: how do I upgrade this, and how fast?
That is what an installation method really is. Everything else — which directory the binaries land in, whether there is a systemd unit — follows from it.
Clear the ground first
Almost every “the install failed for no reason” report starts here. Distros
ship their own container packages, and several of them provide /usr/bin/docker
or a conflicting containerd. The Docker documentation names them explicitly.
for pkg in docker.io docker-doc docker-compose docker-compose-v2 \
docker-buildx podman-docker containerd runc; do
sudo apt-get remove -y "$pkg"
doneOption 1 — Docker’s official apt/yum repository
The recommended approach for production, and the one the rest of this course assumes.
sudo apt-get update
sudo apt-get 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 "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-pluginThat installs five packages:
docker-ce— the daemon,dockerd.docker-ce-cli— thedockerclient.containerd.io— the container supervisor.docker-buildx-plugin—docker buildx.docker-compose-plugin—docker compose.
Updates come from Docker, not your distro, so you get new Engine versions
within days of release rather than at the next distro point release. That speed
is the whole point, and it is also why the very next lesson is about pinning:
an unpinned docker-ce on a fleet means an unattended-upgrade run can change
your container runtime on a Tuesday night.
Option 2 — Distribution packages
Debian, Ubuntu, Fedora, RHEL, SUSE and others ship their own Docker or Podman packages. They are older than Docker’s, and they move on the distro’s cadence.
Choose this when:
- Your compliance regime requires every package to come from the distro vendor, with the distro’s signature and the distro’s support contract.
- You are on RHEL or a rebuild where the vendor position is Podman, and deviating from it costs you support.
- The slower cadence is acceptable because you patch on the distro’s schedule anyway.
The cost is real and worth naming: when a container-runtime CVE lands, you wait for your distro to backport it. That is sometimes faster than you fear (distros backport security fixes aggressively) and sometimes much slower.
Option 3 — Static binaries
Download tarballs of dockerd, docker, containerd and runc and place
them yourself. Docker documents this and is clear it is the least supported
path: no systemd unit, no daemon.json, no user or group, no /var/lib/docker
— you create all of it.
Legitimate uses:
- Air-gapped environments where no repository is reachable, and the artefact is staged through an approved transfer process.
- Immutable/appliance images built by a pipeline that bakes an exact set of binaries and never runs a package manager at runtime.
The trap is that a binary install has no upgrade mechanism at all. Whatever replaces those files is code somebody has to write and test, and that code is the actual deliverable — not the install.
Option 4 — The convenience script
https://get.docker.com detects your distro and does everything. The Docker
documentation states plainly: “The convenience script isn’t recommended for
production environments, but it’s useful for creating a provisioning script
tailored to your needs.”
The reasons it gives are the reasons to take seriously: it requires root, makes every decision for you, installs dependencies without confirmation, and does not let you customise most parameters. It is genuinely useful for a laptop, a throwaway VM, or as a reference for the provisioning code you write yourself.
Option 5 — Rootless Docker
Runs the whole daemon as an unprivileged user inside a user namespace, so a container escape lands on an ordinary user account rather than on root.
# Prerequisite: newuidmap/newgidmap, from the uidmap package,
# and at least 65536 subordinate IDs in /etc/subuid and /etc/subgid.
grep "^$(id -un):" /etc/subuid /etc/subgid
dockerd-rootless-setuptool.sh install
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
docker info --format '{{.Name}} rootless={{.SecurityOptions}}'The rootless daemon listens on a socket under /run/user/<uid>/ and is managed
by systemd --user, not by the system instance. That is the source of most
rootless confusion: sudo systemctl status docker reports the system daemon,
sudo docker ps talks to the system daemon, and neither has anything to do
with your rootless one. Configuration lives in
~/.config/docker/daemon.json, not /etc/docker/daemon.json.
It is the right answer for a shared CI runner, where the job you are running was written by somebody who does not have root on the box and should not gain it. It is a poorer answer where you need low-numbered host ports, host networking, or the full set of cgroup resource controls without additional delegation setup — the rootless documentation lists the current limitations, and they change between releases, so check them against the version you are deploying rather than against a blog post.
What about Docker Desktop?
Docker Desktop exists for Linux, and Docker’s own install page offers it. It is
a development environment, not a server runtime: it runs a virtual machine, and
the docs state it “creates and uses a custom docker context, desktop-linux, on
startup.”
$ docker context lsNAME DESCRIPTION DOCKER ENDPOINT
default * Current DOCKER_HOST based configuration unix:///var/run/docker.sock
desktop-linux Docker Desktop unix:///home/ops/.docker/desktop/docker.sockIllustrative output
Decision matrix
- Production Linux server — Docker official repo, version pinned, upgrades in a controlled window. Everything else is a deviation you should be able to justify.
- Air-gapped — Static binaries or a mirrored repository. Whichever you pick, the upgrade tooling is the deliverable, not the install.
- Shared CI runner — Rootless Docker. The job author does not get root on the runner.
- Single-tenant dev host — Docker official repo. Same as production, so that "works on my machine" means something.
- Developer workstation — Docker Desktop is fine, and knowing about
docker contextis not optional. - Hardened baseline (CIS, STIG) — Distro packages plus documented divergences, or the vendor runtime (Podman on RHEL). Fighting the vendor position costs you support.
- Embedded / resource-constrained — Probably not Docker Engine. containerd alone, or Podman, or no container runtime at all.
Verification that can fail
docker run hello-world proves the whole stack works, which is the point — but
only if you actually check the result rather than watching text scroll past.
set -e
# 1. The client can reach a daemon, and it is the one you expect.
docker context show
docker info --format 'server={{.ServerVersion}} storage={{.Driver}} cgroup={{.CgroupDriver}} runtime={{.DefaultRuntime}}'
# 2. All three layers are present and agree.
dockerd --version
containerd --version
runc --version
# 3. The full path works: pull, create, run, exit 0, remove.
docker run --rm hello-world >/dev/null
echo "end-to-end OK"
# 4. The daemon will come back after a reboot.
systemctl is-enabled docker.serviceStep 4 is the one people skip, and it is the one that produces a host that has been “working fine” for four months and comes back from a kernel patch with no containers.
Knowledge check
Knowledge check · 6 questions
Q1. You install docker-ce from Docker's apt repository on Ubuntu. Do you need `systemctl enable --now docker`?
Q2. Which packages does the Docker documentation tell you to remove before installing docker-ce on Ubuntu? Select all that apply.
Q3. The docker-ce package adds root to the `docker` group so that root can use the socket.
Q4. On a workstation with both Docker Desktop and Docker Engine, an image you built yesterday no longer appears in `docker images`. What is the most likely explanation?
Q5. What does the Docker documentation say about the convenience script at get.docker.com?
Q6. Editing /etc/docker/daemon.json and reloading docker.service has no effect on a rootless Docker daemon.
Passing score: 75%. Answers are checked in this browser.