Essential Docker Compose Stacks for Your Home Lab
Docker Compose turns the chaos of docker run commands into clean, version-controlled YAML files. Instead of remembering a dozen flags and volume mounts, you define everything in a docker-compose.yml and bring it up with one command. For a home lab, this is the difference between a fragile mess and a reproducible setup.
This guide provides ready-to-use Docker Compose stacks for the services that most home labs benefit from. Each example is tested, production-ready, and includes the environment variables you'll actually need to change.
Before You Start
Create a directory structure to keep things organized:
mkdir -p ~/docker/{traefik,portainer,homepage,pihole,uptime-kuma,grafana,nextcloud,jellyfin,vaultwarden,paperless,immich}
Each service gets its own directory with its own docker-compose.yml. This keeps things modular — you can start, stop, and update services independently.
Make sure Docker and Docker Compose are installed:
# Install Docker (if not already installed)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
# Docker Compose v2 is included with Docker Engine
docker compose version
1. Traefik — Reverse Proxy
Traefik automatically discovers your Docker containers and routes traffic to them by hostname. It handles SSL certificates via Let's Encrypt, so every service gets HTTPS automatically.
# ~/docker/traefik/docker-compose.yml
services:
traefik:
image: traefik:v3.2
container_name: traefik
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./config/traefik.yml:/etc/traefik/traefik.yml
- ./config/acme.json:/acme.json
networks:
- proxy
networks:
proxy:
name: proxy
external: true
# ~/docker/traefik/config/traefik.yml
api:
dashboard: true
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
websecure:
address: ":443"
providers:
docker:
exposedByDefault: false
certificatesResolvers:
letsencrypt:
acme:
email: your-email@example.com
storage: /acme.json
httpChallenge:
entryPoint: web
# Create the shared network and empty acme.json
docker network create proxy
touch ~/docker/traefik/config/acme.json
chmod 600 ~/docker/traefik/config/acme.json
Once Traefik is running, other containers just need labels to be reachable by hostname:
labels:
- "traefik.enable=true"
- "traefik.http.routers.myservice.rule=Host(`myservice.lab.example.com`)"
- "traefik.http.routers.myservice.tls.certresolver=letsencrypt"
2. Portainer — Container Management UI
A web-based dashboard for managing Docker containers, images, volumes, and networks. Useful when you want to quickly check logs or restart a container without SSH-ing into the server.
# ~/docker/portainer/docker-compose.yml
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
ports:
- "9000:9000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
volumes:
portainer_data:
3. Homepage — Dashboard
A fast, customizable dashboard that shows all your services in one place. Supports widgets for dozens of services with live status and statistics.
# ~/docker/homepage/docker-compose.yml
services:
homepage:
image: ghcr.io/gethomepage/homepage:latest
container_name: homepage
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./config:/app/config
- /var/run/docker.sock:/var/run/docker.sock:ro
Configure services in ~/docker/homepage/config/services.yaml. Homepage auto-discovers Docker containers and displays their status.
4. Pi-hole — Network Ad Blocking
Blocks ads and trackers at the DNS level for your entire network. Set it as your network's DNS server, and every device benefits without needing browser extensions.
# ~/docker/pihole/docker-compose.yml
services:
pihole:
image: pihole/pihole:latest
container_name: pihole
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
- "8080:80"
environment:
TZ: "America/New_York"
WEBPASSWORD: "change-this-password"
FTLCONF_dns_listeningMode: "all"
volumes:
- ./etc-pihole:/etc/pihole
- ./etc-dnsmasq.d:/etc/dnsmasq.d
After starting Pi-hole, point your router's DNS settings to your server's IP address. Every device on the network will use Pi-hole automatically.
5. Uptime Kuma — Monitoring
A beautiful, self-hosted monitoring tool. Monitors HTTP endpoints, TCP ports, DNS, and more. Sends alerts via email, Slack, Discord, Telegram, or dozens of other services.
# ~/docker/uptime-kuma/docker-compose.yml
services:
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- ./data:/app/data
6. Grafana + Prometheus — Metrics and Dashboards
The standard monitoring stack. Prometheus scrapes metrics from your servers and services. Grafana visualizes them with beautiful dashboards. Add node_exporter to each machine you want to monitor.
# ~/docker/grafana/docker-compose.yml
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.retention.time=30d"
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
ports:
- "3002:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: "change-this-password"
volumes:
- grafana_data:/var/lib/grafana
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- "--path.procfs=/host/proc"
- "--path.sysfs=/host/sys"
- "--path.rootfs=/rootfs"
volumes:
prometheus_data:
grafana_data:
# ~/docker/grafana/prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
- job_name: "node"
static_configs:
- targets: ["node-exporter:9100"]
Import Grafana dashboard ID 1860 (Node Exporter Full) for a comprehensive server monitoring dashboard out of the box.
7. Nextcloud — File Sync and Collaboration
Self-hosted alternative to Google Drive/Dropbox. File sync, calendar, contacts, collaborative editing, and hundreds of apps.
# ~/docker/nextcloud/docker-compose.yml
services:
nextcloud:
image: nextcloud:latest
container_name: nextcloud
restart: unless-stopped
ports:
- "8081:80"
environment:
MYSQL_HOST: nextcloud-db
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: change-this-db-password
NEXTCLOUD_ADMIN_USER: admin
NEXTCLOUD_ADMIN_PASSWORD: change-this-admin-password
NEXTCLOUD_TRUSTED_DOMAINS: "nextcloud.lab.example.com"
volumes:
- nextcloud_data:/var/www/html
depends_on:
- nextcloud-db
nextcloud-db:
image: mariadb:11
container_name: nextcloud-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: change-this-root-password
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: change-this-db-password
volumes:
- nextcloud_db:/var/lib/mysql
volumes:
nextcloud_data:
nextcloud_db:
8. Jellyfin — Media Server
Stream your movies, TV shows, and music to any device. No subscriptions, no account required, no tracking. Supports hardware transcoding with Intel Quick Sync.
# ~/docker/jellyfin/docker-compose.yml
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
ports:
- "8096:8096"
environment:
JELLYFIN_PublishedServerUrl: "http://192.168.1.100"
volumes:
- ./config:/config
- ./cache:/cache
- /path/to/media/movies:/data/movies
- /path/to/media/shows:/data/shows
- /path/to/media/music:/data/music
devices:
- /dev/dri:/dev/dri # Intel Quick Sync hardware transcoding
9. Vaultwarden — Password Manager
A lightweight, self-hosted Bitwarden-compatible password manager. Uses the official Bitwarden clients and browser extensions, but the server runs on minimal resources.
# ~/docker/vaultwarden/docker-compose.yml
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
ports:
- "8082:80"
environment:
DOMAIN: "https://vault.lab.example.com"
SIGNUPS_ALLOWED: "false"
ADMIN_TOKEN: "generate-a-long-random-token-here"
SMTP_HOST: "smtp.example.com"
SMTP_FROM: "vault@example.com"
SMTP_PORT: 587
SMTP_SECURITY: "starttls"
SMTP_USERNAME: "your-smtp-user"
SMTP_PASSWORD: "your-smtp-password"
volumes:
- ./data:/data
Set SIGNUPS_ALLOWED to true initially to create your account, then change it to false and restart the container. Access the admin panel at /admin using your ADMIN_TOKEN.
10. Paperless-ngx — Document Management
Scans, OCRs, and organizes your documents. Consume PDFs from a watched folder (or email), and Paperless automatically classifies, tags, and makes them searchable.
# ~/docker/paperless/docker-compose.yml
services:
paperless:
image: ghcr.io/paperless-ngx/paperless-ngx:latest
container_name: paperless
restart: unless-stopped
ports:
- "8083:8000"
environment:
PAPERLESS_REDIS: redis://paperless-redis:6379
PAPERLESS_DBHOST: paperless-db
PAPERLESS_ADMIN_USER: admin
PAPERLESS_ADMIN_PASSWORD: change-this-password
PAPERLESS_OCR_LANGUAGE: eng
PAPERLESS_TIME_ZONE: America/New_York
volumes:
- ./data:/usr/src/paperless/data
- ./media:/usr/src/paperless/media
- ./consume:/usr/src/paperless/consume
depends_on:
- paperless-db
- paperless-redis
paperless-db:
image: postgres:16
container_name: paperless-db
restart: unless-stopped
environment:
POSTGRES_DB: paperless
POSTGRES_USER: paperless
POSTGRES_PASSWORD: change-this-db-password
volumes:
- paperless_pgdata:/var/lib/postgresql/data
paperless-redis:
image: redis:7
container_name: paperless-redis
restart: unless-stopped
volumes:
paperless_pgdata:
Drop PDFs into the consume folder, and Paperless will automatically import, OCR, and organize them.
11. Immich — Photo Management
Self-hosted alternative to Google Photos. Automatic backup from mobile devices, facial recognition, map view, timeline browsing, and AI-powered search.
# ~/docker/immich/docker-compose.yml
services:
immich:
image: ghcr.io/immich-app/immich-server:release
container_name: immich
restart: unless-stopped
ports:
- "2283:2283"
environment:
DB_HOSTNAME: immich-db
DB_USERNAME: immich
DB_PASSWORD: change-this-db-password
DB_DATABASE_NAME: immich
REDIS_HOSTNAME: immich-redis
volumes:
- ./upload:/usr/src/app/upload
depends_on:
- immich-db
- immich-redis
immich-machine-learning:
image: ghcr.io/immich-app/immich-machine-learning:release
container_name: immich-ml
restart: unless-stopped
volumes:
- immich_ml_cache:/cache
immich-redis:
image: redis:7
container_name: immich-redis
restart: unless-stopped
immich-db:
image: tensorchord/pgvecto-rs:pg16-v0.2.0
container_name: immich-db
restart: unless-stopped
environment:
POSTGRES_DB: immich
POSTGRES_USER: immich
POSTGRES_PASSWORD: change-this-db-password
volumes:
- immich_pgdata:/var/lib/postgresql/data
volumes:
immich_ml_cache:
immich_pgdata:
Tips for Managing Your Stacks
Start and Stop Services
# Start a service
cd ~/docker/jellyfin && docker compose up -d
# Stop a service
cd ~/docker/jellyfin && docker compose down
# View logs
docker compose logs -f --tail 50
# Update a service to the latest image
docker compose pull && docker compose up -d
Back Up Your Data
The most important thing to back up is the volumes and bind-mounted directories. The container images themselves can always be re-pulled.
# Simple backup script
#!/bin/bash
BACKUP_DIR="/path/to/backups/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"
for service in traefik portainer pihole uptime-kuma grafana vaultwarden paperless; do
cd ~/docker/$service
docker compose stop
tar czf "$BACKUP_DIR/$service.tar.gz" .
docker compose up -d
done
Keep Things Updated
# Update all services at once
for dir in ~/docker/*/; do
echo "Updating $(basename $dir)..."
cd "$dir" && docker compose pull && docker compose up -d
done
# Clean up old images
docker image prune -af
These eleven services cover the core needs of most home labs: traffic routing, monitoring, file management, media, security, and document organization. Start with a few that match your immediate needs, and add more as you grow into them. The beauty of Docker Compose is that each service is independent — you can add, remove, or rebuild any service without affecting the others.