Airframe

Why Airframe

A preview for every merge request

Reviewers should be able to try a change, not only read it. Once the right people approve a product's merge request, the platform deploys a preview of the product at a hostname of its own, with a database of its own, and destroys it at the merge, at the close or when its time runs out.

How a merge request gets its preview

  1. The change is approved

    A merge request's own pipeline runs code nobody has reviewed yet, so it holds no cloud credential and deploys nothing. When the gate approves a product's merge request, it starts the executor's runs for that exact commit: the plans, the image build and the preview. A product whose catalog status is retiring gets no preview, since it serves nothing.

  2. The executor deploys it

    The preview run waits up to 25 minutes for the build of the merge request's latest commit, and stops if the build failed. Then, with the product's non-production apply identity, it plans the product's infra/eph root, runs the policy pack on the plan, prices it and applies it. The production identity is never used, so a preview cannot touch production.

  3. Its own hostname, its own data

    The preview answers at <product>-eph-<iid>.<domain>.<journey>.projectairframe.dev, where <iid> is the merge request's number, behind single sign-on for the product's users group. The product's preview router reads the number from the hostname and forwards to that preview's services, which accept calls from the router alone. By default the database is a PostgreSQL sidecar in the same Cloud Run revision, seeded on start from the product's own files.

  4. Reviewers try the change

    The executor posts a note on the merge request with the preview's address, its price and when it expires. Reviewers open it in a browser, where cookies, redirects and links work as in production, and each signal carries its environment, which for a preview is eph-<iid>, so that the product's dashboards can show it on its own: the golden path sets it on the preview's service; the OTLP gateway takes it as the env label, and the platform agent and the logs gateway derive it from the service's name. A new push resets the approvals, and the next approval applies the same preview with the new commit.

  5. Gone at the merge, the close or its time to live

    When the merge request is merged or closed, the gate starts the executor's destroy run, which checks the merge request's state for itself, destroys the preview and posts a note. A preview also expires preview_ttl_hours after its latest apply, 72 unless the catalog entry says otherwise. A daily collector destroys the expired ones, and any that a missed notification or a failed destroy left behind.

A worked example

Merge request 42 of the invoicing product, in the billing domain of the settle journey, is approved, and its entry leaves the preview settings at their defaults. A preview needs the product's projects to be billed, and no demo product is billed today, so this is what would happen; previews ran in the platform's rehearsals in September 2026.

  • The gate approves the commit and starts the executor's plans, image build and preview for it
  • Once the build has passed, the preview run plans infra/eph as invoicing's non-production apply identity, checks the plan against the policy pack, prices it and applies it
  • The note on the merge request gives the address, invoicing-eph-42.billing.settle.projectairframe.dev, the preview's price and its expiry, 72 hours after the apply
  • The database is a PostgreSQL sidecar, seeded on start; the preview's logs, metrics and traces carry env eph-42
  • A push resets the approvals; the next approval applies the same preview with the new commit and a new expiry
  • The merge request merges: the destroy run removes the preview, keeps its empty Terraform state as a record and posts a note

What makes it true

Only an approved change gets a preview, and never with production's identity.
The gate starts the executor's preview run only when it approves a product's merge request, and not for a retiring product; the run applies the product's infra/eph root with the non-production apply identity, after the policy pack has checked the plan. The merge request's own pipeline holds no cloud credential. Because only an approved change gets a preview, someone who can only open merge requests cannot run up the bill.
Each preview has its own hostname, behind single sign-on, and no other way in.
The domain's wildcard certificate and record cover every <product>-eph-<iid> hostname, so nothing is issued per preview. The golden path's preview-substrate module gives each product one router on its non-production project, behind IAP for the product's users group; the router reads the merge request's number from the hostname, and each preview service names the router as its only invoker.
A preview is destroyed when its merge request ends.
The gate learns of the merge or the close from GitLab's webhook and starts the executor's destroy run, which checks the merge request's state itself and destroys with the same non-production identity. Each preview's record carries a lock, so an apply and a destroy never run at once, and a merge request closed and reopened starts a new generation, so a late destroy cannot remove the new preview.
An expired or missed preview is collected.
preview:collect, a job of control-plane's daily scheduled pipeline, lists the previews the executor's records say are still up, checks each merge request's state and expiry, and destroys the merged, the closed and those past preview_ttl_hours (72 by default, at most 168, counted from the latest apply), with a note on each merge request.
A preview costs next to nothing while idle, and never blocks a merge.
The router and the preview's services scale to zero, and the default sidecar database lives in the service's own revision, about 512 MiB more memory while it runs; the golden path's Cloud Run module runs a preview's service as one instance at most. A shared Cloud SQL instance, when the entry chooses one, can be applied only once the product's non-production project is on the platform's shared network, and then costs about USD 9 a month per product. The executor prices each preview's plan and shows it in the note, and the cost check that holds a merge leaves previews out.