Containers Changed Everything
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.