Back to list
KyleKing

docker-compose-bake-ecr-skill

by KyleKing

Personal Dotfiles synced with `chezmoi`

1🍴 0📅 Jan 23, 2026

SKILL.md


name: docker-compose-bake-ecr-skill description: Guide development with modern Docker Compose, Docker Bake, and ECR for building and managing container images.

Docker Compose, Bake, and ECR Skill

Use when configuring Docker Compose services, writing Docker Bake files for CI/CD builds, or managing ECR image repositories. Covers modern Compose features, Bake HCL syntax, and ECR caching strategies.

Docker Compose (Modern Features)

YAML anchors and aliases

Reduce duplication with anchors (&name) and aliases (*name). Use x- prefix for reusable fragments:

# Define reusable fragments with x- prefix (not rendered as services)
x-common-env: &common-env
  LOG_LEVEL: ${LOG_LEVEL:-INFO}
  DEVELOPER_ID: ${USER:-}

x-postgres-depends: &postgres-depends
  postgres:
    condition: service_healthy
    restart: true

x-volumes: &app-volumes
  - type: bind
    source: ./app
    target: /app
  - type: tmpfs
    target: /app/.venv

services:
  api:
    environment:
      <<: *common-env        # Merge anchor into mapping
      API_PORT: 3500         # Add service-specific vars
    volumes: *app-volumes    # Use anchor directly
    depends_on:
      <<: *postgres-depends  # Merge dependency anchor

Merge syntax:

  • <<: *anchor merges mapping keys into current mapping
  • <<: [*anchor1, *anchor2] merges multiple anchors (left-to-right precedence)
  • Direct *anchor reference for non-mapping values (lists, scalars)

Service profiles

Group services for selective startup:

services:
  postgres:
    profiles: ["infra", "api", "workers"]  # Multiple profiles

  api:
    profiles: ["api"]

  workers:
    profiles: ["workers"]
docker compose --profile infra up -d       # Start infrastructure only
docker compose --profile api up -d         # Start api + its dependencies
docker compose --profile infra --profile api up  # Combine profiles

Service extends

Inherit and override service configurations:

services:
  api-base:
    profiles: ["base"]  # Never started directly
    image: api:latest
    build:
      context: ./api
    environment:
      LOG_LEVEL: INFO

  dev-api:
    extends: api-base
    profiles: ["dev"]
    build:
      target: dev-runtime  # Override build target
    environment:
      DEBUG: "true"        # Add to inherited env

  prod-api:
    extends: api-base
    profiles: ["prod"]
    build:
      target: prod-runtime

Health checks

services:
  postgres:
    healthcheck:
      test: "pg_isready --username $$POSTGRES_USER --dbname $$POSTGRES_DB"
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s  # Grace period before checks count

  api:
    healthcheck:
      test: ["CMD", "curl", "-f", "-s", "-o", "/dev/null", "http://0.0.0.0:3500/health"]
      interval: 20s
      timeout: 5s
      start_period: 10s

Note: Use $$VAR to escape environment variables in shell-form test commands.

Dependency conditions

services:
  api:
    depends_on:
      postgres:
        condition: service_healthy       # Wait for healthcheck
        restart: true                    # Restart if dependency restarts
      migrations:
        condition: service_completed_successfully  # Wait for exit 0
ConditionUse case
service_startedDefault, just wait for container start
service_healthyWait for healthcheck to pass
service_completed_successfullyWait for init container to exit 0

Build configuration

services:
  api:
    build:
      context: ./api
      dockerfile: Dockerfile           # Default, can omit
      target: dev-runtime              # Multi-stage target
      additional_contexts:             # Named build contexts
        common: ./common
        lambdas: ./lambdas
      args:
        BUILD_ENV: development
      x-bake:                          # Docker Bake integration
        targets: [api]
        platforms: ["linux/amd64"]
    pull_policy: build                 # Always rebuild locally

additional_contexts enables COPY --from=common in Dockerfile without separate build steps.

Volume types

