Customer Story How Bitso cut change failure rate 83% while scaling delivery with coding agents

Per-PR Preview URLs, Explained

A per-PR preview URL is a link, posted on the pull request, that opens a running version of the application with that change included. Anyone with the link can try the feature before it merges, without pulling a branch or building anything. Frontend platforms made this a default expectation, and the question most teams face is how to get the same experience for services running on Kubernetes.

The short answer is that the URL is the visible end of an environment strategy. For a frontend, the environment is a self-contained build artifact, so a URL per PR is nearly free. For a backend service, the PR’s code is one node in a graph of services and datastores, so the URL has to lead to a place where that whole graph exists. The approaches below differ mainly in how they produce that place.

The frontend version everyone knows

Vercel and Netlify build every pull request and comment a unique link on it, using URL schemes like a per-PR subdomain. The pattern works because the deliverable is a static or serverless bundle. Each build is complete in itself, hosting is a content delivery network (CDN) push, and a thousand previews cost little more than one.

That mental model is what most searchers arrive with. It sets the right expectation for the workflow, a link on every PR that product managers and designers can click, and the wrong expectation for the mechanics once real backends are involved.

Why the same idea is harder for a backend

A backend pull request has nothing to show by itself. To render a page or serve an API response, the changed service needs its upstream callers, its downstream dependencies, a database with plausible data, and often a message queue. The unit that must exist behind the preview URL is the system, not the artifact.

Frontend previewPull requestBuild artifactCDNOne URL per PRBackend previewPull requestService graphChanged serviceServiceQueueDatabaseOne URL, full graph
A frontend preview points at one artifact. A backend preview has to point at a whole graph of services.
Frontend previewPull requestBuild artifactCDNOne URL per PRBackend previewPull requestService graphChanged serviceServiceQueueDatabaseOne URL, full graph
A frontend preview points at one artifact. A backend preview has to point at a whole graph of services.

That leaves two basic strategies. Either you stand up a copy of the system for every pull request, or you share one live system and arrange for the PR’s requests to hit the changed service. Everything else in this article is a variation on those two moves.

Two ways to give a pull request its own URL

Environment per PRpr-123.preview.example.comNew namespaceFull stack copyHeader-based routingX-Preview-Key: pr-123Shared clusteronly changed services deployed
Duplicate the whole stack per PR, or deploy only the changed services and route requests to them, virtualizing the full stack for the preview.
Environment per PRpr-123.preview.example.comNew namespaceFull stack copyHeader-based routingX-Preview-Key: pr-123Shared clusteronly changed services deployed
Duplicate the whole stack per PR, or deploy only the changed services and route requests to them, virtualizing the full stack for the preview.

A full environment per PR, one subdomain each

CI or a GitOps controller deploys the entire stack into a namespace per pull request, and a wildcard Domain Name System (DNS) record plus an ingress rule map pr-123.preview.example.com to it. Tools like ExternalDNS automate the records. This is the backend equivalent of the frontend pattern. Okteto and Release sell managed versions of it, and the Argo CD ApplicationSet pattern is the common way teams build it themselves.

It is conceptually clean because each preview is fully self-contained. Nothing is shared, so there are no routing rules to reason about, no header propagation to verify, and no way for one preview to interfere with another. Debugging an environment means looking at one namespace. But it gets expensive in proportion to system size and PR volume, since every open PR carries a full copy of every service, sitting mostly idle. Twenty open PRs against a thirty-service stack means six hundred running containers before anyone clicks a link. Each of those environments is also something the platform team operates: seeding its data, keeping its config current, and answering for it when a preview comes up broken. Spin-up time grows with the stack too, since the preview is not ready until the slowest service and its data are, and once previews take minutes to build, teams start rationing them to the risky changes, which quietly defeats the point of a preview on every PR.

Header-based routing on a shared cluster

The alternative keeps one stable environment running and deploys only the services a PR changed, whether that is one service or several, as isolated versions alongside their stable counterparts. Requests that carry the PR’s routing key in a header are routed to the changed services at each hop, and fall through to the stable versions of everything else. One link virtualizes the full stack for that PR without deploying it. The preview URL is a hosted address that injects the routing key on every request, so clicking the link is all a reviewer does differently.

This is the architecture behind Signadot’s Sandboxes, lightweight ephemeral environments that hold the changed service inside the shared cluster. Each Sandbox endpoint gets a hosted preview URL that injects the routing key automatically and authenticates through the control plane, so reviewers signed into the dashboard open it in a browser while automation uses an API key (see the preview URL reference).

The biggest win is developer experience. A preview is ready as soon as one container starts, in seconds, and that stays true whether the stack has five services or three hundred, because nothing else needs to be deployed. Every preview also exercises the real, current versions of every dependency rather than a copy that may have drifted. Fast, dependable links are what keep reviewers and product managers clicking on every PR instead of saving previews for the risky changes.

The operational and cost profile follows from the same property. The platform team keeps one baseline environment in sync with the main branch instead of operating a fleet of full copies, each needing data seeding, config updates, and an on-call answer when it comes up broken. And each preview costs one service instance rather than one system copy; this walkthrough of cost-efficient preview environments shows how that changes the bill in detail. The prerequisite is that services propagate request headers across hops, which teams with distributed tracing already do.

