Docker Installation and Setup¶
Overview¶
Installing Docker correctly on day one prevents weeks of permission errors, storage driver mismatches, and "it works on my laptop" CI failures. Production Docker hosts — whether bare-metal build servers, GitLab runners, or Kubernetes worker nodes — all start with the same foundation: Docker Engine installed from official packages, configured with a supported storage driver, and secured so developers can run containers without logging in as root.
This tutorial walks through Linux installation using Docker's official repository, post-installation configuration (daemon settings, log rotation, storage), adding your user to the docker group, and verification steps that belong in every onboarding runbook.
This is Tutorial 2 in Module 1: Foundations of the REBASH Academy Docker series. Complete Introduction to Containers and Docker first. You should be comfortable with Linux package management and Git for version-controlling daemon configs in later tutorials.
Prerequisites¶
- A Linux VM or cloud instance (Ubuntu 22.04/24.04 LTS recommended for this lab)
sudoprivileges on the host- At least 2 GB RAM and 10 GB free disk space
- Outbound HTTPS access to
download.docker.comand container registries - Completion of Introduction to Containers and Docker
- Familiarity with Introduction to Linux — especially
systemctl, users, and groups
Learning Objectives¶
By the end of this tutorial, you will be able to:
- Install Docker Engine from the official Docker repository on Ubuntu/Debian
- Start, enable, and verify the Docker daemon with
systemctl - Add a non-root user to the
dockergroup and understand the security trade-off - Run a test container to confirm the installation works end-to-end
- Configure essential post-install settings: log driver, storage driver, and live-restore
- Uninstall or upgrade Docker cleanly without orphan volumes or images
- Document installation steps for reproducible infrastructure provisioning
Architecture Diagram¶
After installation, your host runs the Docker daemon as a systemd service. The CLI communicates over a Unix socket — understanding this path explains permission errors and remote Docker contexts.
flowchart LR
USER[User — docker group]
CLI["Docker CLI<br/>/usr/bin/docker"]
SOCK["/var/run/docker.sock"]
DAEMON["dockerd<br/>systemd service"]
CONTAINERD[containerd]
RUNC[runc]
CONT[Containers]
USER --> CLI
CLI --> SOCK
SOCK --> DAEMON
DAEMON --> CONTAINERD
CONTAINERD --> RUNC
RUNC --> CONT```
## Theory
### Installation Methods Compared
| Method | Best for | Pros | Cons |
|--------|----------|------|------|
| **Official Docker repo** | Production Linux servers | Latest stable, supported packages | Requires repo setup |
| **Distro packages** (`apt install docker.io`) | Quick local experiments | One command | Often outdated; not Docker Inc. supported |
| **Convenience script** (`get.docker.com`) | Ephemeral CI agents | Fast automation | Less control; review script in prod |
| **Docker Desktop** | macOS/Windows dev machines | GUI, Kubernetes included | Not for Linux production servers |
| **Rootless Docker** | Shared dev machines, security-sensitive | No root required to run daemon | Limitations on networking and storage |
This tutorial focuses on the **official repository method** for Ubuntu/Debian — the pattern used in production runbooks and Ansible playbooks.
### What Gets Installed
Docker Engine installation typically includes:
| Package | Purpose |
|---------|---------|
| `docker-ce` | Docker Engine (Community Edition) — the daemon |
| `docker-ce-cli` | Docker command-line client |
| `containerd.io` | Container runtime managing image and container lifecycle |
| `docker-buildx-plugin` | Extended build capabilities (multi-platform) |
| `docker-compose-plugin` | Compose v2 as a Docker CLI plugin |
The daemon (`dockerd`) runs as root, listens on `/var/run/docker.sock`, and is managed by systemd.
### The docker Group and Security
By default, only **root** and members of the **docker** group can access the Docker socket. Adding a user to `docker` is convenient but grants **effective root access** on the host — any user who can run Docker can mount host filesystems, escalate privileges, and bypass many security controls.
Production patterns:
- **Developer laptops** — docker group membership is common; accept the risk on single-user machines
- **Shared CI runners** — isolate with dedicated VMs, rootless Docker, or Kubernetes agents
- **Production servers** — restrict docker group membership; use orchestrators (Kubernetes) instead of direct Docker access
### Post-Installation Configuration
Key daemon settings in `/etc/docker/daemon.json`:
| Setting | Purpose |
|---------|---------|
| `log-driver` | Default logging (`json-file` with rotation limits) |
| `storage-driver` | `overlay2` on modern Linux (default) |
| `live-restore` | Keep containers running during daemon restarts |
| `default-address-pools` | Prevent IP exhaustion in large Swarm/bridge networks |
| `registry-mirrors` | Pull acceleration in regions far from Docker Hub |
Always validate JSON syntax after editing: `docker info` should succeed without errors.
### systemd Integration
Docker Engine integrates with systemd:
- **Enable on boot:** `systemctl enable docker`
- **Start/stop/restart:** `systemctl start|stop|restart docker`
- **Logs:** `journalctl -u docker.service -f`
- **Reload config:** edit `daemon.json`, then `systemctl reload docker` or restart
On Kubernetes nodes, containerd may run independently; Docker is increasingly replaced by containerd + CRI. Understanding Docker installation still matters because build pipelines and developer workflows rely on it heavily.
## Hands-on Lab
These steps install Docker Engine on **Ubuntu 24.04 LTS**. Adapt package manager commands for RHEL/Rocky (`dnf`) using [Docker's official docs](https://docs.docker.com/engine/install/).
### Step 1 – Remove conflicting packages (if any)
**Command:**
```bash
sudo apt-get remove -y docker docker-engine docker.io containerd runc 2>/dev/null || true
dpkg -l | grep -i docker || echo "No legacy Docker packages found" Explanation: Old distro packages conflict with Docker CE. Clean removal prevents socket and binary path conflicts.
Expected output:
Step 2 – Install prerequisites and add Docker GPG key¶
Command:
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
Explanation: APT requires a signed repository. The GPG key verifies package integrity — never skip this step in production.
Expected output:
Step 3 – Add the Docker APT repository¶
Command:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
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-get update
Explanation: Pins Docker CE to the stable channel. Use test channel only in non-production lab environments.
Expected output:
Step 4 – Install Docker Engine packages¶
Command:
sudo apt-get install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
Explanation: Installs the full Engine stack including Buildx and Compose plugins used throughout this track.
Expected output:
Step 5 – Verify daemon status and run hello-world¶
Command:
Explanation: Confirms the daemon is active and can pull from Docker Hub, create a container, and execute successfully.
Expected output:
● docker.service - Docker Application Container Engine
Active: active (running)
...
Hello from Docker!
This message shows that your installation appears to be working correctly.
Step 6 – Add your user to the docker group¶
Command:
Explanation: usermod -aG appends the group without removing others. Log out and back in (or newgrp) for group membership to take effect.
Expected output:
Step 7 – Configure daemon.json with log rotation¶
Command:
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"live-restore": true
}
EOF
sudo systemctl restart docker
docker info 2>/dev/null | grep -E "Logging Driver|Live Restore|Storage Driver"
Explanation: Unbounded container logs fill disks — a top cause of production outages. live-restore keeps containers running during daemon upgrades.
Expected output:
Step 8 – Capture installation baseline¶
Command:
Explanation: Document versions in runbooks. groups confirms docker group membership.
Expected output:
Commands & Code¶
| Command | Description | Example |
|---|---|---|
systemctl enable docker | Start Docker on boot | sudo systemctl enable docker |
systemctl status docker | Check daemon state | systemctl status docker |
docker version | Show client and server versions | docker version |
docker info | Display system-wide Docker info | docker info |
usermod -aG docker USER | Grant user Docker socket access | sudo usermod -aG docker alice |
docker run hello-world | Verify end-to-end installation | sudo docker run --rm hello-world |
journalctl -u docker | View daemon logs | journalctl -u docker.service -n 50 |
Installation verification script¶
Save as ~/bin/docker-verify.sh:
#!/usr/bin/env bash
# docker-verify.sh — post-install checks for Docker Engine
set -euo pipefail
section() { printf '\n=== %s ===\n' "$1"; }
section "Service Status"
if systemctl is-active --quiet docker; then
echo "docker.service: active"
else
echo "ERROR: docker.service not running"
exit 1
fi
section "Version"
docker version | head -8
section "Hello World"
docker run --rm hello-world | tail -3
section "User Permissions"
if docker info >/dev/null 2>&1; then
echo "Current user can access Docker socket"
else
echo "WARNING: Cannot access socket — add user to docker group or use sudo"
fi
section "Storage and Logging"
docker info 2>/dev/null | grep -E "Storage Driver|Logging Driver|Live Restore"
section "Disk Space"
df -h /var/lib/docker 2>/dev/null || df -h /
Make executable: chmod +x ~/bin/docker-verify.sh && ~/bin/docker-verify.sh
MkDocs template safety
The docker version --format flag uses Go templates with double-brace syntax. In MkDocs pages processed by Jinja macros, prefer jq or plain docker version output instead of embedding those braces in markdown.
Common Mistakes¶
Installing docker.io from default Ubuntu repos for production
The distro package is often outdated and lacks Docker Inc. support. Use the official repository for production and CI images.
Adding every user to the docker group on shared servers
Docker socket access equals root privileges. On multi-tenant hosts, use rootless Docker, separate VMs, or Kubernetes with RBAC.
Skipping log rotation in daemon.json
Default json-file logs grow without bound. Always set max-size and max-file on production hosts.
Forgetting to restart the daemon after daemon.json changes
Invalid JSON in /etc/docker/daemon.json prevents Docker from starting. Validate with python3 -m json.tool /etc/docker/daemon.json before restart.
Best Practices¶
Pin Docker versions in production
Document the exact Engine version in Ansible/Terraform. Test upgrades in staging before rolling to build farms and worker nodes.
Automate installation with IaC
Store installation steps as Ansible roles or cloud-init scripts in Git. Manual installs drift; automated installs are reproducible.
Monitor /var/lib/docker disk usage
Images and layers accumulate. Schedule docker system prune policies carefully — never prune production nodes without understanding impact.
Use official docs for RHEL/Rocky and ARM
Package names and repo URLs differ by distribution. Always reference Docker Engine install docs for your OS.
Troubleshooting¶
| Issue | Cause | Solution |
|---|---|---|
Cannot connect to the Docker daemon | Daemon not running or user lacks socket access | sudo systemctl start docker; add user to docker group |
permission denied while trying to connect | User not in docker group | sudo usermod -aG docker $USER; re-login |
docker: command not found | CLI not installed or PATH issue | Reinstall docker-ce-cli; check /usr/bin/docker |
| Daemon fails after daemon.json edit | Invalid JSON syntax | Validate JSON; check journalctl -u docker |
hello-world pull timeout | Network/firewall blocking registry | Test curl -I https://registry-1.docker.io; configure proxy |
| Wrong storage driver | Old kernel or misconfiguration | Use overlay2 on modern kernels; check docker info |
| Group change not effective | Session cached old groups | Log out/in or run newgrp docker |
Summary¶
- Install Docker Engine from the official Docker repository — not outdated distro packages — for supported, current releases
- The stack includes dockerd, containerd, CLI, Buildx, and Compose plugins
- The docker group grants socket access without sudo but is equivalent to root — use carefully on shared systems
- Post-install essentials: log rotation, overlay2 storage, live-restore, and documented versions
- Verify with
systemctl status docker,docker version, anddocker run hello-world - Next: understand what you installed in Docker Architecture and Components
Interview Questions¶
- What packages are installed when you set up Docker Engine from the official repository?
- Why does adding a user to the docker group have security implications?
- What is the purpose of
/etc/docker/daemon.json? - How do you enable Docker to start automatically on boot?
- What is the difference between Docker CE and Docker Desktop?
- Why should container log rotation be configured on production hosts?
- What does
live-restoredo in the Docker daemon configuration? - How would you verify a Docker installation without relying on GUI tools?
- What is rootless Docker, and when would you choose it?
- How do you troubleshoot "Cannot connect to the Docker daemon" errors?
Sample Answers (Questions 2, 6, and 9)
Q2 — docker group security: The Docker daemon runs as root and exposes a Unix socket at /var/run/docker.sock. Members of the docker group can send API requests to this socket, including mounting host paths into containers and running privileged containers. An attacker with docker group access can mount / and escalate to full root on the host.
Q6 — Log rotation: The default json-file log driver writes container stdout/stderr to files under /var/lib/docker/containers/. Without max-size and max-file limits, busy containers can generate gigabytes of logs, filling the disk and causing daemon and application failures.
Q9 — Rootless Docker: Rootless mode runs the Docker daemon and containers as a non-root user using user namespaces. It reduces the blast radius on shared machines but has limitations: restricted networking features, different socket path, and some storage driver constraints. Choose it for developer workstations or security-sensitive environments where sudo access is restricted.
Related Tutorials¶
- Docker – Category Overview
- Introduction to Containers and Docker (previous in Module 1)
- Docker Architecture and Components (next in Module 1)
- Introduction to Linux
- Git Installation and Configuration