Docker Compose Recipes: 5 Production-Ready Stacks You Can Deploy Today
Five complete, tested Docker Compose stacks for essential services, media servers, monitoring, reverse proxy, and local AI. Copy, customize, deploy.
Docker Compose Recipes: 5 Production-Ready Stacks You Can Deploy Today
Stop copy-pasting random Docker commands from blog posts. Here are five complete, tested Docker Compose stacks that you can deploy in minutes — each one battle-tested and production-ready.
1. The Essential Services Stack
Everything a homelab needs in one docker-compose.yml:
services:
# Ad-blocking DNS
pihole:
image: pihole/pihole:latest
container_name: pihole
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
- "80:80"
environment:
- WEBPASSWORD=changeme
- DNS1=1.1.1.1
- DNS2=8.8.8.8
volumes:
- ./pihole/etc-pihole:/etc/pihole
- ./pihole/etc-dnsmasq.d:/etc/dnsmasq.d
networks:
- homelab
# Password Manager
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
ports:
- "8080:80"
volumes:
- ./vaultwarden:/data
networks:
- homelab
# Monitoring
uptime-kuma:
image: louislam/uptime-kuma:latest
container_name: uptime-kuma
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- ./uptime-kuma:/app/data
networks:
- homelab
# Git Server
gitea:
image: gitea/gitea:latest
container_name: gitea
restart: unless-stopped
ports:
- "3000:3000"
- "2222:22"
volumes:
- ./gitea:/data
networks:
- homelab
networks:
homelab:
driver: bridge
Deploy: docker compose up -d
What you get: Network-wide ad blocking, a password manager, uptime monitoring, and a private Git server. Total setup time: 5 minutes.
2. The Media Server Stack
Plex/Jellyfin with the full *arr suite for automated media management:
services:
jellyfin:
image: lscr.io/linuxserver/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
ports:
- "8096:8096"
volumes:
- ./jellyfin/config:/config
- /media/tv:/data/tvshows
- /media/movies:/data/movies
networks:
- media
sonarr:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr
restart: unless-stopped
ports:
- "8989:8989"
volumes:
- ./sonarr:/config
- /media/tv:/tv
- /downloads:/downloads
networks:
- media
radarr:
image: lscr.io/linuxserver/radarr:latest
container_name: radarr
restart: unless-stopped
ports:
- "7878:7878"
volumes:
- ./radarr:/config
- /media/movies:/movies
- /downloads:/downloads
networks:
- media
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
container_name: prowlarr
restart: unless-stopped
ports:
- "9696:9696"
volumes:
- ./prowlarr:/config
networks:
- media
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- ./qbittorrent:/config
- /downloads:/downloads
networks:
- media
networks:
media:
driver: bridge
The workflow: Prowlarr manages indexers → Sonarr/Radarr monitor for new releases → qBittorrent downloads → Jellyfin serves. Fully automated.
3. The Monitoring Stack
Prometheus + Grafana + Node Exporter for full infrastructure observability:
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- ./prometheus/data:/prometheus
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.retention.time=30d"
networks:
- monitoring
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./grafana:/var/lib/grafana
networks:
- monitoring
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"
networks:
- monitoring
networks:
monitoring:
driver: bridge
Prometheus config (prometheus/prometheus.yml):
global:
scrape_interval: 15s
scrape_configs:
- job_name: "node"
static_configs:
- targets: ["node-exporter:9100"]
First steps: Open Grafana at :3000 (admin/admin), add Prometheus as data source, import dashboard #1860 (Node Exporter Full).
4. The Reverse Proxy Stack
Traefik with automatic SSL via Cloudflare DNS challenge:
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik:/etc/traefik
- ./acme:/acme
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.http.address=:80"
- "--entrypoints.https.address=:443"
- "--certificatesresolvers.cloudflare.acme.dnschallenge=true"
- "--certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.cloudflare.acme.email=you@example.com"
- "--certificatesresolvers.cloudflare.acme.storage=/acme/acme.json"
environment:
- CF_API_EMAIL=you@example.com
- CF_API_KEY=your-cloudflare-api-key
networks:
- proxy
networks:
proxy:
external: true
To expose any service, add these labels to its container:
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`myapp.example.com`)"
- "traefik.http.routers.myapp.entrypoints=https"
- "traefik.http.routers.myapp.tls=true"
- "traefik.http.routers.myapp.tls.certresolver=cloudflare"
5. The Local AI Stack
Ollama + Open WebUI for a ChatGPT-like experience running locally:
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
restart: unless-stopped
ports:
- "11434:11434"
volumes:
- ./ollama:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
networks:
- ai
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
ports:
- "3000:8080"
volumes:
- ./open-webui:/app/backend/data
environment:
- OLLAMA_BASE_URL=http://ollama:11434
networks:
- ai
networks:
ai:
driver: bridge
After deploying: Run docker exec ollama ollama pull qwen3:14b to download your first model, then open :3000 for the web UI.
Pro Tips for All Stacks
Use .env Files
Never hardcode secrets in your compose files:
# .env
PIHOLE_PASSWORD=my-secure-password
CF_API_KEY=your-cloudflare-key
GRAFANA_ADMIN_PASSWORD=admin-password
Then reference in compose: WEBPASSWORD=${PIHOLE_PASSWORD}
Backup Script
#!/bin/bash
# Quick backup of all Docker volumes
DATE=$(date +%Y%m%d)
tar czf ~/backups/homelab-$DATE.tar.gz \
./pihole ./vaultwarden ./gitea ./grafana ./prometheus/data
Updates Made Easy
# Update all containers at once
docker compose pull && docker compose up -d
# Clean up old images
docker image prune -f
Health Checks
Add health checks to critical services:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
Which Stack Should You Start With?
If you're new to self-hosting: Stack #1 (Essential Services). It covers the highest-value replacements and nothing in it is critical if it goes down.
If you have media to serve: Stack #2 (Media). It's the most immediately rewarding — automated media library is magical.
If you're running multiple servers: Stack #3 (Monitoring). You need to know what's happening on your infrastructure.
If you want public access: Stack #4 (Reverse Proxy). This unlocks everything else.
If you have a GPU: Stack #5 (Local AI). Because running your own AI is just cool.
The Combo Build
The real power move: combine stacks 1, 3, and 4 into a single compose file with shared networks. Traefik handles SSL and routing, Prometheus watches everything, and your essential services are accessible from anywhere with proper HTTPS.
That's a production-grade homelab for the cost of electricity.
Got a favorite stack I didn't cover? Share it in the comments — I'm always looking for new Docker Compose recipes to try.