Containers & Orchestration — Docker, Podman & Compose
A practical guide to Docker, Podman, Docker Compose, and container orchestration for self-hosters — from first container to production stacks.
Containers Changed Everything
Before containers, installing software meant wrangling dependencies, conflicting library versions, and praying that an upgrade didn't break something else. Containers wrap each application in its own isolated environment — same host, no conflicts.
For self-hosters, this means you can run 20 different services on one machine without them interfering with each other.
Docker vs Podman
| Feature | Docker | Podman |
|---|---|---|
| Daemon | Requires dockerd (root) | Daemonless |
| Rootless | Possible but complex | Native |
| Compose | docker compose (v2) | podman-compose |
| Ecosystem | Massive | Growing |
| Compatibility | Standard | Docker-compatible CLI |
| Systemd integration | Limited | Native (podman generate systemd) |
For most self-hosters: Docker is the practical choice. The ecosystem is larger, documentation is more abundant, and nearly every self-hosted project provides a Docker image and Compose file.
Podman is better if: You're security-conscious about running a root daemon, or you're on a system where Docker isn't available (some enterprise Linux distributions).
Docker Fundamentals
Images, Containers, and Volumes
- Image — A read-only template (like a blueprint). You pull images from Docker Hub or GitHub Container Registry.
- Container — A running instance of an image. You can have multiple containers from the same image.
- Volume — Persistent storage that survives container restarts and recreations.
Essential Commands
# Pull an image
docker pull nextcloud:latest
# Run a container
docker run -d --name myservice -p 8080:80 -v data:/app/data nextcloud:latest
# List running containers
docker ps
# Stop / Start / Restart
docker stop myservice
docker start myservice
docker restart myservice
# View logs
docker logs myservice
docker logs -f myservice # Follow in real-time
# Enter a running container
docker exec -it myservice bash
# Remove a container (preserves volumes)
docker rm myservice
# Remove unused images
docker image pruneDocker Compose: The Self-Hoster's Standard
Compose lets you define multi-container stacks in a single YAML file. This is how most self-hosters manage their services.
Example: Full Home Lab Stack
version: "3.8"
services:
# ── Reverse Proxy ─────────────────
caddy:
image: caddy:2-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
restart: unless-stopped
# ── File Sync ─────────────────────
nextcloud:
image: nextcloud:latest
volumes:
- nc_data:/var/www/html
environment:
- POSTGRES_HOST=nc_db
- POSTGRES_DB=nextcloud
depends_on:
- nc_db
restart: unless-stopped
nc_db:
image: postgres:16-alpine
volumes:
- nc_db_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=nextcloud
restart: unless-stopped
# ── Password Manager ─────────────
vaultwarden:
image: vaultwarden/server:latest
volumes:
- vw_data:/data
restart: unless-stopped
# ── Media Server ──────────────────
jellyfin:
image: jellyfin/jellyfin:latest
volumes:
- jf_config:/config
- /mnt/media:/media:ro
restart: unless-stopped
# ── DNS & Ad Blocking ────────────
pihole:
image: pihole/pihole:latest
ports:
- "53:53/tcp"
- "53:53/udp"
volumes:
- pihole_data:/etc/pihole
environment:
- TZ=Europe/London
restart: unless-stopped
# ── Monitoring ────────────────────
uptime-kuma:
image: louislam/uptime-kuma:latest
volumes:
- kuma_data:/app/data
restart: unless-stopped
volumes:
caddy_data:
caddy_config:
nc_data:
nc_db_data:
vw_data:
jf_config:
pihole_data:
kuma_data:Compose Commands
# Start all services
docker compose up -d
# Stop all services
docker compose down
# Update all images
docker compose pull
docker compose up -d
# View logs for a specific service
docker compose logs -f nextcloud
# Rebuild after Compose file changes
docker compose up -d --force-recreateContainer Networking
By default, Docker creates a bridge network for each Compose stack. Containers in the same stack can reach each other by service name.
For more control:
networks:
frontend:
driver: bridge
backend:
driver: bridge
services:
caddy:
networks:
- frontend
nextcloud:
networks:
- frontend
- backend
nc_db:
networks:
- backendThis ensures the database is only reachable from Nextcloud, not from the reverse proxy.
Updates and Maintenance
Watchtower — Automatic Updates
Watchtower monitors running containers and automatically pulls new images:
services:
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_SCHEDULE=0 0 4 * * * # 4 AM daily
restart: unless-stoppedCaution: Automatic updates can break things. Consider using Watchtower in notification-only mode for critical services, and auto-update only for low-risk ones.
Resource Limits
Prevent a single container from consuming all system resources:
services:
nextcloud:
image: nextcloud:latest
deploy:
resources:
limits:
cpus: "2.0"
memory: 2G
reservations:
cpus: "0.5"
memory: 512MHealth Checks
Docker can monitor container health and restart unhealthy containers:
services:
nextcloud:
image: nextcloud:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/status.php"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60sBeyond Docker Compose
When one machine isn't enough:
- Docker Swarm — Built-in orchestration for multi-node Docker clusters. Simpler than Kubernetes, sufficient for most self-hosters.
- Kubernetes (K3s) — Lightweight Kubernetes for home labs. Overkill for most setups, but educational.
- Nomad — HashiCorp's alternative to Kubernetes. Simpler, multi-workload (Docker + VMs + raw binaries).
For the vast majority of self-hosters, a single machine with Docker Compose is more than enough.
Product links may include affiliate partnerships — see our affiliate disclosure for details.