The test environment is where QA teams inherit infrastructure work with none of the infrastructure training. AI closes that gap — a docker-compose stack and CI workflow drafted in minutes — but only if you enforce one rule: every line gets explained before it gets committed. Auditable, not magical.
Tier: Foundation · Workflow area: Test data & environments
This playbook scaffolds a reproducible local test stack (app + database + mocked dependencies) and the CI workflow that runs your suite against it, using an assistant for drafting and a line-by-line explanation pass so the environment never becomes a haunted artifact nobody dares touch.
docker compose version ≥ 2.x.Write the stack spec as bullets before prompting. Services, versions, ports, health requirements, seed data. Vague in, generic out.
Prompt for the compose file with constraints and an explanation requirement.
Write a docker-compose.yml for local integration testing of a Node 20
service in ./api.
Requirements:
- Services: api (built from ./api/Dockerfile), postgres:16-alpine,
wiremock/wiremock:3.9.1 loading stubs from ./stubs.
- Postgres seeded from ./db/seed.sql on first start.
- api must wait for postgres to be HEALTHY, not just started —
use a real healthcheck + depends_on condition.
- Pin every image to a specific tag. No :latest anywhere.
- Named volumes only; nothing writes to my host except ./coverage.
- Then explain the file line by line, flagging any line where you
made a judgment call I should review.
Read the explanation, not just the YAML. The judgment-call flags are the review agenda. Typical output worth interrogating:
healthcheck.test: ["CMD-SHELL", "pg_isready -U test"] — I assumed
user 'test'; align with your seed script.
depends_on.postgres.condition: service_healthy — compose v2 syntax;
older CI runners on compose v1 will ignore this silently.
That second flag is exactly the kind of landmine the explanation pass exists to surface.
Boot it and prove the ordering works.
docker compose up -d --wait
docker compose ps --format 'table {{.Name}}\t{{.Status}}'
NAME STATUS
api-1 Up 12 seconds (healthy)
postgres-1 Up 25 seconds (healthy)
wiremock-1 Up 25 seconds
Then the destructive proof: docker compose down -v && docker compose up -d --wait twice. If the second cold start behaves differently from the first, your seed step is not idempotent — fix that now, not in CI.
Prompt for the CI workflow that reuses the same stack.
Write a GitHub Actions workflow that runs `npm test` in ./api against
the docker-compose.yml above.
- Trigger: PRs touching api/** or stubs/**. Path-filtered, not on: push.
- Bring the stack up with --wait, run tests, ALWAYS dump
`docker compose logs` on failure, always tear down.
- Pin all actions to a full commit SHA.
- Explain line by line as before.
Lint, then dry-run on a branch. actionlint the workflow, verify each pinned action exists at that SHA, push to a throwaway branch, and confirm the failure path too: break one test, check that compose logs appear in the run output. A CI job that hides logs on failure is worse than no job.
Commit the explanation. Paste the line-by-line explanation into docs/test-env.md (or the PR description at minimum). The scaffold is done when a teammate can answer "why does api wait on postgres health?" without asking you.
The compose file is not the deliverable — the understood compose file is. If nobody on the team can explain a line, that line is a future 2 a.m. incident, and "the AI wrote it" will not be an acceptable answer in the retro.
services: + .gitlab-ci.yml, validate with glab ci lint.down -v / up --wait cycles produce identical docker compose ps output and identical test results.:latest tags, no unpinned CI actions, no secrets in compose or workflow files (grep -riE 'password|secret|token' and justify every hit).depends_on without a healthcheck. The classic. Container "started" is not service "ready"; you get connection-refused flakes that look like test bugs../ into containers; on CI runners that means permissions chaos. Named volumes unless you have a reason.docker compose version in the job.When any stack change lands, rerun the explanation prompt on the diff — "explain what changed and what could break" — and paste it into the PR. Thirty seconds of prompting keeps the environment permanently auditable.