Skip to main content

Every Pull Request Gets Its Own Environment: The Real Build-vs-Buy Math for Preview Environments

11 min readDora NodaDora Noda
Share
On this page

Release ran the numbers on the shared-staging queue and they are ugly: with a single staging environment, a simulated team finished 12 out of 42 tickets while 23 piled up waiting for an environment to free up. Give the same team five environments and throughput jumps to 39 tickets — better than triple, with nothing else changed. No new hires, no faster CI, just no queue.

That queue is why preview environments went from luxury to table stakes. Humanitec's DevOps benchmarking found 83.56 percent of top-performing engineering orgs let developers spin up ephemeral environments on their own, industry surveys put 80 percent of teams rating preview environments as Valuable or Extremely Valuable, and some 41 percent of teams now validate infrastructure-as-code changes in ephemeral previews before rollout. The promise is simple: every pull request gets its own isolated, automatically-destroyed deployment instead of a shared staging box teams schedule like a conference room.

Here is the answer up front, with the receipts below: for a typical team — four services, around 15 concurrently open PRs, each environment living about two days — buying preview environments from a dedicated vendor costs roughly $9,000 a year at an illustrative $50 per active environment per month, every year, scaling linearly with how many PRs you have open. Building namespace-per-PR previews on a Cluster-API fleet you already run costs roughly $18,000 once (about three engineering-weeks at a loaded $150/hour, stated as an assumption so you can plug in your own rate) plus a few hundred a year in marginal compute — then drops to maintenance-only from year two.

The vendor wins year one for small teams; the build wins from roughly month 18, or sooner the moment concurrent PRs climb past about 20. The variable that moves the answer is always the same: concurrent open PRs times environment lifetime. Everything below shows how each number is built.

The six jobs every preview solution does, whether you build or buy

Strip away the marketing and a preview environment performs exactly six jobs. This checklist is the scoring rubric for everything that follows — a vendor or a DIY recipe that skips one is handing you the job back:

  1. Provision an isolated scope — one PR's deployment cannot see, break, or starve another's. On Kubernetes that is a namespace per PR; vendors use namespaces, virtual clusters, or accounts.
  2. Route traffic with ephemeral DNSpr-1234.preview.example.com must exist minutes after the PR opens, without a human editing a zone file.
  3. Terminate TLS — browsers and webhooks reject plaintext, so every ephemeral hostname needs a valid certificate on day one.
  4. Provision per-PR data — the app needs a database shaped like production: a fresh small instance, a schema-per-PR, or a cloned snapshot, each with different cost and fidelity.
  5. Build and ship the PR image — the environment must run this branch's code, rebuilt on every push, not last week's main.
  6. Garbage-collect on merge — the environment must die when the PR closes, plus a backstop for PRs that are abandoned, never merged, and never closed.

Jobs 1 through 5 are the demo. Job 6 is the product: without it you are not running preview environments, you are running a slow-motion resource leak with URLs.

The DIY build on a Cluster-API fleet you already own

If you already operate a Cluster-API-managed fleet, every one of the six jobs maps to a boring, well-documented component. This is the recipe real teams run in production:

