Back to BlogDocker Container · Docker compose · docker

Compose File Reuse with Anchors and Extensions

2025-12-29

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:

  1. Define an anchor with &anchor-name
  2. Reference it with *anchor-name
  3. 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: &microservice
  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.

We use cookies to improve your experience and analyse site traffic. See our Privacy Policy.