EnvironmentsPLATFORM-SPECIFICSCALE-SPECIFIC

Preview Environments

A running instance per change, created from the pull request and destroyed with it — excellent evidence about wiring and product behaviour, no evidence at all about scale.

The question, the obvious approach, and why it breaks

Every lesson starts where the work starts: an operational problem, a first attempt that is entirely reasonable, and the way production disagrees with it.

The production question

What does giving every pull request its own running environment actually buy, and what does it quietly cost?

The problem

Reviewing a change as a diff tells you about the code and nothing about the running system, so integration and product problems are found after merge, in a queue behind everyone else's changes.

What teams do first

Put a preview URL on every pull request. Reviewers click it, see the change working, and approve with confidence.

How it breaks

The confidence extends further than the evidence. A preview environment has a trivial dataset, no concurrency and stubbed dependencies, so a green preview says nothing about the properties that cause outages.

How it breaks in production
  • The confidence extends further than the evidence. A preview environment has a trivial dataset, no concurrency and stubbed dependencies, so a green preview says nothing about the properties that cause outages.
  • Previews need credentials. If they get production credentials for convenience, every pull request — including one from a fork or a compromised dependency in the build — becomes a path to production data (Secrets in CI).
  • They accumulate. Without enforced teardown, a team ends up running dozens of forgotten stacks and paying for all of them.
  • They frequently share a backing database or a third-party sandbox, so one preview's test data pollutes another's, and a destructive test breaks everybody.
  • Creating one gets slower as the system grows, until the preview arrives after the review is finished and nobody uses it.
CodeBuildTestArtifactReleaseDeployRunObserveOperateIncidentRecoverLearnImprove

What is actually happening

Underneath the tooling, which is the part that survives a change of tool.

  • A preview environment is an ephemeral instance keyed to a change rather than to a stage: the pipeline builds the branch, provisions an isolated stack, seeds it, and publishes a URL that is torn down when the pull request closes.
  • What it genuinely preserves is code paths, integration wiring, configuration plumbing, and the product experience a human can look at. That is a real and underrated set — it catches the class of problem that a diff cannot show.
  • What it does not preserve is everything expensive: data volume, concurrency, real dependencies, cost behaviour. It is the leftmost useful instrument, not a small production.
  • The isolation boundary is the design decision that determines whether previews are safe. Fully isolated stacks are expensive and safe; shared backing services are cheap and let one preview affect another.

The lifecycle, and where each step fails

Preview environments are the most fully automatable thing in this module, which is why the failures cluster in the steps people leave manual: credentials and teardown.

Pull request to preview URL and back
  1. 1
    Build the branch

    Produce an artifact from the pull request head, identically to the main build path.

    fails by A separate preview build path that diverges from the real one, so the preview tests something you will never ship (Build Once, Deploy Many).

    evidence The preview reports the same commit and build identifier as a normal build.

  2. 2
    Provision

    Create an isolated stack — compute, its own schema or database, its own queue prefix.

    fails by Reusing shared backing services, so one preview's migration breaks another's tests.

    evidence Two previews running destructive tests simultaneously do not affect each other.

  3. 3
    Issue credentials

    Mint short-lived, preview-scoped credentials at deploy time.

    fails by Reusing a staging or production credential because it was already in the pipeline (Roles vs Static Keys).

    evidence The preview's identity cannot read production data, demonstrated by trying it.

  4. 4
    Seed

    Load synthetic data that exercises the interesting shapes.

    fails by Loading a production extract for realism, creating an exposure in the least-controlled environment you run.

    evidence The seed dataset is generated by a script in the repository and contains no real identifiers.

  5. 5
    Publish

    Post the URL on the pull request, behind authentication.

    fails by A public URL, indexed and reachable, exposing an unreleased feature.

    evidence An unauthenticated request to the preview URL is refused.

  6. 6
    Destroy

    Tear down on close and on time-to-live expiry, whichever comes first.

    fails by Teardown only on close, so abandoned branches leave stacks running indefinitely.

    evidence Live preview count returns to near zero overnight.

What a green preview is and is not evidence for

This is the same instrument framing as What an Environment Is For, applied to the leftmost useful environment. The left column is genuinely valuable — these are failures a diff review cannot catch — and the right column is the reason a preview must not be treated as a gate for risky change classes.

QuestionDoes a preview answer it?Why
Does the feature work end to end?Yes, for the happy pathReal code, real wiring, a human clicking it
Is the new config key plumbed through?YesThe service boots with the full config schema (Validate at Startup, Fail Clearly)
Does the new dependency get called correctly?PartlyAgainst a stub or sandbox, not the real dependency's bad day
Does the migration run?Yes, mechanicallyOn a trivial dataset — says nothing about duration or locks
Will the migration lock the table?NoLock duration is a function of row count (Expand, Migrate, Contract)
Does it hold up under concurrency?NoOne user, no contention, no races
What does it cost per request?NoNo meaningful traffic to measure against (Cost Per Request)
Does it degrade gracefully when the dependency fails?Only if deliberately testedRequires fault injection; stubs are up by default

The credential boundary

CLOUD-SPECIFICThe identity exchange exists on all major providers but the trust configuration differs in ways that matter: the subject claim used to identify a CI job, whether branch and repository conditions are expressible in the trust policy, and how fork-originated runs are treated are all provider- and CI-specific. A trust policy copied between providers without re-reading the claim semantics is how a preview role ends up assumable by anyone's fork (Human vs Workload Identity).

