a golden docker logo on a black background

Essential Docker Compose Stacks for Your Home Lab

Containers 2026-02-09 · 6 min read docker docker-compose containers self-hosted homelab
By HomeLab Starter Editorial TeamHome lab enthusiasts covering hardware setup, networking, and self-hosted services for home and small office environments.

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.

Photo by Rubaitul Azad on Unsplash

Docker logo

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: [email protected]
      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:

Want more containers guides? Get guides like this in your inbox — HomeLab Starter delivers one free deep-dive every week.

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: "[email protected]"
      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.

Get free weekly tips in your inbox. Subscribe to HomeLab Starter

More containers guides

One focused tutorial every week — no spam, unsubscribe anytime.

Opens Substack to confirm — no spam, unsubscribe anytime.

Before you go...

Get a free weekly guide from HomeLab Starter — one focused topic, delivered every week. No spam.

Opens Substack to confirm — no spam, unsubscribe anytime.