The Composes You Actually Need
Most Docker compose guides show you the same five services everyone already knows. Here’s the set I actually run — the unsexy but essential services that make a homelab reliable: monitoring, logging, backups, and automation.
These are production-ready templates with proper networking, restart policies, healthchecks, and resource limits. No placeholder values, no “TODO: change this.”
1. Watchtower — Auto-Updates
Automatically updates your containers when new images are published. I run this on a 24-hour schedule with notifications to my Telegram channel.
services:
watchtower:
image: containrrr/watchtower:latest
container_name: watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/watchtower:/watchtower
- /etc/localtime:/etc/localtime:ro
environment:
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_REMOVE_IMAGES=true
- WATCHTOWER_SCHEDULE=0 4 * * * # 4am daily
- WATCHTOWER_NOTIFICATIONS=shoutrrr
- WATCHTOWER_NOTIFICATION_URL=${WATCHTOWER_WEBHOOK_URL}
- TZ=America/New_York
mem_limit: 128m
cpu_shares: 128
networks:
- homelab
networks:
homelab:
name: homelab_net
driver: bridge
Store your webhook URL in a .env file:
2. Uptime Kuma — Service Monitoring
Tracks whether your services are responding and alerts you when they go down. I monitor all 8 services on my network.
services:
uptime-kuma:
image: louislam/uptime-kuma:latest
container_name: uptime-kuma
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- ./data:/app/data
- /var/run/docker.sock:/var/run/docker.sock
- /etc/localtime:/etc/localtime:ro
environment:
- TZ=America/New_York
mem_limit: 256m
cpu_shares: 256
networks:
- homelab
networks:
homelab:
name: homelab_net
external: true
Set up monitors for:
http://adguard:53/health(DNS)http://jellyfin:8096/(Media)http://homeassistant:8123/(Home automation)tcp://your-server:22(SSH)
Uptime Kuma sends Telegram alerts when services go down and recovers. After the initial setup, you forget it exists — until it saves you.
3. Traefik — Reverse Proxy with Auto SSL
Replaced Nginx Proxy Manager with Traefik because the config is code, version-controlled, and auto-discovers Docker labels. Let’s Encrypt certs are handled automatically.
services:
traefik:
image: traefik:v2.11
container_name: traefik
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "127.0.0.1:8080:8080" # Dashboard (local only)
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
- ./traefik.yml:/traefik.yml:ro
- ./dynamic.yml:/dynamic.yml:ro
environment:
- TZ=America/New_York
mem_limit: 128m
networks:
- homelab
networks:
homelab:
name: homelab_net
external: true
traefik.yml:
api:
dashboard: true
insecure: true
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
certificatesResolvers:
letsencrypt:
acme:
email: your-email@example.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: homelab_net
file:
filename: /dynamic.yml
On each service you want to proxy, add these labels:
services:
jellyfin:
image: jellyfin/jellyfin:latest
networks:
- homelab
labels:
- "traefik.enable=true"
- "traefik.http.routers.jellyfin.rule=Host(`media.yourdomain.com`)"
- "traefik.http.routers.jellyfin.entrypoints=websecure"
- "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt"
- "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
- "traefik.docker.network=homelab_net"
Traefik reads the Docker socket, finds containers with these labels, and automatically creates routes with SSL. Add a new service in 30 seconds.
4. Duplicati — Backup Manager
Backs up volumes to Backblaze B2, local NAS, or S3. Runs scheduled backup jobs with encryption and versioning.
services:
duplicati:
image: duplicati/duplicati:latest
container_name: duplicati
restart: unless-stopped
ports:
- "8200:8200"
volumes:
- ./data:/data
- ./backups:/backups
- /etc/localtime:/etc/localtime:ro
environment:
- TZ=America/New_York
mem_limit: 512m
networks:
- homelab
networks:
homelab:
name: homelab_net
external: true
Key backup jobs I run:
/docker/volumes/postgres-data→ Backblaze B2 (nightly)/docker/volumes/nextcloud-app→ Backblaze B2 (nightly)- Config directories (
/home/*/.config) → Local NAS (hourly)
Duplicati’s web UI lets you configure retention, compression, and encryption without touching CLI. Set it and forget it.
5. Dozzle — Real-Time Docker Logs
Lightweight log viewer for all containers. Better than docker logs when you need to debug something across multiple containers quickly.
services:
dozzle:
image: amir20/dozzle:latest
container_name: dozzle
restart: unless-stopped
ports:
- "9999:8080"
environment:
- DOZZLE_NO_ANALYTICS=true
- TZ=America/New_York
volumes:
- /var/run/docker.sock:/var/run/docker.sock
mem_limit: 128m
networks:
- homelab
networks:
homelab:
name: homelab_net
external: true
Visit http://your-server:9999 for a live stream of every container’s stdout/stderr. Searchable, filterable, with no install required.
6. Glances — System Monitoring
Cross-platform system monitor that runs in a browser. CPU, memory, disk, network, and per-container stats in one view.
services:
glances:
image: nicolargo/glances:latest
container_name: glances
restart: unless-stopped
ports:
- "61208:61208"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /etc/localtime:/etc/localtime:ro
environment:
- TZ=America/New_York
- GLANCES_OPT=-w # Enable web server
mem_limit: 256m
cpu_shares: 256
pid: host # See host processes
networks:
- homelab
networks:
homelab:
name: homelab_net
external: true
Open http://your-server:61208 for a real-time dashboard. CPU temp monitoring is included if your system has lm-sensors configured.
The Shared Network
All these services share one Docker network:
docker network create homelab_net
This lets containers resolve each other by name (adguard, jellyfin, traefik) instead of IP addresses. Add network_mode: homelab_net to any new service to include it.
Quick-Start Script
Drop this in /opt/homelab/ and run everything in one command:
#!/bin/bash
# Start all homelab services
cd /opt/homelab
for dir in watchtower uptime-kuma traefik duplicati dozzle glances; do
if [ -d "$dir" ]; then
echo "Starting $dir..."
(cd "$dir" && docker compose up -d)
fi
done
echo "All services started. Check status at:"
echo " Uptime Kuma: http://$(hostname -I | awk '{print $1}'):3001"
echo " Traefik: http://$(hostname -I | awk '{print $1}'):8080"
echo " Dozzle: http://$(hostname -I | awk '{print $1}'):9999"
echo " Glances: http://$(hostname -I | awk '{print $1}'):61208"
echo " Duplicati: http://$(hostname -I | awk '{print $1}'):8200"
Each service in its own directory with its own docker-compose.yml and data/ folder. Backups go to ./backups/. Clean, isolated, version-controllable.
What About Portainer?
I keep Portainer around for quick status checks when I’m on my phone. The agent-based setup is cleaner than exposing the Docker socket:
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
ports:
- "9000:9000"
- "9443:9443"
volumes:
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock
mem_limit: 512m
networks:
- homelab
networks:
homelab:
name: homelab_net
external: true
Set admin credentials on first login. I use it read-only — deploys always go through docker compose.