Step-by-Step: Installing Docker on Linux (Ubuntu) with Real-World Safeguards
Correct Docker installation underpins container reliability and security. Treating it as a basic package install ignores deeper system implications: kernel settings, user permissions, storage strategies. For production, these details determine system stability and minimize attack surface. This guide targets Ubuntu 20.04+ (tested with 22.04 LTS), but steps are transferable to Debian-based systems. File paths, group management, and package signatures—all matter.
1. Baseline OS Preparation
Critical: Update the package cache and ensure HTTPS transport support. Failing this, apt
fetches can silently fail or expose your system to old packages. Run:
sudo apt-get update
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
software-properties-common
On minimal VMs, software-properties-common
is sometimes omitted—its absence breaks add-apt-repository
functionality (not used directly below, but required for key handling on some distributions).
2. Secure Docker’s GPG Key
No signature, no trust. Import Docker's GPG key and save it using armored storage for apt:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
Side note: Key rotation occasionally breaks old instructions—Docker changed key fingerprints in late 2022. Always inspect contents with:
gpg --show-keys /usr/share/keyrings/docker-archive-keyring.gpg
3. Register Official Docker Repository
Why not use Ubuntu’s default docker.io
package? Latency: repo lags six months or more behind Docker Inc. releases. To track stable mainline builds:
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
Replace $(lsb_release -cs)
with your codename if the auto-detect fails; e.g., jammy
for 22.04.
4. Install Docker Engine & Core Components
Update package index or apt may fetch from Ubuntu main instead of Docker’s repo.
sudo apt-get update
sudo apt-get install docker-ce=5:24.0.7~ubuntu.22.04~jammy docker-ce-cli=5:24.0.7~ubuntu.22.04~jammy containerd.io
Use apt-cache madison docker-ce
to see available versions. Pinning versions is standard in CI/CD for reproducibility.
Components:
docker-ce
: Actual Docker daemon and server logic.docker-ce-cli
: User CLI; useful to install separately for jump hosts.containerd.io
: Industry standard OCI runtime.
5. Smoke Test: Docker Daemon & Container Launch
Verify Engine install:
sudo systemctl status docker
Typical healthy output:
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since...
Test container bootstrap:
sudo docker run --rm hello-world
Failure modes:
Cannot connect to the Docker daemon
: The service isn’t running (systemd misconfiguration or missing/var/run/docker.sock
).permission denied while trying to connect...
: User lacks group permissions (see next section).
6. Running Docker as Non-Root
Docker as root is a known privilege escalation risk. The following creates a docker
group and puts your user inside:
sudo groupadd docker 2>/dev/null || true
sudo usermod -aG docker $USER
Log out and in—group membership won’t update in open shells.
Test:
docker run --rm hello-world
No sudo
required. For multi-user systems: periodically review members of the docker
group.
7. Persistent Service Enablement
On cloud VMs, containers need to survive reboots—docker.service
should be enabled:
sudo systemctl enable docker
sudo systemctl start docker
Check logs for diagnostics—on misconfigured kernels, overlayfs or seccomp errors will appear here:
journalctl -u docker
8. Practical Example: Deploy a Custom Container
Deploy a namespaced NGINX server mapped to port 8080:
docker run -d --name demo-nginx -p 8080:80 nginx:1.25-alpine
Query its status:
docker ps --filter name=demo-nginx
Gotcha: If -p 8080:80
fails (bind: address already in use
), the port’s occupied—choose another.
To tear down:
docker stop demo-nginx && docker rm demo-nginx
Non-Obvious Tips
- Kernel Compatibility: For Docker 24.x, use Linux kernel ≥ 5.10 for optimal cgroups v2 and overlay2 support. Outdated kernels manifest as unexplained container deadlocks or errors like
failed to mount overlay
. - Storage Footprint: Default Docker root (
/var/lib/docker
) can easily fill disks on CI runners. Configuredockerd
with--data-root=/mnt/docker
if using ephemeral or dedicated volumes. - Image Source Validation: Use
docker pull --disable-content-trust=false
for signature checks on images where provenance is critical (not all upstream images are signed). - Partial Isolation Risk: Adding users to
docker
group effectively grants root; for strict compliance environments, prefer rootless Docker mode—though not all network plugins are compatible. - Daemon Reload: On configuration changes (
/etc/docker/daemon.json
), always reload instead of restart for minimal impact:
Not all options support reload; check logs.sudo systemctl reload docker
Conclusion
Manual Docker installation demands closer inspection than “curl | sh” scripts. Each step—repository trust, GPG management, user groups, kernel settings—absorbs hidden risks if skipped or misunderstood. Production systems rarely behave like tutorials; version pinning and audit logging are routine. For edge cases, refer to Docker’s official install docs, then adapt—never blindly copy-paste.
For troubleshooting: /var/log/syslog
and docker info
provide early signals on resource or system call issues. Stay vigilant, monitor storage, and keep the engine patched.
ASCII Docker Lifecycle Sketch
[Client CLI] -> [Docker Daemon] -> [containerd] -> [runc]
|
[overlay2, aufs, etc.]