The same preview URLs verify behavior for web and mobile applications alike, including mobile and frontend previews driven by backend changes. They also pair with frontend platforms, shown in this Vercel plus Kubernetes preview tutorial.

A preview URL on every PR, without duplicating the stack

Signadot gives each pull request a hosted preview URL backed by a lightweight sandbox on your shared cluster, so reviewers click a link and see the change running against real dependencies. The free tier is open to every developer.

Start for free

Access, data, and cleanup

The people a preview URL is for usually cannot run kubectl, so access has to work at the browser level. The common answers:

  • single sign-on in front of the preview domain
  • short-lived tokenized links
  • network placement behind the corporate virtual private network (VPN)

Managed platforms handle this at their control plane, authenticating the hosted preview URL against the user’s existing session. Whichever you choose, the test is whether a product manager can open the link from the PR with zero setup.

Data matters as much as access. A preview backed by an empty database demos nothing, so environments either share the stable system’s data through the routing approach, or seed fixtures on creation in the duplication approach. Shared data with isolated writes is its own topic, and the preview environments guide covers the options in depth.

Cleanup is the part teams forget until it shows up on the cluster bill. Preview environments should tear down when the PR merges or closes, with a time-to-live backstop for anything that leaks. In the routing model, cleanup is deleting the ephemeral environment. In the duplication model, it is deleting a namespace and its volumes.

Wiring the URL into the pull request

The URL only changes behavior if nobody has to go looking for it. The standard wiring is a CI step that creates the preview and comments the link on the pull request, GitHub Actions and GitLab CI both make this a few lines, and many teams also post it to the ticket and the team channel. Some add a commit status or check run that links to the preview, so the URL sits in the merge box itself.

Two small details pay off:

  • Pin the comment, or edit one comment in place per PR, so reviewers are not scrolling past a stack of stale links after every push.
  • Include the environment’s state in the comment, creating, ready, or failed, because a link that 502s for the first three minutes teaches stakeholders not to click.

The lifecycle, end to end

Step by step:

  1. A developer opens a pull request.
  2. CI builds the image and creates the preview, a full environment copy or a routed ephemeral environment.
  3. A bot comments the URL on the PR, within seconds to minutes depending on the approach.
  4. The reviewer clicks the link to sanity-check behavior, QA runs through the change in isolation, and the product manager confirms it matches intent, all before merge.
  5. New commits update the same preview, so the URL never goes stale.
  6. On merge, the environment is torn down automatically, with nothing left to clean up by hand.

Reviewers are not always human. Coding agents can open the same preview to run end-to-end tests against the change before anyone looks at it, which makes the preview URL the validation surface for agent-written code as much as for human review.

The preview lifecycle1PR opened2CI createspreview3Bot commentsURL4Team clickslink5Fix updatesURL6PR mergesenv removed
The URL is created once, stays valid through every push, and disappears when the PR closes.
The preview lifecycle1PR opened2CI creates the preview3Bot comments the URL4Reviewer, QA, PM click it5Fix updates the URL6PR merges, env removed
The URL is created once, stays valid through every push, and disappears when the PR closes.

Choosing between the approaches

The choice is really about a tipping point. A team with a small stack and moderate PR volume has no bottleneck to fix. A namespace per PR is the simpler approach, cost and spin-up time are tolerable at that scale, and the whole mechanism stays easy to reason about. There is no reason to add a routing layer before the economics demand one.

The calculus changes as the team and the service graph grow. When spin-up time gets long enough that stakeholders stop clicking, when the platform team spends its weeks keeping preview copies alive instead of improving the platform, or when the preview bill becomes a line item that needs explaining, duplication has hit its ceiling. At that point header-based routing stops being an optimization and becomes the requirement for PR previews to keep scaling: previews stay seconds from ready and at the cost of the changed services, no matter how large the stack grows or how many PRs are open.

Both deliver the thing that changes how teams review, a URL on every pull request that anyone can click, and the preview environments solution page covers what the managed version of that workflow looks like.

Frequently asked questions

What is a per-PR preview URL?

A per-PR preview URL is a link, posted on the pull request, that opens a running version of the application with that change included. Anyone with the link, a reviewer, QA, or a product manager, can try the change before it merges without pulling a branch or building anything locally.

Why are preview URLs harder for backend services than for frontends?

A frontend preview deploys one self-contained build artifact to a CDN, so a URL per PR is nearly free. A backend change has nothing to show by itself: it needs its upstream callers, downstream dependencies, a database with plausible data, and often a message queue. The preview URL has to lead to a full working system, not a single artifact.

What are the two ways to give a pull request its own preview URL?

Either duplicate the environment per PR, deploying the full stack into its own namespace behind a per-PR subdomain, or keep one shared baseline environment, deploy only the changed services, and route requests carrying the PR's routing key to them at each hop. Duplication is simpler at small scale; header-based routing keeps previews ready in seconds, spares the platform team from operating a fleet of environment copies, and keeps cost flat as the stack and PR volume grow.

Stay in the loop

Get the latest updates from Signadot

Validate code as fast as agents write it.