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:
- ✅ Isolation: Each service runs independently
- ✅ Easy Updates: Update containers without reinstalling
- ✅ Portability: Move services between servers easily
- ✅ Resource Management: Limit CPU and RAM per container
- ✅ Clean Installs: No leftover files or registry entries
- ✅ Version Control: Track configuration changes with Git
Getting Started:
- Most NAS support Docker (Synology, QNAP, Asustor)
- Need at least 8GB RAM for multiple containers
- SSD cache significantly improves performance
- Use Portainer for visual management
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
- Use specific version tags instead of
latest - Use alpine images when possible
- Multi-stage builds for custom 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
- Official Docker Documentation
- Docker Compose Reference
- Awesome Self-Hosted - List of self-hosted software
- Home Server Reddit - Community support
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.