Reading time: ~18 minutes Audience: Homelab and self-hosting enthusiasts
What Is Docker Compose?
Overview
Docker Compose is a tool for defining and running multi-container Docker applications. With a single YAML file, you configure services, networks, and volumes. One command (docker compose up -d) brings the entire stack online. For homelab operators, Compose is the standard deployment method because it is declarative, version-controlled, and reproducible across machines.
Key Concepts
- Services: Individual containers (e.g.,
nginx,db,app) - Networks: Isolated communication channels between services
- Volumes: Persistent storage for container data
- Environment Variables: Secrets and configuration passed at runtime
- Profiles: Conditional service groups (e.g.,
dev,prod)
Prerequisites
Hardware Requirements
- Any Linux server with Docker and Docker Compose installed
- At least 2 GB RAM for small stacks, 8 GB+ for full monitoring suites
Software Requirements
- Docker Engine 20.10+
- Docker Compose plugin (v2.x)
- A text editor and basic YAML syntax knowledge
Knowledge Prerequisites
- Understanding of Docker images, containers, and ports
- Familiarity with reverse proxies and TLS
- Basic Linux file permissions
Step 1: Basic Web Application Stack
Objective
Deploy a simple web application with a database and reverse proxy.
docker-compose.yml
version: "3.8"
services:
db:
image: mariadb:10.6
container_name: app-db
restart: always
environment:
- MYSQL_ROOT_PASSWORD=*** - MYSQL_DATABASE=app
- MYSQL_USER=app
- MYSQL_PASSWORD=*** volumes:
- db_data:/var/lib/mysql
app:
image: nginx:alpine
container_name: app-web
restart: always
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
depends_on:
- db
volumes:
db_data:
Deploy
docker compose up -d
docker compose logs -f
Step 2: Traefik Reverse Proxy with Automatic HTTPS
Objective
Deploy Traefik as a central reverse proxy with Let’s Encrypt and Docker service discovery.
docker-compose.yml
version: "3.8"
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: always
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "[email protected]"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.com`)"
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8C/"
whoami:
image: traefik/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.yourdomain.com`)"
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
Step 3: Monitoring Stack (Prometheus + Grafana)
Objective
Deploy a complete monitoring stack with metrics collection, storage, and visualization.
docker-compose.yml
version: "3.8"
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: always
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: always
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=***
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: always
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:
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']
Step 4: Nextcloud with PostgreSQL and Redis
Objective
Deploy a production-ready Nextcloud stack with a high-performance database and memory caching.
docker-compose.yml
version: "3.8"
services:
app:
image: nextcloud:apache
container_name: nextcloud
restart: always
ports:
- "8080:80"
volumes:
- nextcloud:/var/www/html
environment:
- POSTGRES_HOST=db
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud
- POSTGRES_PASSWORD=*** - NEXTCLOUD_ADMIN_USER=admin
- NEXTCLOUD_ADMIN_PASSWORD=*** - NEXTCLOUD_TRUSTED_DOMAINS=cloud.yourdomain.com
- REDIS_HOST=redis
depends_on:
- db
- redis
db:
image: postgres:15-alpine
container_name: nextcloud-db
restart: always
volumes:
- db:/var/lib/postgresql/data
environment:
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud
- POSTGRES_PASSWORD=***
redis:
image: redis:alpine
container_name: nextcloud-redis
restart: always
cron:
image: nextcloud:apache
restart: always
volumes:
- nextcloud:/var/www/html:ro
entrypoint: /cron.sh
depends_on:
- db
- redis
volumes:
nextcloud:
db:
Step 5: Pi-hole with Unbound
Objective
Deploy a network-wide ad blocker with a recursive DNS resolver for maximum privacy.
docker-compose.yml
version: "3.8"
services:
pihole:
image: pihole/pihole:latest
container_name: pihole
restart: always
ports:
- "53:53/tcp"
- "53:53/udp"
- "80:80/tcp"
environment:
- TZ=Asia/Singapore
- WEBPASSWORD=*** - FTLCONF_LOCAL_IPV4=192.168.1.10
volumes:
- ./etc-pihole:/etc/pihole
- ./etc-dnsmasq.d:/etc/dnsmasq.d
cap_add:
- NET_ADMIN
dns:
- 127.0.0.1
- 1.1.1.1
unbound:
image: mvance/unbound:latest
container_name: unbound
restart: always
ports:
- "5335:53/tcp"
- "5335:53/udp"
In Pi-hole, set Custom DNS to 127.0.0.1#5335.
Pro Tips
Tip 1: Use .env Files for Secrets
Never commit secrets to your Compose file. Use a .env file:
DB_PASSWORD=supersecret
DOMAIN=yourdomain.com
And reference variables in Compose:
environment:
- MYSQL_PASSWORD=${DB_PASSWORD}
Add .env to .gitignore.
Tip 2: Health Checks and Restart Policies
Add health checks to ensure services recover automatically:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Tip 3: Resource Limits
Prevent a runaway container from consuming all RAM:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
Tip 4: Named Volumes vs Bind Mounts
- Named volumes: Managed by Docker, portable, best for databases
- Bind mounts: Direct host filesystem mapping, best for config files and media
Troubleshooting Common Issues
Problem 1: Port Already in Use
# Find the process using the port
sudo ss -tlnp | grep :80
# Kill or reconfigure the conflicting service
Problem 2: Permission Denied on Bind Mounts
# Fix ownership
sudo chown -R 1000:1000 ./data
# Or use Docker user namespaces
Problem 3: Container Exits Immediately
# Check logs
docker compose logs <service_name>
# Inspect the container
docker inspect <container_name>
Conclusion
Summary
Docker Compose is the backbone of modern homelab infrastructure. It turns complex multi-service applications into declarative, reproducible configurations. The examples in this guide cover the most common patterns: reverse proxying, monitoring, cloud storage, and DNS filtering. With these templates, you can deploy a full homelab stack in minutes.
Next Steps
- Version-control your Compose files in Git
- Add health checks and resource limits to all services
- Configure automated backups for named volumes
- Experiment with Docker Swarm for multi-node orchestration
Affiliate Opportunities
- prerequisites: hosting — VPS for remote Compose stacks
- step-1: tool — Docker Hub subscriptions, JetBrains IDEs
- pro-tips: service — Cloudflare, S3-compatible storage
Internal Linking Strategy
what-is→ related_comparison: Docker Compose vs Podmanprerequisites→ setup_guide: Docker Compose for beginnersconclusion→ next_steps: Portainer Docker management guide
CTA
- [comment] Which Compose stack is your favorite? Share your custom configurations.
- [newsletter] Get weekly Docker and homelab deployment guides.
- [internal_link] Next: learn Docker Compose for beginners