Isolated scope: one namespace per PR. A CI workflow or an operator (open-source previewd, ArgoCD's pullRequestGenerator, or a small in-house controller) creates preview-pr-<number> on PR open and deploys one Helm release or Kustomize overlay into it. Tuist's public write-up of its own preview platform follows exactly this shape: each preview is one Helm release in its own namespace running the full embedded stack. Nothing here is exotic — it is the same manifests you already ship, parameterized by PR number.

Ephemeral DNS: one wildcard record. Point *.preview.example.com at the ingress IP once — managed by ExternalDNS from the ingress Service annotation so it survives cluster rebuilds — and every PR hostname resolves from birth. No per-PR DNS calls, no zone-file toil, no propagation wait.

TLS: one wildcard certificate at cluster bootstrap. Issue *.preview.example.com a single time via cert-manager with a DNS-01 challenge, and have every per-PR ingress pick it up through ingress-nginx's default-SSL-certificate flag. This sidesteps the trap teams hit around week two: requesting a fresh Let's Encrypt certificate per PR slams into production rate limits fast, since preview hostnames churn by design. The standard fix is a staging issuer for previews or, better, the single wildcard — one certificate, zero per-PR issuance.

Per-PR data: pick your fidelity. Three rungs, ascending cost: a schema-per-PR inside one shared Postgres for stateless-ish apps; a fresh small Postgres per namespace (the Tuist shape — full fidelity, real resource cost); or cloned production snapshots for the teams debugging data-dependent bugs. Most teams start on rung one or two and only pay for three where it earns its keep.

PR images: build on push, deploy on change. Your existing CI builds the branch image; the preview release tracks the branch SHA and redeploys on each push so the URL is stable across iterations (pr-1234 stays put while the code underneath moves).

Garbage collection: delete on close, sweep on schedule. A GitHub workflow on pull_request: closed runs kubectl delete namespace preview-pr-<number>, and namespace ownership does the rest — Tuist's refactor made preview-owned CRs children of the namespace so deleting it garbage-collects the StatefulSets, PVCs, Services, and Certificates in one shot. Then add the backstop the demo skips: a TTL sweeper that destroys environments whose PRs went stale with no commits and no close event, because abandoned PRs are where the leak lives.

Total new machinery: ExternalDNS plus a wildcard record, one ClusterIssuer and one wildcard cert, a PR-open/PR-close workflow pair, and a TTL sweeper. If you run Cluster API plus Flux or ArgoCD already, that is genuinely a two-to-four-week build for one platform engineer — which is where the $18,000 year-one figure comes from.

The buy side: what the vendors actually sell

The vendor shelf splits along three lines that matter more than any feature grid: scope (frontend-only vs full-stack), data story, and whose infrastructure it runs on.

VendorScopePer-PR dataRuns onPricing shape
Vercel / Netlify previewsFrontend only — backend services and database state are not isolated per previewShared / production-adjacentVendor cloudBundled with seat plan + overage meters
Render preview envsFull copy of services per PR from render.yaml, optionally with a databaseCloned per previewVendor cloudBilled as normal services, prorated per second
Railway PR envsFull-stack, "focused" — deploys only services whose code changedPer-previewVendor cloudPay-as-you-go meter
BunnyshellFull-stack, auto-created per PR (or per comment, or API), destroyed post-mergeTemplate-driven clonesYour cloud or theirsFlat price per active instance
UffizziFull-stack on Kubernetes multi-tenancy and virtual clusters; open-source coreTemplated per previewYour cluster or theirsPlatform fee + compute
NorthflankFull-stack with isolated backends and databases per PR; bring-your-own-cloudPer-preview, production-likeYour cloud account (BYOC)Project billing, no per-service fees

Three observations fall out of that table. First, frontend-only previews (Vercel, Netlify) are a different product wearing the same name: if the bug you need to catch lives in the API or the migration, a frontend preview cannot show it to you. Second, Render's per-second billing and Railway's focused deploys both attack the same cost — idle environments — from opposite ends: meter precisely, or don't boot what didn't change. Third, Northflank's BYOC and Uffizzi's run-on-your-cluster options concede the premise of this whole post: at some point teams want the preview logic without renting someone else's computers to run it on.

The worked math, line by line

Assumptions, all stated so you can substitute your own: a team running 4 services, averaging 15 concurrently open PRs, each environment living 2 days; a loaded engineering rate of $150/hour; preview workloads bin-packed onto fleet nodes with spare headroom.

DIY year one: ~$18,500. Three engineering-weeks to build (PR workflows, wildcard DNS/TLS, per-PR data, TTL sweeper, dashboards): 120 hours × $150 ≈ $18,000 one time. Marginal compute: ~15 concurrent envs × 4 services at idle-preview sizing fits on roughly one spare node or existing headroom — budget one small box or effectively $0 on a fleet with slack; call it $500 for the year to stay honest. Maintenance in year one is absorbed by the builder still being around.

DIY year two onward: ~$7,700/year. Half a day a month of maintenance (sweeper tuning, issuer renewals, version bumps): 48 hours × $150 ≈ $7,200, plus the same ~$500 in compute.

Vendor: ~$9,000/year at 15 concurrent envs, every year. 15 × illustrative $50 per active full-stack environment per month × 12. The per-env figure varies by vendor and workload — check current price pages — but the shape is what matters: it recurs annually and scales linearly with PR concurrency.

The delta: the vendor is ~$9,000 cheaper in year one and roughly at parity in year two; from year three the build pulls away, and every additional concurrent PR widens the gap in DIY's favor because the fleet's marginal env cost is near zero while the vendor meter ticks per env. Solve for the crossover and the breakeven sits near 20 concurrent environments, or about 18 months at 15 — which is exactly why the sensitivity variable from the top (concurrent PRs × lifetime) is the whole decision.

Neither side's price page shows the hidden costs, and they are symmetric. Orphaned environments bill you on a vendor meter and rot on your own fleet — the TTL sweeper is non-optional in both worlds (vendors call it auto-sleep or auto-stop; same job). Production-data cloning is a fidelity win and a compliance event: a per-PR snapshot containing real user rows inherits your backup's access controls or it inherits your incident.

Parity drift is the quiet killer: a preview that doesn't track production's versions, flags, and seed data teaches reviewers to distrust green previews, and trust, once lost, sends everyone back to the staging queue you just demolished. Budget a day a quarter for parity, wherever the envs run.

The decision rule

  • Buy when you have no platform time. If nobody owns your infrastructure week to week, a vendor's per-env meter is cheaper than a build nobody maintains — an unmaintained preview platform rots into the staging queue with extra steps.
  • Build when you already run a fleet with headroom. If Cluster API plus GitOps is already your world, the six jobs are weeks of work on components you operate anyway, and every PR past breakeven rides capacity you already pay for.
  • Never pay rent on capacity you own. A vendor meter sitting in front of infrastructure you already operate charges you per environment for orchestrating computers that are already yours. That is the configuration to avoid: BYOC or your own controller for the orchestration, your nodes for the compute, one bill that doesn't grow new line items.

Preview-per-PR won because the staging queue was the bottleneck nobody had named — Release's 12-of-42 simulation just put the number on it. Whether you rent the environments or build them on machines you own, the requirement is the same six jobs with the sixth one non-negotiable. Teams that internalize that end up in the same place Humanitec's top performers already are: developers spinning up their own environments, no ticket, no queue, merged by Friday.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Preview environments are a natural primitive on that kind of platform: namespace-scoped deploys with ephemeral DNS, wildcard TLS, and delete-on-merge are exactly what a git-push PaaS on your own fleet does well. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex