Agent skill

sayt-cnt

How to write Dockerfile + compose.yaml — the develop/integrate service convention, multi-stage targets, dind helpers. Use when containerizing a project, writing docker compose services, or setting up integration tests.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/cnt

SKILL.md

launch / integrate — Docker Compose Containerization

sayt launch starts a containerized development environment. sayt integrate runs integration tests in containers. Both use Docker Compose with a specific service convention.

How It Works

sayt launch

  1. Cleans up any leftover containers: docker compose down -v --timeout 0 --remove-orphans
  2. Sets up Docker-in-Docker (dind) environment with a socat proxy
  3. Runs: docker compose run --build --service-ports develop
  4. Cleans up the socat container on exit

sayt integrate

  1. Cleans up: docker compose down -v --timeout 0 --remove-orphans
  2. If --no-cache: builds without Docker layer cache first
  3. Sets up dind environment
  4. Runs: docker compose up integrate --abort-on-container-failure --exit-code-from integrate --force-recreate --build --renew-anon-volumes --remove-orphans --attach-dependencies
  5. On success: cleans up containers
  6. On failure: leaves containers running for inspection (run docker compose logs or docker compose down -v when done)

The compose.yaml Convention

sayt expects two services in compose.yaml:

develop service (for sayt launch)

The development service runs your app with hot reload, debugging, port mapping, etc.

yaml
services:
  develop:
    command: ./gradlew dev -t          # Your dev command
    ports:
      - "8080:8080"                    # Expose ports to host
    build:
      network: host
      context: ../..                   # Monorepo root (or ".")
      dockerfile: services/myapp/Dockerfile
      secrets:
        - host.env
      target: debug                    # Dockerfile stage for dev
    volumes:
      - //var/run/docker.sock:/var/run/docker.sock
    entrypoint:
      - /monorepo/plugins/devserver/dind.sh
    secrets:
      - host.env
    network_mode: host

integrate service (for sayt integrate)

The integration service runs your test suite in an isolated container.

yaml
services:
  integrate:
    command: "true"                    # Overridden by Dockerfile CMD
    build:
      network: host
      context: ../..
      dockerfile: services/myapp/Dockerfile
      secrets:
        - host.env
      target: integrate               # Dockerfile stage for integration

Secrets

yaml
secrets:
  host.env:
    environment: HOST_ENV             # Injected by sayt's dind helper

The HOST_ENV secret contains Docker credentials, Kubernetes config, and dind connection info.

Dockerfile Multi-Stage Pattern

sayt expects Dockerfiles with at least two targets:

debug target (used by develop)

Contains the full development environment with source code, tools, and hot reload:

dockerfile
FROM node:22 AS debug
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install
COPY . .
CMD ["pnpm", "dev"]

integrate target (used by integrate)

Extends debug with integration test execution:

dockerfile
FROM debug AS integrate
COPY tests/ tests/
CMD ["pnpm", "test:int"]

Full Multi-Stage Example (JVM)

dockerfile
# Base with tools
FROM eclipse-temurin:21 AS debug
WORKDIR /app
COPY gradlew gradlew.bat gradle.properties settings.gradle.kts build.gradle.kts ./
COPY gradle/ gradle/
RUN ./gradlew dependencies
COPY src/main/ src/main/
COPY .vscode/ .vscode/
RUN ./gradlew assemble
COPY src/test/ src/test/

# Integration tests
FROM debug AS integrate
COPY src/it/ src/it/
CMD ["./gradlew", "integrationTest"]

Full Multi-Stage Example (Node.js)

dockerfile
FROM node:22 AS debug
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
CMD ["pnpm", "dev"]

FROM debug AS integrate
CMD ["pnpm", "test:int", "--run"]

Docker-in-Docker (dind) Support

sayt provides dind helpers for scenarios where containers need to talk to Docker (e.g., testcontainers):

  1. socat proxy — A socat container is started to proxy the Docker socket over TCP
  2. Environment variablesDOCKER_HOST, TESTCONTAINERS_HOST_OVERRIDE, and DOCKER_AUTH_CONFIG are injected
  3. host.env secret — All dind connection info is passed as a build secret

This enables testcontainers-based integration tests to create sibling containers.

Host Networking

sayt services use network_mode: host by default. This means:

  • Services can reach each other via localhost
  • No port mapping conflicts
  • Testcontainers can connect back to the host Docker daemon
  • Works on Linux natively; on macOS, Docker Desktop provides host networking emulation

Complete compose.yaml Example

yaml
volumes:
  root-dot-docker-cache-mount: {}

services:
  develop:
    command: ./gradlew dev -t
    ports:
      - "8080:8080"
    build:
      network: host
      context: ../..
      dockerfile: services/tracker/Dockerfile
      secrets:
        - host.env
      target: debug
    volumes:
      - //var/run/docker.sock:/var/run/docker.sock
      - ${HOME:-~}/.skaffold/cache:/root/.skaffold/cache
    entrypoint:
      - /monorepo/plugins/devserver/dind.sh
    secrets:
      - host.env
    network_mode: host

  integrate:
    command: "true"
    build:
      network: host
      context: ../..
      dockerfile: services/tracker/Dockerfile
      secrets:
        - host.env
      target: integrate

secrets:
  host.env:
    environment: HOST_ENV

Cleanup Behavior

  • Success: sayt integrate automatically runs docker compose down -v to clean up
  • Failure: Containers are left running so you can inspect logs:
    bash
    docker compose logs          # View all logs
    docker compose logs integrate # View integration service logs
    docker compose down -v       # Clean up manually when done
    

Writing Good Compose Files for sayt

  1. Always define develop and integrate — These are the services sayt expects
  2. Use target: in builddebug for develop, integrate for integrate
  3. Set context to monorepo root — Usually ../.. from a service directory
  4. Include the host.env secret — Required for dind and credential forwarding
  5. Use network_mode: host for develop — Simplifies networking
  6. Set command: "true" for integrate — Let the Dockerfile CMD handle execution

Current flags

!sayt help launch 2>&1 || true !sayt help integrate 2>&1 || true

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results