volumes:
  - type: bind                    # Host path mount
    source: ./app
    target: /app
    read_only: true               # Optional

  - type: volume                  # Named volume
    source: static-files
    target: /static

  - type: tmpfs                   # In-memory filesystem
    target: /app/.venv            # Useful for venvs in dev

volumes:
  static-files:                   # Named volume definition

Network aliases

services:
  postgres:
    networks:
      default:
        aliases:
          - postgres.myapp.local   # Additional DNS names
          - db.myapp.local

Environment variable interpolation

services:
  api:
    container_name: api${COMPOSE_ID:-}        # Suffix with ID or empty
    ports:
      - "${COMPOSE_ID:-}3500:3500"            # Prefix port
    environment:
      LOG_LEVEL: ${LOG_LEVEL:-INFO}           # Default value
      SECRET: ${SECRET:?required}             # Error if unset

Docker Bake

Docker Bake builds multiple images in parallel with shared configuration. Uses HCL or JSON format.

Variables

variable "REGISTRY" {
  default = "760682031284.dkr.ecr.us-east-1.amazonaws.com"
}

variable "TAG" {
  default = "latest"
}

variable "BRANCH" {
  default = "main"
}

// Nullable variable (no default)
variable "SENTRY_AUTH_TOKEN" {
  default = null
}

Groups

group "default" {
  targets = ["api", "workers", "dashboard"]
}

group "backend" {
  targets = ["api", "workers"]
}
docker buildx bake                 # Build default group
docker buildx bake backend         # Build specific group
docker buildx bake api workers     # Build specific targets

Targets

// Base target for inheritance
target "base" {
  dockerfile = "Dockerfile"
  target     = "prod-runtime"
  platforms  = ["linux/amd64"]
}

// Inherit and extend
target "base_with_common" {
  inherits = ["base"]
  contexts = {
    common = "./common"
  }
}

target "api" {
  inherits   = ["base_with_common"]
  context    = "./api"
  tags       = ["${REGISTRY}/myapp-api:${TAG}"]
  cache-from = [
    "type=registry,ref=${REGISTRY}/myapp-api:branch-${BRANCH}",
    "type=registry,ref=${REGISTRY}/myapp-api:branch-main"
  ]
  cache-to   = ["type=registry,ref=${REGISTRY}/myapp-api:branch-${BRANCH},mode=max"]
}

target "dashboard" {
  inherits   = ["base"]
  context    = "./dashboard"
  tags       = ["${REGISTRY}/myapp-dashboard:${TAG}"]
  args = {
    VITE_GITHASH      = VITE_GITHASH
    SENTRY_AUTH_TOKEN = SENTRY_AUTH_TOKEN
  }
}

Target attributes

AttributeDescription
inheritsList of targets to inherit from
contextBuild context directory
dockerfileDockerfile path relative to context
targetMulti-stage build target
platformsTarget platforms (e.g., ["linux/amd64", "linux/arm64"])
tagsImage tags to apply
argsBuild arguments
contextsNamed build contexts (maps to additional_contexts)
cache-fromCache sources
cache-toCache destinations
outputOutput destinations
pullAlways pull base images
no-cacheDisable cache

Multiple contexts

target "lambda" {
  context  = "./lambdas/my_lambda"
  contexts = {
    common         = "./common"
    lambdas_common = "./lambdas"
  }
}

In Dockerfile:

COPY --from=common . /app/common
COPY --from=lambdas_common shared/ /app/shared/

Compose integration

Reference Bake settings in compose.yml:

services:
  api:
    build:
      context: ./api
      x-bake:
        targets: [api]
        platforms: ["linux/amd64"]
docker buildx bake --file compose.yml  # Build from compose

ECR (Elastic Container Registry)

Authentication

# Configure Docker to authenticate with ECR
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin \
  760682031284.dkr.ecr.us-east-1.amazonaws.com

Tagging strategies