A preview environment is deployed from a pull request, and a pull request is untrusted input — potentially from a fork, potentially containing a build step someone else wrote. Anything the preview pipeline can reach, that input can reach.

This is why the credential design matters more here than in any other environment, and why "it is only a preview" is the sentence that precedes the incident.

triggersrequests tokenpreview scope onlyshort-lived credsdenied by policyPull request (untrusted input)CI jobWorkload identity scoped to previewPreview secret scopePreview stack own schema, synthetic dataProduction data not reachable
UserLLMAgentToolDataDecisionHumanGuardrail

How to do it properly

Most important first.

  • Scope preview credentials to the preview environment only, issued at deploy time through workload identity rather than stored as long-lived keys (Workload Identity).
  • Seed with synthetic data by default. A preview environment is the least-controlled environment you run; it is the worst place for a production extract (Production Data in Lower Environments).
  • Enforce teardown on pull request close and on an absolute time-to-live, so an abandoned branch cannot leave a stack running for a month.
  • Give each preview its own schema or database when a full instance is too expensive, so tests cannot collide.
  • Treat third-party integrations explicitly: point at a sandbox, a recorded fixture, or a stub, and make it obvious in the UI which one is in use.
  • Keep creation fast enough that the preview beats the reviewer. Past a few minutes people review the diff and ignore the link.

How much can this affect

Every production change has a blast radius. Stated as a scale so it is comparable between changes rather than adjectival — and paired with what actually contains it, because a wide scope with a real containment mechanism is a different situation from a wide scope with none.

Blast radius if this is wrongOne test
One testEveryone
What contains it

Contained by isolation, when isolation is real. Where previews share a database, a third-party sandbox or a credential, the effective scope jumps to every other preview — and if the credential reaches production data, to everyone.

What can go wrong

Failure modes, including of the mitigation
  • A preview URL that is publicly reachable and indexed, exposing an unreleased feature or a debug endpoint.
  • Shared database, so two previews running migrations at once corrupt each other and both look like flaky tests (Flaky Tests).
  • Teardown that runs on pull request close only, so branches abandoned without closing accumulate silently.
  • A preview that talks to production dependencies for convenience — sending real emails, charging real cards, writing real analytics events.
  • Preview infrastructure that itself drifts from production, so it tests a wiring shape that does not exist anywhere else (Environment Drift).
Misreads this invites
  • "The preview looked fine, so it is safe to ship." The preview covered code paths and wiring. It had no data, no concurrency, and stubs where your dependencies are (Why Local Success Predicts So Little).
  • "Previews replace staging." They replace some of what staging was used for — product review and integration wiring. They do not replace rehearsal against realistic data, which staging may or may not have been providing either.
  • "It is just a test environment, the credentials do not matter." A preview environment is reachable from a pull request, which is the least trusted input in your system (CI Security).
  • "We should keep the preview around after merge for reference." That is how you get a long-lived, unowned, drifting environment. Keep the pull request; delete the stack.

Operating it

Evidence is the signal, not the intention. Rollback is sometimes 'you cannot, and that is the point'.

How you know it worked
  • Preview creation time is measured, and the median is short enough that reviewers use the link.
  • A count of live previews exists on a dashboard and returns to near zero when the team is not working.
  • A credential audit shows preview environments hold no credential that grants access to production data (Least Privilege in Production).
  • Reviewers cite behaviour they saw in the preview, not just the diff.
How you get back
  • A preview environment is disposable by construction: the rollback is deletion, and it should be cheap enough to do on a whim.
  • What does not roll back is anything a preview wrote to a shared system — emails sent, webhooks delivered, rows written to a shared database, events pushed into an analytics pipeline. Those side effects need to be blocked at the boundary, not undone.
What to automate, and what stays human
  • Automate creation, seeding, URL publication, teardown on close, and expiry by time-to-live. All of it is mechanical and none of it should require a person.
  • Automate the "no production credentials" check as policy rather than convention (Policy as Code).
  • Do not automate the conclusion. A preview passing is not an approval; a human still decides whether the change's risk class is covered by this kind of evidence (Parity That Is Worth Paying For).
What this costs
  • Full isolation per preview multiplies infrastructure spend by the number of open pull requests; shared backing services cut the cost and reintroduce interference.
  • Seeding realistic-looking synthetic data is real ongoing work, and stale seed data makes previews progressively less useful.
  • The faster you make creation, the more you cut — and each cut moves the preview further from the wiring it was supposed to verify.

Where this applies

This domain is unusually tool- and organisation-dependent. These labels say what each claim is specific to, and what a different platform, provider or organisation does instead.

  • PLATFORM-SPECIFICFrontend and stateless-service previews are close to solved — several hosting platforms create one per pull request by default. Previews for a system with a large stateful backend are a bespoke engineering effort, because the hard part is producing an isolated dataset quickly, not running the code (Stateless vs Stateful Services).
  • SCALE-SPECIFICWith a handful of open pull requests, per-preview isolation is cheap and obviously worth it. At hundreds of concurrent branches, isolation cost dominates and teams move to shared backing services with per-preview schemas — which brings back the interference this lesson warns about, now as a deliberate trade.

Where the depth lives

This domain teaches delivery and operation, and hands the mechanism off to the domain that owns it.

Domains that do not exist yet
  • Testing & Reliability Engineering — which test tiers belong on a preview environment and which are wasted there.