Skip to content
On this page

Preview environments

Reviewing a change by reading it works until the change touches a migration, a queue or a third-party callback. A preview environment is that branch actually running: the project it belongs to, copied, deployed from the pull request's own commits, on a URL you can open.

Anyport previews the whole project, not one container. An app without the database it talks to and the configuration that connects them is not a preview of anything.

Turning them on#

Previews need two things that are probably already true: the GitHub App installed on the repository, and at least one app in the project deployed from it.

Then open the project, go to Settings → Preview environments, and switch on Enable previews. The same panel holds the answers that cannot be defaulted for you:

SettingDefaultWhat it does
When to create a previewWhen somebody asksWhen somebody asks creates nothing until a person asks from the pull request or the console; see Asking for a preview. For every pull request creates one as each pull request opens. Projects that had previews before this setting existed stay on For every pull request.
Build pull requests from forksOffSee Pull requests from forks before turning this on.
Skip draft pull requestsOffLeaves a draft without a preview until it is marked ready. Only shown for For every pull request: asking for a preview is past this gate.
Delete after (without a push)7 daysRemoves a preview whose pull request has gone quiet, counted in hours or days. Zero means never.
Most previews at once5Refuses new previews past this many, so a busy repository cannot fill the cluster. Zero means no cap.
Also serve previews on this project's hostnamesOffSee Reaching a preview on your own hostname.

Below the table, the panel asks what each of the project's managed services becomes in a preview, which is Databases and other services below. Above it, the panel states what one preview will reserve on the cluster: the apps and services it copies, and the CPU and memory they ask for. Anyport does not autoscale your hardware, so the cost of the switch is on screen before you touch it.

What happens on a pull request#

Opening a pull request duplicates the whole project onto its own URL. Merging or closing it takes the copy with it.

Opened. On a project that creates previews when somebody asks, Anyport posts its comment and waits: see Asking for a preview. Everything below happens once somebody does, or straight away on a project that creates them for every pull request.

Created. The project is duplicated into a new one named <project>-pr-<number>, carrying its apps, managed services, secrets, configuration, routes and tasks. Its apps build from the pull request's head, and each one that serves traffic gets a generated URL. An app the pull request builds waits for that build rather than starting on the base project's image, so a preview URL never serves the main branch under the pull request's name, and the base project's migrations never run in it first.

Its builds and deploys are filed under the preview itself, so a failed build is visible where you are looking rather than in the project it was copied from.

Anyport comments once. A single comment on the pull request lists each app with its URL and the state of its build, the commit being served, the services the preview was given, and a link into the console. While an app waits for its build or for a service it depends on, the comment says what it is waiting for rather than calling it ready. If a build fails, the comment says the app is not running and links the run that failed.

More commits land. The preview rebuilds, and that same comment is edited in place rather than a new one being posted underneath.

Merged or closed. The preview and everything in it is deleted, and the comment says so. Deleting the base project deletes its previews with it.

Gone quiet. A preview with no pushes for the configured number of days is removed the same way. The clock runs from the last push, not from when the preview was created, and the next push brings it back as it was.

Destroyed. Tick Destroy preview in the comment, or use the bin on its row in the Previews tab, and the preview is removed while the pull request stays open. Unlike a quiet one, it stays removed: a push does not bring it back, and only asking again does.

The project's Previews tab lists the live previews, and Show deleted adds the ones removed in the last seven days, each with why it ended: the pull request closed, it expired, it was deleted by hand, or its base project was deleted.

Asking for a preview#

Most pull requests never need a running copy: a dependency bump, a typo, a refactor the tests already cover. On a project that creates previews when somebody asks, each pull request instead gets a comment that works as a control panel:

### Anyport preview: not running

This project creates previews when asked.

**Apps.** Ticked apps get their own copy, and the ones built from this repository
build this pull request's branch. Unticked apps reuse `shop`'s.

- [x] `api`: built from this pull request
- [ ] `web`

**Services.**

- `postgres`
  - [x] fresh: its own empty instance
  - [ ] copy: its own instance, restored from `shop`'s latest backup
  - [ ] reuse `shop`'s: anything the preview writes lands in `shop`'s data
- `redis`: reuse `shop`'s: anything the preview writes lands in `shop`'s data

This preview would run 1 app and 1 service on `cluster-a1b2`: 500m CPU and 512 MiB.

- [ ] **Create preview**

Tick the apps and pick the service options you want, then tick Create preview. Choosing does nothing on its own, so ticking three apps one after another does not create three previews.

  • Apps are cloned or reused. A cloned app gets its own copy, and builds the pull request if it is built from that repository. A reused app is the base project's running instance, reached by the same name, so http://web:3000 still answers.
  • Services are fresh, a copy of the base project's data, or the base project's own instance, as below. A copy is offered only for a service with backups configured, and reuse only where the preview runs on the base project's cluster. A service with only one possible answer is stated rather than offered.
  • The boxes start from the project's settings, which are the defaults. Something the settings leave out of every preview is never offered.
  • The footprint line is recomputed from your choice, so the cost is on screen when you decide.

