name: sayt-cnt description: > 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. user-invocable: false
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
- Cleans up any leftover containers:
docker compose down -v --timeout 0 --remove-orphans - Sets up Docker-in-Docker (dind) environment with a socat proxy
- Runs:
docker compose run --build --service-ports develop - Cleans up the socat container on exit
sayt integrate
- Cleans up:
docker compose down -v --timeout 0 --remove-orphans - If
--no-cache: builds without Docker layer cache first - Sets up dind environment
- Runs:
docker compose up integrate --abort-on-container-failure --exit-code-from integrate --force-recreate --build --renew-anon-volumes --remove-orphans --attach-dependencies - On success: cleans up containers
- On failure: leaves containers running for inspection (run
docker compose logsordocker compose down -vwhen 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.
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.
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
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:
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:
FROM debug AS integrate
COPY tests/ tests/
CMD ["pnpm", "test:int"]
Full Multi-Stage Example (JVM)
# 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)
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):
- socat proxy — A socat container is started to proxy the Docker socket over TCP
- Environment variables —
DOCKER_HOST,TESTCONTAINERS_HOST_OVERRIDE, andDOCKER_AUTH_CONFIGare injected - 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
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 integrateautomatically runsdocker compose down -vto clean up - Failure: Containers are left running so you can inspect logs:
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
- Always define
developandintegrate— These are the services sayt expects - Use
target:in build —debugfor develop,integratefor integrate - Set
contextto monorepo root — Usually../..from a service directory - Include the
host.envsecret — Required for dind and credential forwarding - Use
network_mode: hostfor develop — Simplifies networking - 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