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

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.