d4rk notes
Infrastructure

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.

dami2026-06-02dockerdocker-composehomelabcontainers

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.