Theo's Corner
dev / irl / thoughts
← Back
tech

How I structure my Docker Compose files

Theo|Jul 2026|~5 min read

Docker Compose files can get messy fast if you don't have a consistent approach. Here's the structure I've settled on after running enough stacks to know what causes problems.

A real example — the media stack

services:
  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/London
    volumes:
      - ./radarr/config:/config
      - /mnt/media/movies:/movies
      - /mnt/downloads:/downloads
    ports:
      - "7878:7878"
    restart: unless-stopped
    networks:
      - media

networks:
  media:
    driver: bridge

The things I always include

container_name — always set this explicitly. Without it Docker generates a random name and docker logs becomes annoying. With it you can always just type docker logs radarr.

restart: unless-stopped — means the container restarts automatically if it crashes or the server reboots, but stays stopped if you manually stop it. Almost always what you want.

Named networks — putting related containers on the same named network means they can talk to each other by container name, not IP address. http://radarr:7878 from another container on the same network just works.

Use named volumes or bind mounts for anything that needs to persist. Container filesystems are ephemeral — if you remove and recreate a container without volumes, that data is gone.

Environment variables

For anything sensitive — API keys, passwords, database credentials — I use a .env file alongside the Compose file and reference variables with ${VARIABLE_NAME}. The .env file goes in .gitignore. Never hardcode secrets in a Compose file that's going in version control.

One Compose file per stack

I keep related services together in one Compose file. The media stack is one file. The monitoring stack is another. The panel stack is another. This keeps things manageable — you can bring a whole stack up or down with one command, and the files stay readable.

Health checks

For services that other services depend on, I add health checks so Docker waits for them to be actually ready before starting dependent containers:

healthcheck:
  test: ["CMD", "pg_isready", "-U", "postgres"]
  interval: 10s
  timeout: 5s
  retries: 5