
Docker Impl Compose Workflows
- 14 installs
- 9 repo stars
- Updated July 8, 2026
- openaec-foundation/docker-claude-skill-package
Helps with devops & ci/cd tasks.
About
docker-impl-compose-workflows is a Claude Code skill for devops & ci/cd. It helps solo builders move faster with AI-assisted development.
- docker-impl-compose-workflows
- DevOps & CI/CD
- AI-coding skill
Docker Impl Compose Workflows by the numbers
- 14 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #957 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/docker-claude-skill-package --skill docker-impl-compose-workflowsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 14 |
|---|---|
| repo stars | ★ 9 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/docker-claude-skill-package ↗ |
What it does
Helps with devops & ci/cd tasks.
Files
docker-impl-compose-workflows
Quick Reference
Compose File Search Order
Compose searches for files in this order: 1. compose.yaml (preferred) 2. compose.yml 3. docker-compose.yaml 4. docker-compose.yml
ALWAYS use compose.yaml for new projects. The docker-compose.* names exist only for backward compatibility.
NEVER include a version: field. It is deprecated and ignored by modern Compose.
Environment Variable Precedence (Highest to Lowest)
| Priority | Source | Example |
|---|---|---|
| 1 (highest) | docker compose run -e CLI flag | docker compose run -e DEBUG=1 web |
| 2 | Shell/interpolation in environment or env_file | ${DEBUG} resolved from host shell |
| 3 | environment attribute in compose.yaml | environment: DEBUG: "true" |
| 4 | env_file attribute files | env_file: .env.local |
| 5 (lowest) | Image ENV directive | ENV DEBUG=false in Dockerfile |
Variable Interpolation Syntax
| Syntax | Behavior |
|---|---|
$VAR or ${VAR} | Direct substitution |
${VAR:-default} | Use default if VAR is unset or empty |
${VAR-default} | Use default only if VAR is unset |
${VAR:?error} | Error if VAR is unset or empty |
${VAR?error} | Error if VAR is unset |
${VAR:+replacement} | Use replacement if VAR is set and non-empty |
${VAR+replacement} | Use replacement if VAR is set |
Use $$ to produce a literal $ sign. Interpolation applies to unquoted and double-quoted values only. Single-quoted values in .env files are literal.
Merge Rules (Multiple Compose Files)
| Field Type | Behavior | Examples |
|---|---|---|
| Scalar (single-value) | Later file replaces earlier | image, command, mem_limit |
| Sequence (multi-value) | Values concatenated | ports, expose, dns, tmpfs |
| Mapping (key-value) | Merge by key; later overrides matching keys | environment, labels, volumes |
Critical Warnings
NEVER use environment variables for secrets (passwords, tokens, API keys). ALWAYS use Docker secrets or mounted secret files instead.
NEVER assume depends_on waits for service readiness. ALWAYS combine with condition: service_healthy and a healthcheck.
ALWAYS use docker compose config to verify the resolved configuration after merging multiple files or applying overrides.
NEVER use container_name on services you intend to scale. It prevents scaling because names must be unique.
---
Profiles
Assignment and Activation
Services WITHOUT a profiles attribute are ALWAYS started. Services WITH profiles start only when their profile is activated.
services:
app: # No profile = ALWAYS enabled
image: myapp
phpmyadmin:
image: phpmyadmin
profiles: [debug] # Only with debug profile
frontend:
image: node
profiles: [frontend, dev] # Multiple profilesActivation Methods
# CLI flag (repeatable)
docker compose --profile debug up
docker compose --profile frontend --profile debug up
docker compose --profile "*" up # Enable ALL profiles
# Environment variable (comma-separated)
COMPOSE_PROFILES=debug docker compose up
COMPOSE_PROFILES=frontend,debug docker compose upAuto-Activation
When you explicitly target a profiled service, Compose runs it regardless of profile activation:
docker compose run db-migrations # Runs even without --profile toolsCritical constraint: If the targeted service has profiled dependencies, those dependencies MUST either share the same profile, be started separately, or have no profile assignment.
---
Extends and Include
Extends Directive
Inherits configuration from another service without including that service in the final project.
# From another file
services:
web:
extends:
file: common-services.yml
service: webapp
ports:
- "8080:80"
# Within the same file
services:
web:
extends: webappLocally-defined attributes ALWAYS override extended values. Relative paths in extended files are automatically converted.
Include Directive
Imports entire Compose files as independent application models:
include:
- my-compose-include.yaml
- path:
- third-party/compose.yaml
- override.yaml # Paired override
- oci://docker.io/user/app:latest # Remote OCI sourceEach included file resolves paths relative to its own directory. Direct resource conflicts between included files cause an error. Works recursively.
---
Multiple Compose Files
Override Convention
By default, Compose loads compose.yaml THEN compose.override.yaml automatically. No -f flag needed.
# Explicit multi-file (processed left-to-right, later overrides earlier)
docker compose -f compose.yaml -f compose.prod.yaml up
docker compose -f compose.yaml -f compose.admin.yaml run backup_dbOverride files need NOT be complete or valid standalone Compose files. They can be fragments.
ALWAYS resolve paths relative to the first (base) file.
Remote Compose Files
# OCI Registry
docker compose -f oci://registry.example.com/project:latest up
# Git Repository
docker compose -f https://github.com/user/repo.git up
docker compose -f https://github.com/user/repo.git@v1.0.0 up
docker compose -f git@github.com:user/repo.git#main:path/to/compose.yaml up---
Compose Watch
Configuration
services:
web:
build: .
develop:
watch:
- action: sync
path: ./web
target: /src/web
initial_sync: true
ignore:
- node_modules/
- action: rebuild
path: package.json
- action: sync+restart
path: ./proxy/nginx.conf
target: /etc/nginx/conf.d/default.confAction Types
| Action | Behavior | Use Case |
|---|---|---|
sync | Syncs host files to container path | Hot reload frameworks (React, Vue) |
rebuild | Builds new image, replaces container | Dependency changes, compiled languages |
sync+restart | Syncs files then restarts container | Config file changes (nginx.conf, .ini) |
Usage
docker compose up --watch # Combined with logs
docker compose watch # Separate from logsConstraints
- Works ONLY with services that have a
buildattribute, NEVER with pre-builtimage-only services - Container image MUST contain
stat,mkdir,rmdirutilities - Container
USERMUST have write permissions to target paths - ALWAYS ignore large directories (e.g.,
node_modules/) for performance - Does NOT support glob patterns in path definitions
---
Compose CLI Workflow
Essential Commands
| Command | Purpose |
|---|---|
docker compose up -d | Create and start containers (detached) |
docker compose down | Stop and remove containers and networks |
docker compose build | Build or rebuild service images |
docker compose ps | List running containers |
docker compose logs -f | Follow container log output |
docker compose exec <svc> <cmd> | Run command in running container |
docker compose run <svc> <cmd> | Run one-off command on a service |
docker compose config | Validate and display resolved config |
docker compose --dry-run up | Preview changes without executing |
.env File Rules
- Default location:
.envnext tocompose.yaml - Override with:
docker compose --env-file ./config/.env.dev up - Multiple files:
docker compose --env-file .env --env-file .env.override up - Later files override earlier files
- Lines starting with
#are comments - Single-quoted values are literal (no interpolation)
- Double-quoted values support escape sequences:
\n,\r,\t,\\
---
Decision Trees
Which Multi-File Strategy to Use?
Need to customize a third-party Compose file?
├── YES → Use `include` with paired override file
└── NO
├── Need to share base service config across services?
│ └── YES → Use `extends` directive
└── Need environment-specific overrides (dev/prod)?
├── YES, two environments → Use compose.override.yaml (auto-loaded)
└── YES, multiple → Use explicit -f flag with per-environment filesWhich Watch Action to Use?
What changed?
├── Source code (interpreted language) → sync
├── Config file (needs process restart) → sync+restart
└── Dependencies or compiled code → rebuild---
Reference Links
- references/patterns.md -- Profile patterns, merge rules, extend patterns, watch configuration
- references/examples.md -- Dev/prod workflow, multi-file setup, profile-based optional services
- references/anti-patterns.md -- Workflow mistakes and corrections
Official Sources
- https://docs.docker.com/compose/how-tos/environment-variables/
- https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/
- https://docs.docker.com/compose/how-tos/environment-variables/envvars-precedence/
- https://docs.docker.com/compose/how-tos/profiles/
- https://docs.docker.com/compose/how-tos/multiple-compose-files/merge/
- https://docs.docker.com/compose/how-tos/multiple-compose-files/extends/
- https://docs.docker.com/compose/how-tos/multiple-compose-files/include/
- https://docs.docker.com/compose/how-tos/file-watch/
- https://docs.docker.com/reference/cli/docker/compose/
Compose Workflow Anti-Patterns
AP-1: Using compose.override.yaml for Production Settings
Problem: compose.override.yaml is auto-loaded. If production settings are placed there, they apply during development too, or worse, development overrides leak into production.
# WRONG — compose.override.yaml with production config
services:
app:
image: registry.example.com/myapp:v2.1.0
deploy:
replicas: 3Fix: ALWAYS use explicit -f flags for non-development environments. Reserve compose.override.yaml exclusively for local development overrides.
# Development (auto-loads compose.override.yaml)
docker compose up
# Production (explicitly skips compose.override.yaml)
docker compose -f compose.yaml -f compose.prod.yaml up -d---
AP-2: Duplicating Configuration Instead of Using Extends
Problem: Copy-pasting identical configuration across services leads to drift and maintenance burden.
# WRONG — duplicated config
services:
web:
image: python:3.12
environment:
PYTHONUNBUFFERED: "1"
LOG_LEVEL: info
working_dir: /app
worker:
image: python:3.12
environment:
PYTHONUNBUFFERED: "1"
LOG_LEVEL: info
working_dir: /appFix: ALWAYS use extends to share common configuration.
services:
base:
image: python:3.12
environment:
PYTHONUNBUFFERED: "1"
LOG_LEVEL: info
working_dir: /app
web:
extends: base
command: gunicorn app:app
worker:
extends: base
command: celery -A tasks worker---
AP-3: Hardcoding Environment Values in compose.yaml
Problem: Hardcoded values prevent environment-specific configuration and risk leaking secrets into version control.
# WRONG — hardcoded secret
services:
db:
environment:
POSTGRES_PASSWORD: "my-secret-password"Fix: ALWAYS use interpolation with required-variable syntax for secrets and environment-specific values.
services:
db:
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Database password is required}Commit .env.example with empty values. Add .env to .gitignore.
---
AP-4: Not Verifying Merged Configuration
Problem: Multiple Compose files can produce unexpected merge results. Deploying without verification leads to runtime failures.
# WRONG — deploy without checking
docker compose -f compose.yaml -f compose.prod.yaml -f compose.monitoring.yaml up -dFix: ALWAYS run docker compose config before deploying with multiple files.
# Verify first
docker compose -f compose.yaml -f compose.prod.yaml config
# Then deploy
docker compose -f compose.yaml -f compose.prod.yaml up -d---
AP-5: Running Debug Tools Without Profiles
Problem: Development and debugging tools running in production waste resources and create security exposure.
# WRONG — phpmyadmin always running
services:
app:
image: myapp
phpmyadmin:
image: phpmyadmin
ports:
- "8080:80"Fix: ALWAYS place optional/debug services behind profiles.
services:
app:
image: myapp
phpmyadmin:
image: phpmyadmin
profiles: [dev]
ports:
- "127.0.0.1:8080:80"---
AP-6: Using Bind Mounts Instead of Compose Watch
Problem: Bind mounts in production configs create host-dependency. Mixing development bind mounts with production configs causes confusion.
# WRONG — bind mount in base compose.yaml
services:
app:
image: myapp
volumes:
- ./src:/app/src # Only useful in developmentFix: Use compose watch for development file syncing. Keep bind mounts in compose.override.yaml only if watch is not suitable.
# compose.yaml — clean base
services:
app:
build: .
# compose.override.yaml — development only
services:
app:
develop:
watch:
- action: sync
path: ./src
target: /app/src
ignore:
- node_modules/---
AP-7: Ignoring .env File Precedence Rules
Problem: Not understanding that host shell variables override .env file values leads to "it works on my machine" issues.
# Developer A has LOG_LEVEL=debug in their shell
# Developer B does not
# Both use the same .env file with LOG_LEVEL=info
# They get different behavior — confusingFix: ALWAYS document the precedence chain. Use docker compose config --environment to verify resolved values. For critical variables, use the ${VAR:?error} syntax to fail fast on missing values.
# Verify what Compose sees
docker compose config --environment
# Force a specific value regardless of shell
docker compose run -e LOG_LEVEL=debug app---
AP-8: Not Ignoring Large Directories in Watch
Problem: Watching directories like node_modules/ causes excessive CPU usage, slow sync, and thousands of unnecessary file events.
# WRONG — no ignore list
develop:
watch:
- action: sync
path: ./frontend
target: /appFix: ALWAYS ignore dependency directories, build outputs, and test artifacts.
develop:
watch:
- action: sync
path: ./frontend
target: /app
ignore:
- node_modules/
- .next/
- dist/
- coverage/
- "*.test.js"---
AP-9: Using Watch with Image-Only Services
Problem: compose watch only works with services that have a build attribute. Using it with image-only services silently does nothing.
# WRONG — watch does nothing without build
services:
app:
image: myapp:latest
develop:
watch:
- action: sync
path: ./src
target: /app/srcFix: ALWAYS ensure watched services have a build attribute.
services:
app:
build: .
develop:
watch:
- action: sync
path: ./src
target: /app/src---
AP-10: Conflicting Resources in Include Files
Problem: Two included Compose files defining the same service name or volume name causes a hard error.
# WRONG — both files define "db" service
include:
- team-a/compose.yaml # has services.db
- team-b/compose.yaml # also has services.db
# Result: ERROR — conflicting service namesFix: Use unique service names across included files. If you need to customize an included service, use the paired override pattern.
include:
- path:
- team-a/compose.yaml
- team-a/overrides.yaml # Customizes team-a services
- path:
- team-b/compose.yaml---
AP-11: Missing Healthchecks on Profile Dependencies
Problem: A profiled service depends on a core service without a healthcheck, causing startup race conditions.
# WRONG — migration starts before db is ready
services:
db:
image: postgres:16
migrate:
profiles: [tools]
command: python manage.py migrate
depends_on:
- dbFix: ALWAYS add healthchecks to services that other services depend on, and use condition: service_healthy.
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
migrate:
profiles: [tools]
command: python manage.py migrate
depends_on:
db:
condition: service_healthy---
AP-12: Exposing Ports to All Interfaces in Development
Problem: Default port mapping "8080:80" binds to 0.0.0.0, exposing development services to the entire network.
# WRONG — exposed to all interfaces
services:
adminer:
profiles: [dev]
ports:
- "8080:8080"Fix: ALWAYS bind development service ports to 127.0.0.1.
services:
adminer:
profiles: [dev]
ports:
- "127.0.0.1:8080:8080"Compose Workflow Examples
Dev/Prod Workflow with Override Files
Base Configuration (compose.yaml)
services:
app:
image: myapp:latest
ports:
- "80:80"
environment:
NODE_ENV: production
LOG_LEVEL: warn
depends_on:
db:
condition: service_healthy
restart: unless-stopped
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
db:
image: postgres:16
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${POSTGRES_DB:?Database name required}
POSTGRES_USER: ${POSTGRES_USER:?Database user required}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Database password required}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
db-data:Development Override (compose.override.yaml — auto-loaded)
services:
app:
build:
context: .
target: development
ports:
- "127.0.0.1:3000:80"
environment:
NODE_ENV: development
LOG_LEVEL: debug
DEBUG: "true"
volumes:
- ./src:/app/src
develop:
watch:
- action: sync
path: ./src
target: /app/src
ignore:
- node_modules/
- action: rebuild
path: package.json
restart: "no"
db:
ports:
- "127.0.0.1:5432:5432"Production Override (compose.prod.yaml — explicit -f)
services:
app:
image: registry.example.com/myapp:${APP_VERSION:?Version required}
deploy:
replicas: 3
resources:
limits:
cpus: '2.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 256M
db:
volumes:
- db-data:/var/lib/postgresql/data
deploy:
resources:
limits:
cpus: '2.0'
memory: 2GUsage
# Development (compose.yaml + compose.override.yaml auto-loaded)
docker compose up --watch
# Production (compose.yaml + compose.prod.yaml, override skipped)
docker compose -f compose.yaml -f compose.prod.yaml up -d
# Verify resolved config before deploying
docker compose -f compose.yaml -f compose.prod.yaml config---
Multi-File Setup with Include
Project Structure
project/
├── compose.yaml # Main orchestration
├── compose.override.yaml # Dev overrides (gitignored)
├── compose.override.yaml.example
├── .env # Default env vars
├── .env.example # Template (committed)
├── infra/
│ └── compose.yaml # Database, cache, queue
├── monitoring/
│ └── compose.yaml # Prometheus, Grafana
└── app/
├── Dockerfile
└── src/Main Compose File (compose.yaml)
include:
- infra/compose.yaml
- monitoring/compose.yaml
services:
app:
build:
context: ./app
ports:
- "8080:8080"
environment:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
REDIS_URL: redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_startedInfrastructure File (infra/compose.yaml)
services:
db:
image: postgres:16
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${POSTGRES_DB:-myapp}
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Required}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
interval: 10s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
volumes:
- cache-data:/data
queue:
image: rabbitmq:3-management
profiles: [messaging]
ports:
- "127.0.0.1:15672:15672"
volumes:
db-data:
cache-data:Monitoring File (monitoring/compose.yaml)
services:
prometheus:
image: prom/prometheus:latest
profiles: [monitoring]
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
ports:
- "127.0.0.1:9090:9090"
grafana:
image: grafana/grafana:latest
profiles: [monitoring]
volumes:
- grafana-data:/var/lib/grafana
ports:
- "127.0.0.1:3001:3000"
depends_on:
- prometheus
volumes:
grafana-data:Usage
# Core services only
docker compose up -d
# Core + monitoring
docker compose --profile monitoring up -d
# Core + messaging + monitoring
docker compose --profile messaging --profile monitoring up -d---
Profile-Based Optional Services
Debug and Admin Tools
services:
# Core — always running
web:
build: .
ports:
- "8080:8080"
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- db-data:/var/lib/postgresql/data
# Dev tools
adminer:
image: adminer
profiles: [dev]
ports:
- "127.0.0.1:8081:8080"
mailpit:
image: axllent/mailpit
profiles: [dev]
ports:
- "127.0.0.1:8025:8025"
- "127.0.0.1:1025:1025"
# Testing
test-runner:
build:
context: .
target: test
profiles: [test]
command: pytest
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://postgres:postgres@db:5432/test
# Seed/migration tools
seed:
build: .
profiles: [tools]
command: python manage.py seed
depends_on:
db:
condition: service_healthy
migrate:
build: .
profiles: [tools]
command: python manage.py migrate
depends_on:
db:
condition: service_healthy
volumes:
db-data:Usage Patterns
# Daily development
docker compose --profile dev up -d
# Run tests (auto-starts db dependency)
docker compose run test-runner
# Run migration (auto-starts db dependency)
docker compose run migrate
# Seed database
docker compose run seed
# CI pipeline
COMPOSE_PROFILES=test docker compose up --abort-on-container-exit---
Environment Variable Workflow
.env File (committed as .env.example, actual .env gitignored)
# .env.example — committed to repo
POSTGRES_DB=myapp
POSTGRES_USER=postgres
POSTGRES_PASSWORD=
APP_VERSION=latest
NODE_ENV=development
# .env — local overrides (gitignored)
POSTGRES_DB=myapp
POSTGRES_USER=postgres
POSTGRES_PASSWORD=supersecret
APP_VERSION=2.1.0
NODE_ENV=developmentCompose with Required Variables
services:
app:
image: myapp:${APP_VERSION:?APP_VERSION is required}
environment:
# Required — fails if missing
DATABASE_URL: postgres://${POSTGRES_USER:?}:${POSTGRES_PASSWORD:?}@db:5432/${POSTGRES_DB:?}
# Optional with default
LOG_LEVEL: ${LOG_LEVEL:-info}
# Optional — only set if present
SENTRY_DSN: ${SENTRY_DSN+${SENTRY_DSN}}Multiple .env Files for Environments
# Load base + environment-specific
docker compose --env-file .env --env-file .env.staging up -d
# Verify interpolation result
docker compose --env-file .env --env-file .env.staging config --environment---
Compose Watch Full Example
Node.js + Python + Nginx Stack
services:
frontend:
build:
context: ./frontend
target: development
develop:
watch:
- action: sync
path: ./frontend/src
target: /app/src
initial_sync: true
ignore:
- node_modules/
- "*.test.tsx"
- __tests__/
- action: rebuild
path: ./frontend/package.json
api:
build:
context: ./api
develop:
watch:
- action: sync
path: ./api/app
target: /app/app
ignore:
- __pycache__/
- "*.pyc"
- action: sync+restart
path: ./api/gunicorn.conf.py
target: /app/gunicorn.conf.py
- action: rebuild
path: ./api/requirements.txt
nginx:
build:
context: ./nginx
ports:
- "127.0.0.1:8080:80"
develop:
watch:
- action: sync+restart
path: ./nginx/nginx.conf
target: /etc/nginx/nginx.conf
depends_on:
- frontend
- apiStarting Watch Mode
# Start with watch (logs + watch combined)
docker compose up --watch
# Or start detached, then watch separately
docker compose up -d
docker compose watch---
Remote Compose Files
OCI Registry
# Pull and run from OCI registry
docker compose -f oci://registry.example.com/myapp:latest up -d
# Specific version
docker compose -f oci://registry.example.com/myapp:v2.1.0 up -dGit Repository
# Default branch
docker compose -f https://github.com/myorg/infra.git up -d
# Specific branch
docker compose -f https://github.com/myorg/infra.git@staging up -d
# Specific tag
docker compose -f https://github.com/myorg/infra.git@v1.0.0 up -d
# Subdirectory in repo
docker compose -f git@github.com:myorg/infra.git#main:docker/compose.yaml up -d
# Combine remote base with local override
docker compose -f https://github.com/myorg/base.git@v1.0.0 -f compose.local.yaml up -dCompose Workflow Patterns
Profile Patterns
Categorized Profile Assignment
Organize profiles by purpose. Services without profiles ALWAYS start.
services:
# Core services — no profile, ALWAYS running
app:
image: myapp:latest
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# Development tools — only when needed
phpmyadmin:
image: phpmyadmin
profiles: [dev]
ports:
- "127.0.0.1:8081:80"
mailhog:
image: mailhog/mailhog
profiles: [dev]
ports:
- "127.0.0.1:8025:8025"
# Debug/monitoring — heavy tools behind profile
prometheus:
image: prom/prometheus
profiles: [monitoring]
grafana:
image: grafana/grafana
profiles: [monitoring]
# One-off tasks — run explicitly
db-migrations:
image: myapp:latest
profiles: [tools]
command: python manage.py migrate
depends_on:
db:
condition: service_healthyProfile Activation Patterns
# Development with debug tools
docker compose --profile dev up -d
# Production monitoring
docker compose --profile monitoring up -d
# Run one-off migration (auto-activates service + dependencies)
docker compose run db-migrations
# Enable everything
docker compose --profile "*" up -d
# Via environment variable (useful in CI)
COMPOSE_PROFILES=dev,monitoring docker compose up -d---
Merge and Override Patterns
Merge Rules Reference
Scalar Fields (Replacement)
Later file completely replaces earlier value:
# compose.yaml
services:
web:
image: myapp:latest
command: python app.py
# compose.prod.yaml
services:
web:
image: myapp:v2.1.0
command: gunicorn app:app
# Result: image=myapp:v2.1.0, command=gunicorn app:appSequence Fields (Concatenation)
Values from all files are concatenated:
# compose.yaml
services:
web:
expose:
- "3000"
dns:
- 8.8.8.8
# compose.override.yaml
services:
web:
expose:
- "4000"
- "5000"
dns:
- 9.9.9.9
# Result: expose=["3000","4000","5000"], dns=["8.8.8.8","9.9.9.9"]Sequence fields: ports, expose, external_links, dns, dns_search, tmpfs
Mapping Fields (Smart Merge)
Merge by key; later files override matching keys while preserving unmatched:
# compose.yaml
services:
web:
environment:
FOO: original
BAR: original
volumes:
- ./src:/app/src
- data:/app/data
# compose.override.yaml
services:
web:
environment:
BAR: overridden
BAZ: new
volumes:
- ./src:/app/src:cached
# Result environment: FOO=original, BAR=overridden, BAZ=new
# Result volumes: ./src:/app/src:cached (overridden by mount path), data:/app/data (preserved)Mapping fields: environment, labels, volumes, devices
compose.override.yaml Convention
Compose ALWAYS loads compose.override.yaml automatically alongside compose.yaml. No flag needed:
# compose.yaml — base configuration
services:
web:
image: myapp:latest
ports:
- "80:80"
# compose.override.yaml — development overrides (auto-loaded)
services:
web:
build: .
ports:
- "127.0.0.1:8080:80"
volumes:
- ./src:/app/src
environment:
DEBUG: "true"ALWAYS add compose.override.yaml to .gitignore if it contains developer-specific settings. Provide compose.override.yaml.example as a template.
---
Extends Patterns
Base Service Inheritance
# common-services.yml
services:
base-python:
image: python:3.12-slim
environment:
PYTHONUNBUFFERED: "1"
PYTHONDONTWRITEBYTECODE: "1"
working_dir: /app
volumes:
- ./requirements.txt:/app/requirements.txt
# compose.yaml
services:
web:
extends:
file: common-services.yml
service: base-python
command: gunicorn app:app
ports:
- "8000:8000"
worker:
extends:
file: common-services.yml
service: base-python
command: celery -A tasks workerSame-File Extends
services:
base:
image: node:20-slim
working_dir: /app
environment:
NODE_ENV: production
frontend:
extends: base
command: npm run start:frontend
ports:
- "3000:3000"
backend:
extends: base
command: npm run start:backend
ports:
- "4000:4000"Multi-Level Extension
# base.yml
services:
base:
image: python:3.12
environment:
LOG_LEVEL: info
# web-base.yml
services:
web-base:
extends:
file: base.yml
service: base
command: gunicorn app:app
# compose.yaml
services:
web:
extends:
file: web-base.yml
service: web-base
ports:
- "8000:8000"---
Include Patterns
Importing Sub-Projects
# compose.yaml
include:
- infra/compose.yaml # Database, cache, queue
- monitoring/compose.yaml # Prometheus, Grafana
services:
app:
build: .
depends_on:
db:
condition: service_healthyEach included file resolves paths relative to its own directory.
Include with Override
include:
- path:
- third-party/compose.yaml
- third-party/compose.override.yamlRemote Include
include:
- oci://docker.io/myorg/infra-compose:latest
- https://github.com/myorg/shared-compose.git@v2.0.0---
Watch Configuration Patterns
Full-Stack Watch Setup
services:
frontend:
build:
context: ./frontend
develop:
watch:
- action: sync
path: ./frontend/src
target: /app/src
initial_sync: true
ignore:
- node_modules/
- "*.test.js"
- action: rebuild
path: ./frontend/package.json
backend:
build:
context: ./backend
develop:
watch:
- action: sync
path: ./backend/app
target: /app/app
ignore:
- __pycache__/
- "*.pyc"
- action: rebuild
path: ./backend/requirements.txt
- action: sync+restart
path: ./backend/config.ini
target: /app/config.ini
nginx:
build:
context: ./nginx
develop:
watch:
- action: sync+restart
path: ./nginx/nginx.conf
target: /etc/nginx/nginx.confDockerfile Permissions for Watch
The container USER MUST have write access to sync targets. Use COPY --chown in the Dockerfile:
FROM node:20-slim
WORKDIR /app
RUN addgroup --system app && adduser --system --ingroup app app
COPY --chown=app:app package*.json ./
RUN npm ci
COPY --chown=app:app . .
USER app
CMD ["npm", "start"]Path Mapping Behavior
For a source file change at ./app/html/index.html with path: ./app:
| Target | Result in Container |
|---|---|
/app/html | /app/html/index.html |
/app/static | /app/static/index.html |
/assets | /assets/index.html |
Ignore Patterns
Patterns are relative to the path of the current watch action, NOT the project directory.
Default ignored (no configuration needed):
.dockerignorerules- Temporary/backup files from common IDEs (Vim, Emacs, JetBrains)
.gitdirectories
ALWAYS ignore dependency directories and build artifacts for performance:
ignore:
- node_modules/
- __pycache__/
- "*.pyc"
- .next/
- dist/
- build/