Docker Compose Patterns

Reusable patterns for structuring Docker Compose applications in homelab and development environments

created: Sat Mar 14 2026 00:00:00 GMT+0000 (Coordinated Universal Time) updated: Sat Mar 14 2026 00:00:00 GMT+0000 (Coordinated Universal Time) #containers#docker#compose

Introduction

Docker Compose defines multi-container applications in a single declarative file. It is a good fit for homelab stacks, local development, and small self-hosted services that do not require a full orchestrator.

Purpose

Compose helps when you need:

  • Repeatable service definitions
  • Shared networks and volumes for a stack
  • Environment-specific overrides
  • A clear deployment artifact that can live in Git

Architecture Overview

A Compose application usually includes:

  • One or more services
  • One or more shared networks
  • Persistent volumes
  • Environment variables and mounted configuration
  • Optional health checks and startup dependencies

Step-by-Step Guide

1. Start with a minimal Compose file

services:
  app:
    image: ghcr.io/example/app:1.2.3
    ports:
      - "8080:8080"

Start it:

docker compose up -d
docker compose ps

2. Add persistent storage and configuration

services:
  app:
    image: ghcr.io/example/app:1.2.3
    ports:
      - "8080:8080"
    environment:
      APP_BASE_URL: "https://app.example.com"
    volumes:
      - app-data:/var/lib/app
 
volumes:
  app-data:

3. Add dependencies with health checks

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - db-data:/var/lib/postgresql/data
 
  app:
    image: ghcr.io/example/app:1.2.3
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app
    ports:
      - "8080:8080"
 
volumes:
  db-data:

Common Patterns

Use one project directory per stack

Keep the Compose file, .env example, and mounted config together in one directory.

Use user-defined networks

Private internal services should communicate over Compose networks rather than the host network.

Prefer explicit volumes

Named volumes are easier to back up and document than anonymous ones.

Use profiles for optional services

Profiles are useful for dev-only services, one-shot migration jobs, or optional observability components.

Troubleshooting Tips

Services start in the wrong order

  • Use health checks instead of only container start order
  • Ensure the application retries database or dependency connections

Configuration drift between hosts

  • Commit the Compose file to Git
  • Keep secrets out of the file and inject them separately
  • Avoid host-specific bind mount paths when portability matters

Containers cannot resolve each other

  • Check that the services share the same Compose network
  • Use the service name as the hostname
  • Verify the application is not hard-coded to localhost

Best Practices

  • Omit the deprecated top-level version field in new Compose files
  • Keep secrets outside the Compose YAML when possible
  • Pin images to intentional versions
  • Use health checks for stateful dependencies
  • Treat Compose as deployment code and review changes like application code

References