The Anyport Team
The hard part of a preview environment is the database
Running a branch on its own URL is the easy half. What the branch talks to is the half that decides whether previews are useful, expensive, or an incident. The four answers and what each one costs.
A preview environment per pull request is a well understood idea. Open a pull request, get the branch running on a URL, review the thing rather than the diff. It is also easy to build, right up to the moment the branch touches a database. Everything before that point is container scheduling. Everything after it is a set of trade-offs nobody can pick for you.
Why the stateless version was solved a decade ago
Frontend preview deploys work because a built bundle has no state. Build the branch, serve it, throw it away. There is nothing to isolate, nothing to seed, and nothing that can leak. When the change under review is a component, that is a complete preview and the problem is genuinely finished.
The moment the change is a migration, a queue consumer or a webhook handler, the preview has to include the thing that holds state. The question stops being whether you can run the branch and becomes what the branch runs against. That question has four answers, and the interesting part is that none of them is right in general.
A fresh, empty instance
The preview gets its own database with its own credentials. This is the only answer under which the preview is genuinely isolated: a half finished migration cannot reach real data, because it has no route to it.
What it costs is that the database is empty, and an empty database is a weak test. Somebody has to get a schema into it, which is what a pre-deploy step is for: a command that runs with the incoming image before the new version serves anything, so in a preview it runs before anyone opens the URL. Even then, an empty schema hides the bugs that only appear against realistic volume or an awkward row that has been in production since 2019.
Restored from a recent backup
The preview still gets its own instance and its own credentials, but the base project's most recent backup is restored into it. It is isolated exactly like the fresh option and it is realistic, which catches a substantially larger class of bug in review.
The cost is worth stating plainly, because it is easy to enable and hard to notice: you have put a copy of production data into an environment that was created automatically when somebody opened a pull request. That is a defensible decision for some services and a bad one for others, which is why it should be chosen per service rather than switched on everywhere. It also quietly depends on your backups working, which is a good thing to find out here rather than during an incident.
Pointed at the shared instance
The preview's apps talk to the base project's database. It is the cheapest option and immediately realistic, and it is the one that looks convenient on the settings page and turns out to be an incident.
The branch under review is now a second writer on real data. A migration in that branch runs against real data. The reviewer opening the URL is exercising code that has not been merged, against the rows your customers are using. There are services where this is fine and services where it very much is not, and the difference should be visible in the pull request rather than discovered afterwards.
Skipped entirely
The preview gets nothing for that service. Underrated, and the right answer more often than people expect. Previews are not free, and copying a search cluster into every pull request to review a change to a stylesheet is a way of making previews expensive enough that somebody eventually turns them off.
The parts teams find out later
Three things tend to arrive after the feature does.
A useful preview holds real credentials. A preview with blank secrets previews nothing, so a working one carries a copy of the project's configuration, values included. That makes pull requests from forks a security boundary rather than a checkbox: a fork pull request is code written by someone outside your repository, and building a preview of it means running that code inside your infrastructure next to those credentials. Off by default is the only defensible default, and turning it on should be a decision about the repository rather than a convenience.
They accumulate. One preview per open pull request on a busy repository will fill a cluster. That needs an expiry for pull requests that have gone quiet and a cap on how many can exist at once. On hardware you own the pressure shows up as pods that will not schedule, which is a worse error message than a bill.
Deletion is the feature. Environments that are created automatically and removed manually stop being previews and become clutter with URLs. Removal when the pull request closes is not a tidiness detail, it is the thing that makes the whole idea sustainable.
What the reviewer actually needs to see
The URL is not the whole product. A reviewer needs to know which commit is being served and whether the build that was meant to produce it succeeded.
The failure worth designing for is the quiet one. A build fails, the URL keeps answering, and it answers with the image the preview was created with. A reviewer clicks it, sees the old behaviour, and concludes the change does nothing. That is worse than having no preview at all, because it produces a confident wrong answer. Whatever surface reports the preview has to say outright when the URL is serving something other than the branch.
How this works in Anyport
Previews copy the whole project, not one container: apps, managed services, secrets, routes and tasks, deployed from the pull request's own commits. Each managed service is set independently to fresh, seeded from a backup, shared, or skipped, with fresh as the default, and whichever you chose is named in the pull request comment.
Anyport posts one comment and edits it in place as commits land, listing each app with its URL and build state. When a build fails, that comment says the URL is still serving the image the preview was created with rather than this pull request's code. Previews expire after a week without a push, are capped per project, and are deleted when the pull request closes. Fork pull requests are refused unless you allow them.
They run on the same cluster as the project they copy, which has one useful consequence: there is no per-preview or per-minute charge, because the machine is already yours. The constraint is your hardware instead, so the console adds up what one preview will reserve before you switch them on.
The documentation covers the settings and the failure modes in detail. The decision worth making before you touch any of it is the one this post is about: for each service in the project, what should a pull request be allowed to talk to.