BXUP Track progress & test yourself — free
QA & Testing · Playbook · Foundation · ~4 min read
QA & TESTING · PLAYBOOK · FOUNDATION

Scaffold Test Environments — Compose Stacks and CI Files, Explained Line by Line

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.

When to use

USE THIS WHEN

You own tests, not infra

  • Your suite currently runs against a shared staging environment that breaks weekly.
  • You can describe what the stack needs (app, Postgres, WireMock, seeded data) but not the compose/YAML syntax cold.
  • A new service needs its test job wired into CI and the last example in the repo is two years stale.
SKIP IT WHEN

The environment is the product

  • Your platform team provides a golden test-environment template — use theirs; divergence costs more than convenience.
  • The change is one line in a working compose file. Edit it directly.
  • The stack needs Kubernetes, service meshes, or cloud IAM — that is a platform-engineering task, not a scaffold.

Prerequisites

  • Docker Desktop or a compatible runtime; docker compose version ≥ 2.x.
  • Claude Code (best — it reads the repo) or a chat assistant plus willingness to paste context.
  • Your test command and its dependencies written down: what the suite needs running, which ports, what seed data.

The recipe

  1. Write the stack spec as bullets before prompting. Services, versions, ports, health requirements, seed data. Vague in, generic out.

  2. 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.
    
  3. 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.

  4. 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.

  5. 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.
    
  6. 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.

  7. 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.

Key Takeaway:

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.

Variations

  • Claude Code: run it in the repo; it finds your Dockerfile, ports, and test command itself and drafts both files in one pass. Still demand the explanation.
  • GitLab CI: same recipe; ask for services: + .gitlab-ci.yml, validate with glab ci lint.
  • Testcontainers instead of compose: when the suite is JVM/Node/Python-native, ask the assistant to convert the compose spec into Testcontainers code — the health-ordering logic comes for free.

Quality check

  • Two consecutive down -v / up --wait cycles produce identical docker compose ps output and identical test results.
  • No :latest tags, no unpinned CI actions, no secrets in compose or workflow files (grep -riE 'password|secret|token' and justify every hit).
  • CI logs appear on failure; teardown runs even when tests crash.
  • The line-by-line explanation exists somewhere a new joiner will find it.

Pitfalls

  • depends_on without a healthcheck. The classic. Container "started" is not service "ready"; you get connection-refused flakes that look like test bugs.
  • Host-path volume surprises. Drafts love mounting ./ into containers; on CI runners that means permissions chaos. Named volumes unless you have a reason.
  • Compose-version drift. Syntax that works locally on compose v2 silently degrades on older runners. Pin the runner image or assert docker compose version in the job.
  • The magical environment. Skip the explanation pass under deadline pressure and in six months this stack is the thing "nobody touches because tests break." That is the exact failure this playbook exists to prevent.
Pro Tip

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.

This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in