Docker Compose Templates for Your Home Server

Published: March 2026 | Reading Time: 16 minutes

Docker has revolutionized home server management. Instead of installing software directly on your NAS, you can run everything in containers—keeping your system clean, making updates easier, and allowing you to run multiple services without conflicts.

In this guide, I'll provide ready-to-use Docker Compose templates for the most popular self-hosted services, along with best practices for managing your containerized home server.

Why Use Docker on Your Home Server?

Benefits:

Getting Started:

Get a NAS Ready for Docker → (affiliate)


Essential Prerequisites

1. Create Directory Structure

mkdir -p ~/docker/{data,config}
cd ~/docker

2. Install Portainer (Recommended)

version: '3'
services:
  portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    ports:
      - "9000:9000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./portainer-data:/data
    restart: unless-stopped

Access Portainer at http://your-server-ip:9000

3. Set Up Reverse Proxy

Use Nginx Proxy Manager for automatic SSL:

version: '3'
services:
  npm:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: nginx-proxy-manager
    ports:
      - '80:80'
      - '443:443'
      - '81:81'
    volumes:
      - ./npm-data:/data
      - ./npm-letsencrypt:/etc/letsencrypt
    restart: unless-stopped

Complete Docker Compose Templates

Media Server Stack

Plex Media Server

version: '3'
services:
  plex:
    image: plexinc/pms-docker:latest
    container_name: plex
    network_mode: host  # Required for DLNA and Bonjour
    volumes:
      - ./plex-config:/config
      - ./plex-transcode:/transcode
      - /path/to/media:/media
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/New_York
      - PLEX_CLAIM=your-claim-token
    restart: unless-stopped

Jellyfin (Free Alternative)

version: '3'
services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    ports:
      - "8096:8096"
    volumes:
      - ./jellyfin-config:/config
      - ./jellyfin-cache:/cache
      - /path/to/media:/media
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/New_York
    restart: unless-stopped

Tautulli (Plex Monitoring)

version: '3'
services:
  tautulli:
    image: tautulli/tautulli:latest
    container_name: tautulli
    ports:
      - "8181:8181"
    volumes:
      - ./tautulli-config:/config
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/New_York
    restart: unless-stopped

Productivity Stack

Nextcloud with MariaDB

version: '3'
services:
  nextcloud-db:
    image: mariadb:latest
    container_name: nextcloud-db
    volumes:
      - ./nextcloud-db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=root-password
      - MYSQL_USER=nextcloud
      - MYSQL_PASSWORD=secure-password
      - MYSQL_DATABASE=nextcloud
    restart: unless-stopped

  nextcloud:
    image: nextcloud:latest
    container_name: nextcloud
    ports:
      - "8080:80"
    volumes:
      - ./nextcloud-html:/var/www/html
    environment:
      - MYSQL_HOST=nextcloud-db
      - MYSQL_USER=nextcloud
      - MYSQL_PASSWORD=secure-password
      - MYSQL_DATABASE=nextcloud
      - REDIS_HOST=nextcloud-redis
    depends_on:
      - nextcloud-db
      - nextcloud-redis
    restart: unless-stopped

  nextcloud-redis:
    image: redis:alpine
    container_name: nextcloud-redis
    restart: unless-stopped

Paperless-ngx (Document Management)

version: '3'
services:
  broker:
    image: redis:7-alpine
    container_name: paperless-redis
    restart: unless-stopped

  db:
    image: postgres:15-alpine
    container_name: paperless-db
    volumes:
      - ./paperless-db:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=paperless
      - POSTGRES_USER=paperless
      - POSTGRES_PASSWORD=paperless
    restart: unless-stopped

  webserver:
    image: ghcr.io/paperless-ngx/paperless-ngx:latest
    container_name: paperless
    ports:
      - "8000:8000"
    volumes:
      - ./paperless-data:/usr/src/paperless/data
      - ./paperless-media:/usr/src/paperless/media
      - ./paperless-export:/usr/src/paperless/export
      - ./paperless-consume:/usr/src/paperless/consume
    environment:
      - PAPERLESS_REDIS=redis://broker:6379
      - PAPERLESS_DBHOST=db
      - PAPERLESS_DBNAME=paperless
      - PAPERLESS_DBUSER=paperless
      - PAPERLESS_DBPASS=paperless
      - PAPERLESS_TIMEZONE=America/New_York
    depends_on:
      - db
      - broker
    restart: unless-stopped

Vaultwarden (Bitwarden Server)

version: '3'
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    ports:
      - "80:80"
    volumes:
      - ./vaultwarden-data:/data
    environment:
      - WEBSOCKET_ENABLED=true
      - ADMIN_TOKEN=your-admin-token
    restart: unless-stopped

Monitoring Stack

Grafana + Prometheus + Node Exporter

version: '3'
services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus-config:/etc/prometheus
      - ./prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    volumes:
      - ./grafana-data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin-password
    depends_on:
      - prometheus
    restart: unless-stopped

  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    ports:
      - "9100:9100"
    command:
      - '--path.rootfs=/host'
    volumes:
      - '/:/host:ro,rslave'
    restart: unless-stopped

Watchtower (Auto-Update Containers)

version: '3'
services:
  watchtower:
    image: containrrr/watchtower:latest
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WATCHTOWER_SCHEDULE=0 0 3 * * *  # Daily at 3 AM
      - WATCHTOWER_CLEANUP=true
    restart: unless-stopped

Development Stack

Gitea (Git Hosting)

version: '3'
services:
  gitea-db:
    image: postgres:15-alpine
    container_name: gitea-db
    volumes:
      - ./gitea-db:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=gitea
      - POSTGRES_DB=gitea
    restart: unless-stopped

  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    ports:
      - "3000:3000"
      - "222:22"
    volumes:
      - ./gitea-data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - DB_TYPE=postgres
      - DB_HOST=gitea-db:5432
      - DB_NAME=gitea
      - DB_USER=gitea
      - DB_PASSWD=gitea
    depends_on:
      - gitea-db
    restart: unless-stopped

VS Code Server

version: '3'
services:
  vscode:
    image: lscr.io/linuxserver/code-server:latest
    container_name: vscode
    ports:
      - "8443:8443"
    volumes:
      - ./vscode-config:/config
      - /path/to/projects:/config/workspace
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/New_York
      - PASSWORD=vscode-password
      - SUDO_PASSWORD=sudo-password
    restart: unless-stopped

Smart Home Stack

Home Assistant with Supervisor

version: '3'
services:
  homeassistant:
    image: homeassistant/home-assistant:latest
    container_name: homeassistant
    ports:
      - "8123:8123"
    volumes:
      - ./homeassistant-config:/config
    environment:
      - TZ=America/New_York
    restart: unless-stopped

Mosquitto MQTT Broker

version: '3'
services:
  mosquitto:
    image: eclipse-mosquitto:latest
    container_name: mosquitto
    ports:
      - "1883:1883"
      - "9001:9001"
    volumes:
      - ./mosquitto-config:/mosquitto/config
      - ./mosquitto-data:/mosquitto/data
      - ./mosquitto-logs:/mosquitto/log
    restart: unless-stopped

Advanced Docker Tips

1. Resource Limits

Control CPU and memory usage per container:

services:
  plex:
    # ... other config ...
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 4G
        reservations:
          cpus: '1'
          memory: 2G

2. Automatic Backups

Use Docker volumes backup script:

#!/bin/bash
# docker-backup.sh

BACKUP_DIR="/path/to/backups"
DOCKER_DIR="/path/to/docker"
DATE=$(date +%Y%m%d)

for container in $(docker ps --format '{{.Names}}'); do
    echo "Backing up $container..."
    docker run --rm \
        -v $DOCKER_DIR:/source \
        -v $BACKUP_DIR:/backup \
        alpine tar czf /backup/$container-$DATE.tar.gz /source/$container
done

3. Health Checks

Add health monitoring:

services:
  nextcloud:
    # ... other config ...
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

4. Log Rotation

Prevent logs from filling your disk:

services:
  plex:
    # ... other config ...
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

5. Network Isolation

Create separate networks:

version: '3'
services:
  # Create a bridge network for your stack
networks:
  default:
    name: homelab

Docker Compose Best Practices

1. Use Environment Variables
Store sensitive data in .env file:

MYSQL_PASSWORD=secure-password
PLEX_CLAIM=your-token

Reference in docker-compose.yml:

environment:
  - MYSQL_PASSWORD=${MYSQL_PASSWORD}

2. Version Control Your Configs

git init
echo "*.db" >> .gitignore
echo "*/transcode/*" >> .gitignore
git add .
git commit -m "Initial Docker setup"

3. Use Named Volumes
For persistent data across container updates:

volumes:
  plex-config:
    driver: local

4. Set Proper Permissions

environment:
  - PUID=1000  # Your user ID
  - PGID=1000  # Your group ID

5. Monitor Container Health

# Check all container status
docker ps -a

# View container logs
docker logs -f container-name

# Check resource usage
docker stats

Security Considerations

1. Run as Non-Root User

user: "1000:1000"

2. Read-Only Filesystem

read_only: true

3. No Privileged Mode
Avoid using privileged: true unless necessary.

4. Limit Capabilities

cap_drop:
  - ALL
cap_add:
  - NET_BIND_SERVICE

5. Use Secrets (Swarm)
For sensitive configuration in production.

Performance Optimization

1. Use Host Networking (when appropriate)

network_mode: host

2. Enable Volumes Caching

volumes:
  - ./cache:/cache:cached

3. Use tmpfs for Temp Files

tmpfs:
  - /tmp

4. Optimize Docker Images

Troubleshooting Common Issues

Problem: Container won't start

# Check logs
docker logs container-name

# Check if ports are already in use
sudo netstat -tulpn | grep PORT

Problem: High memory usage

# Check memory usage
docker stats

# Add memory limits to docker-compose.yml

Problem: Permission denied errors

# Check container user
docker exec container-name whoami

# Fix permissions
sudo chown -R 1000:1000 /path/to/volumes

Problem: Container can't access network

# Check network configuration
docker network ls
docker network inspect network-name

# Ensure correct ports are mapped

Complete Example: All-in-One Home Server

Here's a complete docker-compose.yml for a typical home server:

version: '3'

services:
  # Media
  plex:
    image: plexinc/pms-docker:latest
    network_mode: host
    volumes:
      - ./plex-config:/config
      - ./plex-transcode:/transcode
      - /media:/media
    environment:
      - TZ=America/New_York
      - PLEX_CLAIM=your-token
    restart: unless-stopped

  # Productivity
  nextcloud:
    image: nextcloud:latest
    ports:
      - "8080:80"
    volumes:
      - ./nextcloud-data:/var/www/html
    depends_on:
      - nextcloud-db
    restart: unless-stopped

  nextcloud-db:
    image: mariadb:latest
    volumes:
      - ./nextcloud-db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=root-pass
      - MYSQL_PASSWORD=nextcloud-pass
      - MYSQL_DATABASE=nextcloud
    restart: unless-stopped

  # Monitoring
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - ./grafana-data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin-pass
    restart: unless-stopped

  # Passwords
  vaultwarden:
    image: vaultwarden/server:latest
    ports:
      - "80:80"
    volumes:
      - ./vaultwarden-data:/data
    environment:
      - WEBSOCKET_ENABLED=true
    restart: unless-stopped

  # Automation
  homeassistant:
    image: homeassistant/home-assistant:latest
    ports:
      - "8123:8123"
    volumes:
      - ./homeassistant-config:/config
    environment:
      - TZ=America/New_York
    restart: unless-stopped

Learning Resources


Disclosure: This post contains affiliate links. If you purchase through these links, I may earn a commission at no extra cost to you. This helps support the blog and allows me to continue creating content.