Docker Compose files often contain repetitive configuration blocks across multiple services. YAML anchors and Docker Compose extension fields provide powerful mechanisms for eliminating this duplication, making your Compose files more maintainable, consistent, and easier to modify. Understanding these reuse patterns transforms lengthy, repetitive configurations into concise, well-organized definitions.
Understanding YAML Anchors
YAML anchors are a native YAML feature that allows you to mark a node and reference it elsewhere in the document. An anchor is defined with & and referenced with *:
version: '3.8'
# Define anchor
x-common-logging: &default-logging
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
services:
web:
image: nginx:alpine
logging: *default-logging
api:
image: node:18-alpine
logging: *default-logging
Both services use the same logging configuration without duplication. The x- prefix on x-common-logging marks it as an extension field that Docker Compose ignores as a service.
Basic Anchor Syntax
The basic anchor workflow has three steps:
- Define an anchor with &anchor-name
- Reference it with *anchor-name
- Merge or override values as needed
version: '3.8'
x-base-service: &base-service
restart: unless-stopped
logging:
driver: json-file
options:
max-size: 10m
services:
service-one:
<<: *base-service
image: app:v1
service-two:
<<: *base-service
image: app:v2
The <<: operator merges the anchor's content into the service definition.
Extension Fields Convention
Extension fields (starting with x-) serve as template definitions that don't create services or other resources:
version: '3.8'
x-healthcheck-defaults: &healthcheck
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
services:
api:
image: myapp/api:latest
healthcheck:
<<: *healthcheck
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
The extension field defines common healthcheck parameters, while each service adds its specific test command.
Merging Multiple Anchors
Merge multiple anchors into a single service:
version: '3.8'
x-logging: &logging
logging:
driver: json-file
options:
max-size: 10m
x-restart: &restart
restart: unless-stopped
services:
web:
<<: [*logging, *restart]
image: nginx:alpine
Multiple anchors are merged using array notation [*anchor1, *anchor2].
Override Anchor Values
Values defined in the service override values from anchors:
version: '3.8'
x-defaults: &defaults
restart: unless-stopped
logging:
driver: json-file
options:
max-size: 10m
services:
critical-service:
<<: *defaults
image: myapp:latest
restart: always # Overrides the anchor value
The restart: always in the service overrides the restart: unless-stopped from the anchor.
Nested Anchor Structures
Create complex nested configurations with anchors:
version: '3.8'
x-database-defaults: &db-defaults
image: postgres:14
environment: &db-environment
POSTGRES_DB: myapp
POSTGRES_USER: appuser
restart: unless-stopped
services:
main-database:
<<: *db-defaults
environment:
<<: *db-environment
POSTGRES_PASSWORD: mainpass
test-database:
<<: *db-defaults
environment:
<<: *db-environment
POSTGRES_PASSWORD: testpass
Both &db-defaults and &db-environment can be referenced independently, allowing fine-grained reuse.
Common Environment Variables
Share environment configurations across services:
version: '3.8'
x-common-env: &common-env
LOG_LEVEL: info
NODE_ENV: production
TZ: UTC
services:
api:
image: myapp/api:latest
environment:
<<: *common-env
SERVICE_NAME: api
PORT: 3000
worker:
image: myapp/worker:latest
environment:
<<: *common-env
SERVICE_NAME: worker
CONCURRENCY: 4
Both services share common environment variables while adding service-specific ones.
Build Configuration Reuse
Reuse build configurations across similar services:
version: '3.8'
x-node-build: &node-build
context: .
dockerfile: Dockerfile.node
args:
NODE_VERSION: 18
services:
api-service:
build:
<<: *node-build
target: api
image: myapp/api:latest
worker-service:
build:
<<: *node-build
target: worker
image: myapp/worker:latest
Different services use the same base build configuration with different targets.
Port Configuration Templates
Define standard port mapping patterns:
version: '3.8'
x-http-ports: &http-ports
- "80:80"
- "443:443"
x-metrics-port: &metrics-port
- "9090:9090"
services:
web:
image: nginx:alpine
ports:
- *http-ports
- *metrics-port
This creates a service with three port mappings from two anchor references.
Label Reuse Patterns
Share common labels across services:
version: '3.8'
x-common-labels: &labels
com.example.team: platform
com.example.project: myapp
com.example.environment: production
services:
frontend:
image: myapp/frontend:latest
labels:
<<: *labels
com.example.component: frontend
backend:
image: myapp/backend:latest
labels:
<<: *labels
com.example.component: backend
Each service inherits common labels and adds component-specific ones.
Complex Service Templates
Create comprehensive service templates:
version: '3.8'
x-microservice-defaults: µservice
restart: unless-stopped
logging:
driver: json-file
options:
max-size: 10m
max-file: 3
environment:
LOG_LEVEL: info
ENVIRONMENT: production
services:
user-service:
<<: *microservice
image: myapp/user-service:latest
environment:
<<: *microservice.environment
SERVICE_NAME: user-service
PORT: 3001
order-service:
<<: *microservice
image: myapp/order-service:latest
environment:
<<: *microservice.environment
SERVICE_NAME: order-service
PORT: 3002
The template provides consistent base configuration for all microservices.
Anchor Inheritance and Composition
Build layered anchor structures:
version: '3.8'
x-base: &base
restart: unless-stopped
logging:
driver: json-file
x-web-base: &web-base
<<: *base
expose:
- "8080"
x-api-base: &api-base
<<: *base
environment:
API_MODE: enabled
services:
web:
<<: *web-base
image: nginx:alpine
api:
<<: *api-base
image: node:18-alpine
web-base and api-base both inherit from base, creating a hierarchy of configuration templates.
Array Merging Behavior
Understand how arrays merge with anchors:
version: '3.8'
x-base-ports: &base-ports
- "8080:80"
services:
web:
image: nginx:alpine
ports:
- *base-ports
- "8443:443"
Arrays from anchors and service definitions concatenate, not replace. This service has both ports.
Anchor Scope and Visibility
Anchors are document-scoped and can be referenced anywhere:
version: '3.8'
x-defaults: &defaults
restart: unless-stopped
services:
service1:
<<: *defaults
image: app:v1
service2:
<<: *defaults
image: app:v2
# Can also reference in networks, volumes, etc.
networks:
custom:
labels:
<<: *defaults
Anchors defined at any level can be referenced at any other level in the document.
Environment File References in Anchors
Combine anchors with environment file patterns:
version: '3.8'
x-app-env: &app-env
env_file:
- ./common.env
environment:
APP_MODE: service
services:
api:
<<: *app-env
image: myapp/api:latest
environment:
<<: *app-env.environment
SERVICE_TYPE: api
worker:
<<: *app-env
image: myapp/worker:latest
environment:
<<: *app-env.environment
SERVICE_TYPE: worker
Services share the same environment file reference and base environment variables.
Command and Entrypoint Templates
Reuse command patterns:
version: '3.8'
x-python-command: &python-cmd
command: ["python", "-u"]
services:
script-runner:
<<: *python-cmd
image: python:3.11
command:
- *python-cmd.command
- "script.py"
This pattern is less common because commands are typically service-specific.
Dependency Patterns
Share common dependencies:
version: '3.8'
x-db-dependency: &needs-db
depends_on:
- database
services:
api:
<<: *needs-db
image: myapp/api:latest
worker:
<<: *needs-db
image: myapp/worker:latest
database:
image: postgres:14
Multiple services declare the same dependency through an anchor.
Anchor Naming Conventions
Follow consistent naming patterns for anchors:
version: '3.8'
# Template definitions
x-template-service: &template-service
restart: unless-stopped
# Common configurations
x-common-logging: &common-logging
logging:
driver: json-file
# Base definitions
x-base-api: &base-api
environment:
API_VERSION: v1
# Default values
x-default-timeouts: &default-timeouts
timeout: 30s
interval: 10s
Descriptive prefixes help organize and identify anchor purposes.
Deep Merging with Multiple Levels
Handle complex nested merging:
version: '3.8'
x-base-config: &base-config
environment:
BASE_VAR: value
logging:
driver: json-file
options:
max-size: 10m
x-extended-config: &extended-config
<<: *base-config
environment:
<<: *base-config.environment
EXTENDED_VAR: value
services:
app:
<<: *extended-config
image: myapp:latest
environment:
<<: *extended-config.environment
APP_VAR: value
Each level merges the previous level's environment, building up the configuration.
Anchor References in Lists
Use anchors within list items:
version: '3.8'
x-production-env: &prod-env
ENVIRONMENT: production
LOG_LEVEL: warn
services:
app:
image: myapp:latest
environment:
- NODE_ENV=production
- *prod-env
However, this syntax is limited. For better merging, use the merge key:
services:
app:
image: myapp:latest
environment:
<<: *prod-env
NODE_ENV: production
Conditional-Like Patterns with Anchors
Simulate conditional configuration:
version: '3.8'
x-dev-settings: &dev-settings
environment:
DEBUG: "true"
LOG_LEVEL: debug
x-prod-settings: &prod-settings
environment:
DEBUG: "false"
LOG_LEVEL: warn
services:
app:
image: myapp:latest
# Choose one based on deployment
<<: *prod-settings
While not truly conditional, you can manually switch which anchor to use.
Practical Microservices Template
Complete example for microservices architecture:
version: '3.8'
x-service-defaults: &service-defaults
restart: unless-stopped
logging:
driver: json-file
options:
max-size: 10m
max-file: 3
environment: &common-env
LOG_FORMAT: json
LOG_LEVEL: info
TRACE_ENABLED: "false"
x-api-defaults: &api-defaults
<<: *service-defaults
expose:
- "3000"
environment:
<<: *common-env
PORT: 3000
services:
user-api:
<<: *api-defaults
image: myapp/user-api:latest
environment:
<<: *api-defaults.environment
SERVICE_NAME: user-api
order-api:
<<: *api-defaults
image: myapp/order-api:latest
environment:
<<: *api-defaults.environment
SERVICE_NAME: order-api
payment-api:
<<: *api-defaults
image: myapp/payment-api:latest
environment:
<<: *api-defaults.environment
SERVICE_NAME: payment-api
All APIs share common configuration with service-specific customization.
Database Configuration Reuse
Standardize database service configurations:
version: '3.8'
x-postgres-defaults: &postgres
image: postgres:14
restart: unless-stopped
environment: &postgres-env
POSTGRES_DB: myapp
POSTGRES_USER: appuser
services:
primary-db:
<<: *postgres
environment:
<<: *postgres-env
POSTGRES_PASSWORD: primary-pass
ROLE: primary
replica-db:
<<: *postgres
environment:
<<: *postgres-env
POSTGRES_PASSWORD: replica-pass
ROLE: replica
Both databases share base configuration with different passwords and roles.
Testing Service Templates
Create test service templates:
version: '3.8'
x-test-service: &test-service
restart: "no"
logging:
driver: none
environment:
ENVIRONMENT: test
services:
unit-tests:
<<: *test-service
image: myapp:latest
command: npm run test:unit
integration-tests:
<<: *test-service
image: myapp:latest
command: npm run test:integration
e2e-tests:
<<: *test-service
image: myapp:latest
command: npm run test:e2e
Test services share common test environment settings.
Worker Service Patterns
Template worker services:
version: '3.8'
x-worker-defaults: &worker-defaults
restart: unless-stopped
environment: &worker-env
WORKER_MODE: "true"
QUEUE_CONNECTION: redis://redis:6379
services:
email-worker:
<<: *worker-defaults
image: myapp/worker:latest
environment:
<<: *worker-env
WORKER_TYPE: email
CONCURRENCY: 5
notification-worker:
<<: *worker-defaults
image: myapp/worker:latest
environment:
<<: *worker-env
WORKER_TYPE: notification
CONCURRENCY: 10
Workers share base configuration with type-specific settings.
Anchor Limitations and Workarounds
Understand anchor limitations:
version: '3.8'
# Anchors cannot be partial strings
x-base-image: &base
image: myapp
services:
# This doesn't work
service1:
<<: *base
image: *base:v1 # Error: can't append to anchor
# Workaround: use complete values
service2:
image: myapp:v1
Anchors reference complete values, not partial strings for concatenation.
Extension Field Organization
Organize extension fields logically:
version: '3.8'
# === Service Templates ===
x-service-base: &service-base
restart: unless-stopped
x-web-service: &web-service
<<: *service-base
expose: ["80"]
# === Common Configurations ===
x-logging-config: &logging-config
logging:
driver: json-file
options:
max-size: 10m
# === Environment Variables ===
x-common-env: &common-env
LOG_LEVEL: info
TZ: UTC
services:
# Services here
Comments help navigate large configuration files.
Validating Merged Configurations
Check the final merged configuration:
docker-compose config
This command outputs the fully resolved Compose file with all anchors expanded and merged, helping verify your template logic.
Anchor Debugging Techniques
Debug anchor merging issues:
version: '3.8'
x-debug: &debug
environment:
DEBUG_ANCHOR: "true"
services:
test:
<<: *debug
image: alpine
command: env | grep DEBUG
Run this service to verify anchor values are applied correctly.
Best Practices for Anchor Usage
Group related anchors together: Keep related templates near each other for better readability.
Use descriptive anchor names: Names like &service-defaults are clearer than &s1.
Document complex anchors: Add comments explaining what templates are for:
# Base configuration for all API services # Includes logging, restart policy, and common env vars x-api-defaults: &api-defaults restart: unless-stopped
Prefer extension fields over inline anchors: Define anchors in the extension section rather than inline:
# Good
x-defaults: &defaults
restart: unless-stopped
services:
app:
<<: *defaults
# Avoid
services:
app: &app-anchor
restart: unless-stopped
app-copy:
<<: *app-anchor
Keep anchor nesting shallow: Deep nesting becomes hard to follow. Limit to 2-3 levels.
Test merged configurations: Always run docker-compose config to verify merging works as expected.
Don't overuse anchors: Use anchors for repeated configuration, not for everything. Too many anchors reduce readability.
Consider anchor scope: Remember that anchors defined in one Compose file aren't available in other files.
Use consistent merge syntax: Stick with <<: *anchor syntax for merging rather than mixing approaches.
Version control anchor changes carefully: Changes to anchors affect all services using them. Review impacts before committing.
Document override patterns: When services override anchor values, comment why:
services:
critical:
<<: *defaults
restart: always # Override: critical service needs always restart
YAML anchors and Docker Compose extension fields provide powerful abstraction mechanisms for reducing duplication in Compose files. By creating reusable templates for common configurations, environment variables, and service patterns, you can maintain large, complex Compose files with greater consistency and less effort. The merge key operator enables sophisticated configuration composition, while extension fields keep template definitions organized and separate from actual service definitions.