Files
docker-compose-config-skill/SKILL.md

9.5 KiB

name, description
name description
docker-compose-config Docker Compose configuration management for multi-host server deployments. Use when creating, modifying, or managing docker-compose.yml files with environment-specific configurations, external volume mounts, and reverse proxy networks. Triggers on tasks involving Docker Compose files, environment overrides, multi-host deployments, or service configuration for self-hosted applications.

Docker Compose Configuration Management

This skill provides guidance for managing Docker Compose configurations across multiple server environments with per-host overrides.

Directory Structure

Each project follows this structure:

/docker/config/
├── <project>/
│   ├── docker-compose.yml              # Main service definitions
│   ├── docker-compose.override.yml     # Current host overrides (gitignored)
│   ├── .env                            # Environment variables (gitignored)
│   ├── .gitignore                      # Excludes /.env and /docker-compose.override.yml
│   ├── env.example                     # Template for .env files
│   ├── README.md                       # Setup and usage instructions
│   └── environments/
│       └── <hostname>/                 # Per-host configs (committed, secrets scrubbed)
│           ├── .env
│           └── docker-compose.override.<hostname>.yml

Compose File Conventions

Service Definition Pattern

name: <project-name>

services:
  <service-name>:
    container_name: <service_container>
    image: <registry>/<image>:<version>
    environment:
      PUID: ${UID}
      PGID: ${GID}
      # Service-specific vars use env substitution
      VAR_NAME: ${VAR_NAME}
    volumes:
      - ${DATA_PATH}:/app/data
      - /etc/localtime:/etc/localtime:ro
    ports:
      - ${SERVICE_PORT:-default}:internal_port
    restart: unless-stopped
    networks:
      - default
      - reverse_proxy

networks:
  reverse_proxy:
    name: reverse_proxy
    attachable: true

Key Patterns

  1. Image versioning: Always use pinned version tags (never latest)

    image: ghcr.io/org/image:v1.0.0
    image: lscr.io/linuxserver/app:2.5.1
    

    Note: SHA256 digests are managed automatically by Renovate bot and should not be added manually.

  2. Port defaults: Always provide defaults for ports

    ports:
      - ${APP_PORT:-8080}:8080
    
  3. Volume mounts: Use environment variables for paths

    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - ${DB_DATA_LOCATION}/data:/var/lib/postgresql/data
    
  4. Proxy network: Services needing reverse proxy access join reverse_proxy network

    networks:
      - default
      - reverse_proxy
    
    networks:
      reverse_proxy:
        name: reverse_proxy
        attachable: true
    

Environment File Conventions

Structure (.env)

# Permissions
UID=1000
GID=1000

# Paths
UPLOAD_LOCATION=/mnt/user/pictures
DB_DATA_LOCATION=/mnt/user/appdata/<project>

# Ports
APP_PORT=8080

# Secrets
DB_PASSWORD=<generated>

# Database
DB_HOSTNAME=database
DB_USERNAME=postgres
DB_DATABASE_NAME=<project>

Per-Host Variations

  • environments/<hostname>/.env overrides paths for specific hosts
  • Common overrides: mount paths (/mnt/user/ vs /mnt/main/), ports

Secret Management with SOPS

Projects use SOPS with age encryption to securely commit secrets to git. This replaces the manual "secret scrubbing" approach.

Directory Structure (with SOPS)

/docker/config/<project>/
├── docker-compose.yml
├── docker-compose.override.yml     # (gitignored)
├── .env                            # (gitignored, generated by decrypt)
├── .gitignore
├── .sops.yaml                      # SOPS encryption config
├── Makefile                        # encrypt/decrypt targets
├── env.sops.yaml                   # Encrypted env vars (committed)
└── environments/
    └── <hostname>/
        ├── env.sops.yaml           # Encrypted, host-specific (committed)
        └── .env                    # (gitignored, generated)

Key Repository

