Skip to content

Environments

An environment is a topology decision with a budget attached. The question is not how many to have — it is what each one is for, and what would be lost if it did not exist.

Most organisations have four or five, of which two are load-bearing and the rest exist because they always have.

Started for the test, thrown away after it. This is the correct default for everything up to and including integration tests, and Testcontainers is what made it practical.

Isolated. Nobody else’s data, nobody else’s schema, nobody else’s failed run.

Parallel. Ten test classes, ten databases, no coordination.

Versioned with the code. The container tag is in the repository, so the environment moves with the branch. A migration and its test travel together.

Never down. There is nothing to be down. Nothing blocks a merge because somebody is redeploying.

The cost is a few seconds of startup per suite, and a reusable-container mode removes most of that locally. It is almost always less than one afternoon of the failure modes below.

Most organisations have one, and it fails in the same way in all of them:

  • Two teams deploy at once and neither can tell whose change broke it.
  • A failed run leaves data behind, and next week’s unrelated failure is caused by it.
  • The schema drifts from what any branch expects, and nobody can say when.
  • It is down, and nothing can be merged, and the queue forms.
  • It becomes nobody’s job, because it is everybody’s.

None of these are fixed by discipline; they are consequences of sharing mutable state between concurrent workflows. The fix is to stop needing it, and the thing that removes the need is contract testing — which verifies both sides independently, without deploying either.

That is the argument for contract tests that has nothing to do with contracts: it is what lets an organisation delete the environment that costs it a day a week.

What is actually needed, and what each one is for.

Environment Purpose Lifetime Data
Local Development and everything in-process The session Synthesised
Ephemeral Integration tests The test run Created by the test
Preview One per pull request — review, demo, exploratory The pull request Seeded, small
Staging Release candidate: E2E, exploratory, security Permanent Masked production subset
Performance Load and soak runs On demand Production-scale, synthesised
Production The system Permanent Real

Preview environments are the highest-value addition for most teams and the one most often skipped: a reviewer clicking through the actual change, and an exploratory charter run against it, both happen before the merge — which is the whole point of shift-left expressed as infrastructure.

The performance environment is separate from staging on purpose. A load test on a shared staging environment measures the neighbours, and it also makes staging unusable for everyone else for the duration. See performance.

“Production-like” is used to mean anything from an identical replica to a laptop with three services stubbed. Say which axes match, because they are the ones the results are valid along:

Topology — same services, same replicas, same proxies, same TLS termination. Cheap to match and the source of most configuration defects.

Configuration — same mechanism, different values. If production reads from a secret store and staging reads from a file, the secret-store path is untested.

Data shape — same volume distribution. A table with 5,000 rows and one with 5 million behave differently in ways that no amount of care compensates for. See test data.

Capacity — usually the axis that is not matched, for cost. Fine, and it means absolute performance numbers from that environment are trends rather than measurements. Say so when quoting them.

Environments are code. Same manifests, same chart, different values. An environment configured by hand drifts, and its drift is discovered by a release.

Rebuildable from nothing. If staging cannot be recreated in an afternoon, it is a pet, and the day it breaks is a project.

Nothing is manually mutated. A fix applied by hand to unblock a run makes the environment lie for everyone afterwards.

Each has an owner and an expiry. Environments without owners accumulate; the sixth environment nobody can name the purpose of is costing money and confusing the release path.