Docker Compose Stacks
How I structure and manage Docker Compose stacks across the homelab — separate stack directories, shared networks, named volumes, and the habits that make running 20+ containers manageable.
Status
live
Difficulty
beginner
Time Required
15m
Last Tested
2026-06-01
How I Organise Stacks
Each service or related group of services lives in its own directory with a compose.yml and a .env. No single monolithic Compose file that controls everything — small, focused stacks that start and stop independently.
~/stacks/
├── traefik/
│ ├── compose.yml
│ ├── traefik.yml
│ └── .env
├── monitoring/
│ ├── compose.yml
│ └── .env
├── n8n/
│ ├── compose.yml
│ └── .env
├── ollama/
│ ├── compose.yml
│ └── .env
The benefit: if I need to tear down n8n to upgrade it, nothing else is affected. Each stack is isolated.
Shared Networks
Services that need to communicate (or route through Traefik) join a shared external network:
docker network create proxy
In each compose.yml:
networks:
proxy:
external: true
services:
myapp:
networks:
- proxy
I use proxy for anything that goes through Traefik. Services that only need to talk to each other (like an app and its database) get their own internal network within the stack.
Volumes
Named volumes for anything I care about:
volumes:
app-data:
services:
myapp:
volumes:
- app-data:/app/data
For bulk storage (media, backups) I bind-mount to specific paths on the host:
volumes:
- /mnt/tank/media:/media
.env Pattern
Every stack has a .env. The Compose file references variables with ${VAR_NAME}. This keeps credentials out of the Compose file and makes it easy to see what needs to be configured when setting up on a new machine.
POSTGRES_PASSWORD=changeme
DOMAIN=yourdomain.com
TZ=Europe/Dublin
Useful Commands
# Start a stack in the background
docker compose up -d
# View real-time logs for a service
docker compose logs -f servicename
# Restart a single service
docker compose restart servicename
# Pull latest images and recreate
docker compose pull && docker compose up -d
# Remove unused containers, networks, images
docker system prune -f
Always-On Services
restart: unless-stopped on every service that should survive a server reboot. Without this, containers have to be started manually after a restart — which I always forget.
services:
myapp:
restart: unless-stopped
Notes
The main thing that makes this work is keeping stacks small and independent. The temptation is to put everything in one giant Compose file — resist it. Isolated stacks mean a broken upgrade on one service doesn't cascade into an afternoon of debugging unrelated things.