Public keys are managed centrally at ~/repos/sops-age-keys/:

  • recipients/*.pub - Public keys for each host
  • scripts/init-host.sh - Generate key for new host
  • scripts/add-recipient.sh - Add host's public key
  • templates/Makefile - Makefile template for projects

Private keys stay at ~/.config/sops/age/keys.txt (never committed).

.sops.yaml Configuration

creation_rules:
  - path_regex: env\.sops\.yaml$
    encrypted_regex: "^(DB_PASSWORD|API_KEY|SECRET_.*|.*_SECRET|.*_PASSWORD|.*_TOKEN)$"
    age: >-
      age1abc...,age1def...
  • encrypted_regex specifies which keys to encrypt (only secrets)
  • Get recipient keys from ~/repos/sops-age-keys/recipients/*.pub
  • Comments and non-matching keys remain unencrypted

env.sops.yaml Format

Use normal variable names. Only keys matching encrypted_regex are encrypted:

# Permissions
UID: "1000"
GID: "1000"

# Paths
UPLOAD_LOCATION: /mnt/user/pictures
DB_DATA_LOCATION: /mnt/user/appdata/immich

# Ports
APP_PORT: "2283"

# Database
DB_HOSTNAME: database
DB_USERNAME: postgres
DB_DATABASE_NAME: immich
DB_PASSWORD: secret_value_here

After encryption, only DB_PASSWORD is encrypted; comments and other values remain readable.

Makefile Targets

SOURCE ?= env.sops.yaml
TARGET ?= .env

decrypt:
	@sops decrypt --output-type dotenv --output $(TARGET) $(SOURCE)
	@echo "Decrypted $(SOURCE) -> $(TARGET)"

encrypt:
	@sops encrypt --input-type dotenv --output $(SOURCE) $(TARGET)
	@echo "Encrypted $(TARGET) -> $(SOURCE)"

clean:
	@rm -f $(TARGET)
	@echo "Removed $(TARGET)"

Common SOPS Commands

# Decrypt to .env (for Docker Compose)
make decrypt SOURCE=environments/<host>/env.sops.yaml TARGET=environments/<host>/.env

# Edit secrets directly (decrypts in editor, re-encrypts on save)
sops environments/<host>/env.sops.yaml

# Encrypt after editing .env
make encrypt SOURCE=environments/<host>/env.sops.yaml TARGET=environments/<host>/.env

# Add new host to recipients (after adding to .sops.yaml)
sops updatekeys environments/<host>/env.sops.yaml

# Clean up decrypted file
make clean TARGET=environments/<host>/.env

Starting Services with SOPS

cd /docker/config/<project>

# Decrypt secrets
make decrypt SOURCE=environments/<host>/env.sops.yaml TARGET=environments/<host>/.env

# Start services
docker compose --env-file environments/<host>/.env \
  -f docker-compose.yml \
  -f environments/<host>/docker-compose.override.<host>.yml up -d

# Clean up (optional, secrets stay decrypted for restarts)
make clean TARGET=environments/<host>/.env

Migrating Existing Projects

See ~/repos/sops-age-keys/README.md for full migration guide, or ~/repos/sops-age-keys/docs/design.md for architecture details.

Common Commands

# Start a project
docker compose -f <project>/docker-compose.yml up -d

# Start with host-specific override
docker compose -f <project>/docker-compose.yml \
  -f <project>/environments/<host>/docker-compose.override.<host>.yml up -d

# View logs
docker compose -f <project>/docker-compose.yml logs -f

# Stop services
docker compose -f <project>/docker-compose.yml down

Creating a New Project

When creating a new Docker Compose project:

  1. Create the project directory structure
  2. Create docker-compose.yml with service definitions
  3. Create env.example as a template
  4. Create environment config only for the current host (environments/<current-hostname>/.env)
  5. Optionally create a README.md with first-run instructions

Important: Only create the environment for the current host unless explicitly asked to create configurations for other hosts.

Adding a New Host Environment

  1. Create environments/<hostname>/ directory
  2. Copy existing host's .env as template
  3. Update paths and port mappings for the host
  4. Create override compose file if device mappings differ

Git Repository Management

Each project should be version controlled with its own git repository.

.gitignore Configuration

# Root environment file (generated, may contain secrets)
/.env

# Docker compose override (host-specific, not committed)
/docker-compose.override.yml

# Per-host decrypted .env files (generated from env.sops.yaml)
/environments/*/.env

Note: All .env files are gitignored. Secrets are stored encrypted in env.sops.yaml files using SOPS (see "Secret Management with SOPS" section above).

Creating a Git Repository

# Initialize
cd /docker/config/<project>
git init && git branch -m main

# Create remote on Gitea (using git-gitea skill)
source ~/.claude/skills/git-gitea/scripts/gitea-helper.sh
gitea_create_repo "docker-<project>" "Docker Compose configuration for <project>" true

# Add remote, commit, push
git remote add origin https://git.prettyhefty.com/Bill/docker-<project>.git
git add -A
git commit -m "Initial commit: <project> docker-compose configuration"
git push -u origin main

Service Dependencies

Use depends_on with health checks for proper startup order:

depends_on:
  database:
    condition: service_healthy

Healthchecks

healthcheck:
  test: ["CMD", "curl", "-f", "http://127.0.0.1:8080/"]
  start_period: 5m
  interval: 30s
  timeout: 20s
  retries: 10

References