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:

W A T C H T O W E R _ W E B H O O K _ U R L = t e l e g r a m : / / y o u r - t e l e g r a m - b o t - t o k e n

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.