Mastering Docker Installation on Linux: A Step-by-Step Practical Guide
Deployment pipelines, CI/CD workflows, stateful services—all routinely demand isolation, portability, and reproducibility. Odds are, you’ll need Docker running on Linux before your first production deployment or local integration test ever passes. But “installation” isn’t just a curl-bash script: package source hygiene, systemd implications, and kernel module compatibility matter. Below, practical steps—distilled from production setups—for Ubuntu and RHEL-based distros, with common gotchas annotated.
Pre-Install Checklist: Environment Sanity and Old Binaries
Failing to purge legacy Docker traces (“docker-engine”, “containerd”, etc.) can trigger obscure daemon conflicts:
# Ubuntu/Debian
sudo apt-get remove -y docker docker-engine docker.io containerd runc
# CentOS/RHEL
sudo yum remove -y docker docker-client docker-client-latest docker-common \
docker-latest docker-latest-logrotate docker-logrotate docker-engine
Verify your distribution and architecture—critical for repo setup:
lsb_release -a # Ubuntu/Debian
cat /etc/redhat-release # CentOS/RHEL/Fedora
uname -m # Architecture, e.g. x86_64 or aarch64
Note: Some lightweight or cloud-optimized distros (AlmaLinux, Amazon Linux 2, etc.) may require minor adjustments.
Ubuntu 20.04/22.04+: Official Repo and Engine Deployment
Repository trust is non-negotiable. Skip unofficial mirrors—even a minor difference in signing keys compromises the “supply” side of your supply chain.
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg lsb-release
Add Docker’s GPG key and source list (as of 2024, the repo location remains stable):
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
Now install the complete engine, including CLI and runtime:
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
Verify with:
docker --version
# Docker version 24.0.6, build ed223bc
A simple CI test: can you pull and run from Docker Hub?
sudo docker run --rm hello-world
Gotcha: If docker.socket
or containerd
fails to start, check /var/log/syslog
for AppArmor or cgroup v2 issues—particularly in hardened or custom kernel environments.
CentOS 7/8, RHEL 8+: Engine, Repo, Service Management
Minimal images frequently lack yum-utils
. Install dependencies or the install will fail, mid-way.
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
Add and verify the Docker repository:
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum makecache
For CentOS Stream or RHEL 8, swap yum
for dnf
as needed.
Install the latest stable engine:
sudo yum install -y docker-ce docker-ce-cli containerd.io
After package install: systemd must manage the lifecycle. Enable on boot, then start manually to avoid orphaned containers after reboots.
sudo systemctl enable docker
sudo systemctl start docker
Verify health and logs:
sudo systemctl status docker
journalctl -u docker --since "10 minutes ago"
Test container launch:
sudo docker run --rm hello-world
Note: With hybrid physical/virtual RHEL clusters, storage driver defaults can mismatch (e.g., overlay2
vs. devicemapper
). Inspect with docker info
and adjust /etc/docker/daemon.json
if needed.
Post-Install: Secure Access and Shell Usability
By default, Docker’s UNIX socket at /var/run/docker.sock
is root-owned, which is deliberate. To allow non-root Docker commands (full container access is granted):
sudo usermod -aG docker $USER
newgrp docker
May require shell re-login; test access:
docker run --rm hello-world
Security note: Never expose the Docker socket (-H tcp://0.0.0.0:2375
or similar) unless it’s strictly firewalled—privilege escalation risk is substantial.
Troubleshooting: What Breaks, and Why
-
Daemon unreachable:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
Usually either
systemctl start docker
is missing or group permissions aren’t applied. Check withid $USER
and verify restart. -
Installation blocked by firewall or proxy:
Manifests as
Timeout while connecting to ...
. Either sethttp_proxy
/https_proxy
environment variables or ensure outbound 443 is allowed. -
Kernel version unsupported for overlay2:
Typical with self-customized kernels or old VPS images. Confirm with:
uname -r docker info | grep 'Storage Driver'
Upgrade kernel or adjust
daemon.json
. -
Stray or stale packages:
After repo/key changes, always:
sudo apt-get clean sudo apt-get update
Practical Example: Multi-Arch Setup on a Build Host
Building cross-architecture images? Validate QEMU and binfmt_misc support post-install:
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
docker run --rm --platform linux/arm64 alpine uname -m
# Expected output: aarch64
Beyond Installation
Next logical steps: iteration with a minimal Dockerfile, basic Compose orchestration, or hardening the runtime with user namespaces in /etc/docker/daemon.json
. In CI, always lock engine minor versions for reproducibility (apt-mark hold docker-ce
).
Example minimal Dockerfile for node.js:
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm ci
CMD ["npm", "start"]
Known issue: Some CI runners (GitHub-hosted Ubuntu 22.04, for instance) pre-install an older containerd; purge before custom-engine installation to avoid clingy segfaults.
No installation is ever perfect—expect subtle edge cases around kernel, shell, or new LTS releases. Still, following the above will get you a resilient Docker host, not just a passing “hello-world”. For edge-case errors or environment-specific questions, scan /var/log/syslog
, use strace
, and avoid trial-and-error via random forum answers.