Only people with write access to the repository can act through the comment. Anybody else's tick is undone, and nothing is posted in reply. In the console, the project's Previews tab lists the open pull requests without a preview, each with a Create preview button that offers the same choices; that needs permission to change the project.

A pull request opened by a bot, such as Dependabot, gets no comment until somebody asks for its preview from the console.

What a running preview contains cannot be changed from the comment: destroy it and create it again with a different choice. A preview removed for inactivity comes back with the same choice.

This needs the Anyport GitHub App subscribed to Issue comment events. Without it the boxes do nothing, and previews can still be created from the Previews tab.

Databases and other services#

Each managed service in the base project becomes one of four things in a preview. The settings choose the default for each, and a pull request can change fresh, seeded and shared when it asks for its preview:

  • Fresh — the preview gets its own instance, with its own credentials. This is the default, and the only one under which a preview is genuinely isolated: a half-finished migration in a pull request cannot reach real data.
  • Seeded from backup — the preview gets its own instance, with its own credentials, restored from the base project's most recent backup. Isolated exactly like fresh, and holding a copy of that project's data, which catches a larger class of bug in review than an empty schema does. It needs the base service to have a backup destination configured. Without one, or if the restore cannot start, the preview is created with an empty database and the reason is written into the pull request comment rather than the preview failing.
  • Shared — the preview's apps are pointed at the base project's instance instead. Cheaper, and a second writer on the base project's data. The Previews tab flags any service set this way, and the pull request comment names it, so it is visible rather than discovered.
  • Skipped — the preview gets nothing for that service. Useful for something the reviewed code does not touch and you would rather not pay for once per pull request.

A fresh database starts empty. Getting a schema into it is what an app's pre-deploy command is for: it runs with the incoming image before the new version serves, which in a preview means before anyone opens the URL.

A preview runs on the same cluster as the project it copies.

What you can do inside a preview#

A preview is a copy, so the console gives it the verbs that make sense for one. Its apps, services, tasks, secrets, logs, metrics and terminal are all there, which is the reason to open one rather than just click the URL. You can also delete it early, without waiting for the pull request to close.

What it withholds are the project verbs. Its settings are read-only, because the spec is a copy of the base project's and editing it here drifts from the thing under review. It cannot be saved as a template or given previews of its own. Configure the project, and the preview follows on the next push.

A preview can be paused, from Settings → Advanced, to free what it reserves while nobody is reviewing it. The next push to the pull request resumes it. Pausing does not stop the clock: a paused preview with no pushes still expires on schedule.

Secrets in a preview#

A preview holds a copy of the project's secrets, with their real values. That is deliberate, since a preview with blank credentials previews nothing, and it is the reason the fork setting below is off by default. It is worth knowing before you turn previews on for a repository whose pull requests come from people you would not give those credentials to.

Pull requests from forks#

Off by default, and worth leaving off. A fork pull request is code written by someone outside your repository, and building a preview of it means running that code inside your cluster, in a project holding a copy of your secrets. Turn it on only for a repository where you would already have been comfortable with that.

With it off, a fork's pull request can still have a preview, but only once somebody who can change the project approves it from the Previews tab. Nothing on GitHub can approve one, whoever ticks the box: write access to a repository and trust in your cluster are granted by different people. A fork's pull request also cannot choose more of the base project's data than the project's default for each service.

Reaching a preview on your own hostname#

A preview's own URL is enough for a person with a browser. It is not enough for a client whose API address is fixed: a mobile build, a frontend deployed somewhere else, a Postman collection or a test script. Switch on Also serve previews on this project's hostnames and each preview also answers on the base project's hostnames, custom domains included, for any request carrying its routing key:

curl -H 'X-Anyport-Preview: pr-42' https://api.example.com/

The same request without the header still reaches the base project. The key is pr- followed by the pull request number, the same label the preview's own URLs carry, so a build can set it from the pull request number alone. It is also on the pull request comment and the project's Previews tab.

A few things to know:

  • It is an address, not a password. Anyone who knows a pull request number can use the key, just as they can guess the preview's own URL. Protect the app itself the way you already do.
  • Only apps that are cloned and have a hostname are reachable this way. A request enters the preview through the app on that hostname; from there the preview's other apps and services are reached the usual way.
  • A key that matches no live preview is answered 418, with an X-Anyport-Preview-Miss header, never by the base project. A tester whose preview has gone finds out rather than silently testing the old code.
  • The header never reaches your app. It is removed before the request is passed on.
  • Browsers cannot send it, so this is for API clients. Previews of forks never get it.
  • It needs Traefik, the controller Anyport installs and k3s's default, and previews on the same cluster as the project. A cluster Anyport set up on ingress-nginx can switch from its cluster page. The settings panel says when either is not the case.

What a preview is not#

It is not a copy of your data unless you ask for one. A fresh service starts empty, exactly like a project spawned from a template. Seeding from a backup is the deliberate opt-in, and it is worth deciding per service rather than everywhere: it puts a copy of real data in an environment a pull request created automatically.

It is also not a staging environment. It lives as long as its pull request does, and everything in it goes when that closes.

Next steps#