// Git-based tags for traceability
tags = [
  "${REGISTRY}/myapp-api:${GIT_SHA}",              // Immutable commit ref
  "${REGISTRY}/myapp-api:branch-${BRANCH}",        // Mutable branch ref
  "${REGISTRY}/myapp-api:${TAG}",                  // Release tag (latest, v1.2.3)
]

Recommended approach:

  • Use git SHA for production deployments (immutable)
  • Use branch tags for cache references (mutable)
  • Use semver tags for releases

Cache configuration

Registry-based caching with ECR:

target "api" {
  cache-from = [
    // Try current branch cache first
    "type=registry,ref=${REGISTRY}/myapp-api:branch-${BRANCH}",
    // Fall back to main branch cache
    "type=registry,ref=${REGISTRY}/myapp-api:branch-main"
  ]
  cache-to = [
    // Push cache to branch-specific tag
    "type=registry,ref=${REGISTRY}/myapp-api:branch-${BRANCH},mode=max"
  ]
}

Cache modes:

  • mode=min: Only cache final stage layers (smaller, faster push)
  • mode=max: Cache all intermediate stages (better cache hits)

CI/CD workflow

# .github/workflows/docker-build.yml
jobs:
  build:
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::760682031284:role/github-actions
          aws-region: us-east-1

      - uses: aws-actions/amazon-ecr-login@v2

      - name: Build and push
        run: |
          docker buildx bake \
            --set "*.cache-from=type=registry,ref=$REGISTRY/$IMAGE:branch-$BRANCH" \
            --set "*.cache-to=type=registry,ref=$REGISTRY/$IMAGE:branch-$BRANCH,mode=max" \
            --push

ECR lifecycle policies

Configure in ECR console or via Terraform/Pulumi to auto-clean old images:

{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Keep last 10 branch images",
      "selection": {
        "tagStatus": "tagged",
        "tagPrefixList": ["branch-"],
        "countType": "imageCountMoreThan",
        "countNumber": 10
      },
      "action": { "type": "expire" }
    },
    {
      "rulePriority": 2,
      "description": "Expire untagged after 7 days",
      "selection": {
        "tagStatus": "untagged",
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 7
      },
      "action": { "type": "expire" }
    }
  ]
}

Dockerfile best practices

Multi-stage builds

# syntax=docker/dockerfile:1

FROM base AS builder
# Install build dependencies and compile

FROM base AS dev-builder
# Add dev/test dependencies

FROM base AS runtime
# Minimal runtime setup
COPY --from=builder /app /app

FROM runtime AS prod-runtime
COPY --from=builder /opt/venv /opt/venv

FROM runtime AS dev-runtime
COPY --from=dev-builder /opt/venv /opt/venv

Cache mounts for package managers

# uv/pip cache
RUN --mount=type=cache,id=uv-cache,sharing=shared,target=/root/.cache/uv \
    uv sync --no-default-groups

# npm cache
RUN --mount=type=cache,target=/root/.npm \
    npm ci

# apt cache
RUN --mount=type=cache,target=/var/cache/apt \
    apt-get update && apt-get install -y package

Copy from named contexts

# Reference additional_contexts from compose or bake
COPY --from=common . /app/common
COPY --from=lambdas_common shared/ /app/shared/

Debugging checklist

  1. Compose service not starting: Check docker compose logs <service>
  2. Healthcheck failing: Verify endpoint and use docker inspect for health status
  3. Build context too large: Check .dockerignore, use targeted additional_contexts
  4. Cache not working: Verify ECR auth, check cache-from refs exist
  5. Anchor not merging: Ensure <<: syntax for mappings, direct *ref for lists
  6. Profile not activating: Use --profile flag, check service profile list

Safeguards

  • Do not use latest tag for production deployments; use immutable git SHA tags
  • Do not disable healthchecks in production configurations
  • Validate cache-from references exist before relying on them
  • Use pull_policy: build in development, not production
  • Prefer type=registry cache over type=local for CI/CD
  • Set appropriate ECR lifecycle policies to manage storage costs

Score

Total Score

50/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon