Deploy Skill — Docker Compose Stack and Bootstrap
Usage
/deploy
/deploy --up
/deploy— generate all deployment files without starting services./deploy --up— generate files and immediately runbash init.shto start the stack.
Prerequisites
The following must exist before running this skill:
backend/and/orfrontend/— source code with known runtimes and entry points.specs/design/deployment.md— deployment architecture decisions (DB engine, port assignments, service names).project-manifest.json— for service names, port config, and environment variable inventory.
If any of these are missing, stop and report what is absent.
Steps
Step 1 — Read Project Manifest
Read project-manifest.json. Extract:
- Service names and their roles (backend API, frontend SPA, worker, etc.)
- Port assignments for each service.
- Database engine and version.
- Any declared environment variable names.
Step 2 — Read Deployment Architecture
Read specs/design/deployment.md. Extract:
- Service dependencies and startup order.
- Health check endpoints for each service.
- Volume requirements (DB data, uploads, etc.).
- Any explicit constraints (network mode, resource limits).
Step 3 — Generate docker-compose.yml
Generate docker-compose.yml at the project root from the deployment template.
Requirements:
- Every service that another service depends on must use
depends_onwith acondition: service_healthyentry. - Define named volumes for all persistent data (DB data directory, upload storage).
- No hardcoded passwords — all secrets come from environment variables.
- Each service must have a
healthcheckblock with a realistictest,interval,timeout, andretries. - Use specific image tags (e.g.,
postgres:16-alpine), neverlatest. - Observability (G9): when
project-manifest.json#observability.enabledis true, add Prometheus scrape-discovery labels to the backend service so any Prometheus can scrape it —prometheus.io/scrape: "true",prometheus.io/path: "<observability.metrics_path>",prometheus.io/port: "<backend port>". Do not modify application source here; deploy only wires the service so the app's existing/metricsendpoint is discoverable.
Step 4 — Generate Dockerfiles
backend/Dockerfile
- Multi-stage build:
builderstage installs dependencies,runtimestage copies only the built artefact. - Run as non-root user.
EXPOSEthe port declared in the manifest.HEALTHCHECKinstruction matching the health check endpoint.
frontend/Dockerfile (if frontend exists)
- Build stage: install deps, run build.
- Serve stage: NGINX or equivalent static server.
EXPOSEthe frontend port.
Step 5 — Generate .env.example
Generate .env.example at the project root.
Every environment variable referenced in docker-compose.yml or either Dockerfile must have an entry. Each entry must include a comment explaining what it is and what format it expects:
# Database connection string
DATABASE_URL=postgresql://user:pass@db:5432/appdb
# JWT signing secret — generate with: openssl rand -hex 32
JWT_SECRET=change-me-in-production
No variable may appear in compose or Dockerfiles without a corresponding .env.example entry.
Step 6 — Generate init.sh
Generate init.sh at the project root from the init template.
The script must:
- Check that Docker and Docker Compose are available.
- Copy
.env.exampleto.envif.envdoes not already exist. - Run
docker compose build. - Run
docker compose up -d. - Poll each service health check URL until it responds or a timeout is reached (30s per service).
- Print a summary: which services are up, which URLs are accessible.
Make init.sh executable (chmod +x init.sh).
Step 7 — Phase Evaluation Gate
This gate runs BEFORE the stack is started — it exists to catch invalid compose syntax, port conflicts, and missing health checks before they become runtime failures.
First, validate syntax deterministically:
docker compose config
Fix any errors before spawning the evaluator.
Then spawn the evaluator agent (artifact mode) to validate deploy artifacts.
Agent invocation:
Spawn Agent with subagent_type="evaluator" and prompt:
- Phase: deploy
- Artifacts: docker-compose.yml, all Dockerfile* files, .env.example, init.sh
- Upstream: specs/design/architecture.md (if exists)
- Rubric: Read .claude/templates/phase-eval-rubrics.json, key "deploy"
- Iteration: 1 (increment on retry)
- Previous score: null (or previous iteration's weighted_average)
- Check: Every service in architecture.md has a compose entry. No port conflicts. Health checks defined. YAML syntax valid.
- Write result to specs/reviews/phase-deploy-eval.json
Ratchet loop (max 2 iterations):
- If verdict is PASS — proceed to Step 8.
- If verdict is FAIL — fix config issues based on findings. Re-run evaluator.
- Ratchet rule: weighted_average must be >= previous iteration. Revert on regression.
- After 2 iterations — proceed to Step 8 with findings attached as warnings, but do NOT run
--upon an unresolved FAIL — report the failure instead.
Step 8 — If --up Flag
Only after the gate above has passed (or warnings were explicitly accepted), run:
bash init.sh
Report the output. If any service fails its health check, print the service logs and stop.
Output
| Path | Purpose |
|---|---|
docker-compose.yml |
Full service stack definition |
backend/Dockerfile |
Backend container build |
frontend/Dockerfile |
Frontend container build (if applicable) |
.env.example |
All required environment variables documented |
init.sh |
Bootstrap script: build, start, health check |
Gotchas
- Missing
depends_onwith health conditions. Services that start before their dependencies are ready cause intermittent failures. Always usecondition: service_healthy. - No bind mount volumes. Named volumes persist across
docker compose down. Bind mounts to host paths break in CI and on other machines. - Hardcoded passwords. Never put credentials directly in
docker-compose.yml. All secrets go in.env. - Missing
.env.exampleentries. New variables added to compose but not documented leave other developers with silent failures. latestimage tags. Tags likepostgres:latestbreak reproducibility. Pin to a specific version.- Root user in containers. Run application processes as a non-root user to limit blast radius on compromise.