Docker has become the default packaging format for almost every service we deploy. Ubuntu's distro docker.io package usually trails upstream by a release or two; for production, install from Docker's official APT repository instead. This guide walks through that install plus the Compose v2 plugin, post-install hardening, and a sample stack.
Prerequisites
- Ubuntu 24.04 LTS with
sudo. - A non-root user that will run
dockercommands. - Outbound TCP/443 to
download.docker.com.
Step 1: Remove old packages and add Docker's GPG key
# Remove anything from the distro that might conflict
sudo apt remove -y docker docker-engine docker.io containerd runc 2>/dev/null || true
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release
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
Step 2: Add the Docker repository
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 update
Step 3: Install Docker Engine + Compose plugin
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Verify:
docker --version
docker compose version
sudo docker run --rm hello-world
The third command pulls and runs the hello-world image. If it prints "Hello from Docker!" you are ready.
Step 4: Post-install — run Docker without sudo
sudo usermod -aG docker $USER
newgrp docker
docker ps
Security note: members of the
dockergroup can mount the host filesystem inside a container and escalate to root. Treat it as equivalent to sudo and limit membership accordingly.
Enable the daemon on boot:
sudo systemctl enable --now docker containerd
Step 5: Configure the daemon — log rotation and a default address pool
A misconfigured Docker daemon will quietly fill /var/lib/docker with multi-gigabyte JSON log files. Drop /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"default-address-pools": [
{ "base": "172.30.0.0/16", "size": 24 }
],
"live-restore": true,
"userland-proxy": false
}
Apply:
sudo systemctl restart docker
sudo docker info | grep -E 'Logging Driver|Default'
Step 6: A sample Compose stack
Create ~/stacks/web/compose.yaml:
name: skyline-web
services:
web:
image: nginx:1.27-alpine
restart: unless-stopped
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost/"]
interval: 30s
timeout: 5s
retries: 3
cache:
image: redis:7-alpine
restart: unless-stopped
command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"]
volumes:
- cache_data:/data
volumes:
cache_data:
Bring it up:
cd ~/stacks/web
mkdir -p html && echo "<h1>Hello from SKYLINE</h1>" > html/index.html
docker compose up -d
docker compose ps
docker compose logs --tail=20
Visit http://server.ip:8080/ — you should see the greeting.
Step 7: Routine operations
Day-to-day commands that earn their keep:
docker ps # what's running
docker stats --no-stream # CPU + RAM per container
docker system df # disk used by Docker
docker system prune -af --volumes # clean stopped containers, dangling images, volumes
docker compose pull && docker compose up -d # safe update of all services
Conclusion
You now have a current Docker Engine, the Compose v2 plugin, sensible log rotation, and a Compose stack you can build on. Treat the Compose file as code: commit it, review it, deploy it via CI — not from a developer laptop.
Next steps
- For LAMP applications running alongside containers, follow the LAMP guide.
- Tighten the host with the SSH + UFW + Fail2ban guide.
- Wire in unattended security upgrades so Docker, the kernel and the base system patch themselves.
Comments
0 total · 0 threads