Skip to content

Cost and billing

On Airframe, every product has one monthly budget, written once in its catalog entry, and a change that would cost more than that budget cannot merge. This page is the how-to for living with that rule: reading the price a merge request carries, raising the budget when the product needs it, putting a product on the billing account or taking it off, and watching what it really spends. The reasons are on the FinOps page.

Where: the catalog repository, the product’s entry, products/<journey>.<domain>.<context>.<product>.yaml, field monthly_budget.

The figure is in US dollars a month, production and non-production together. The catalog’s lint checks that the budgets of all products and playgrounds still fit the organization’s monthly budget in catalog.yaml. Everything else reads the entry: the cost rules, the cost dashboard’s thresholds and the Google Cloud budgets on the product’s projects.

When an owner approves a product’s merge request, the executor plans each of the product’s roots, infra/np, infra/prod and infra/sandbox, prices each plan with Infracost, and posts one note per root. The cost lines of a note look like the illustration below, which puts the figures of the platform’s recorded rehearsal of the cost check, a small database against a budget of USD 20, in today’s format:

Cost: USD 27.27 a month, from USD 0 (delta USD 27.27,
no base to compare); budget USD 20. 5 usage-based and
3 unsupported resources are not in the number.

Below them come the resources whose monthly cost changed, and the cost rules’ verdict:

  • WARN when the change adds more than USD 10 a month or more than 25 percent. It is meant to need the platform team’s approval, which is designed: today it holds nothing back.
  • FAIL when that root alone is above the budget.
  • Cost: not estimated when the plan could not be priced. The change is not admitted without a price.

The merge request also carries a status check named “Airframe cost”, which the gate sets once every root’s plan has finished. It passes only when every root was priced and their sum is within the budget. It fails when one root is refused, a plan failed, a root has no price, or the sum is above the budget. Until it passes, GitLab keeps the merge closed (enforced: the project’s setting that status checks must succeed). The refusal of a sum names the total, each root’s share and what is left out; it is recorded on the plan runs and printed in the executor’s job log, while each note gives one root’s figure.

When the check fails on cost, there are two ways out: make the change smaller, or raise the budget.

  1. Where: the catalog repository. Change monthly_budget in the product’s entry, in a merge request.
  2. Who approves: a domain lead of the product’s domain, because the figure rises, plus an owner of the entry (the product’s team, the context’s leads or the domain’s leads) who is not the author. One domain lead’s approval covers both. Where the domain’s leads have no GitLab account, the platform team stands in. Then the gate adds its own (enforced). Today those approvers are the maintainer’s own accounts: the mechanism is enforced, but the separation between two people is not yet real.
  3. If the lint says the products no longer fit the organization’s budget, raising that budget in catalog.yaml is a separate change, which the platform team approves twice; today those are two of the maintainer’s own accounts.
  4. Once the catalog merge request has merged, approve the product’s merge request again. The new approval plans and prices every root against the new budget, and the check settles again.

The executor reads the budget from the catalog’s entry on main, never from the copy inside the product’s repository.

A new product’s two projects exist without billing, with their identities and permissions. Its pipeline plans for real, and its apply stops at the first service that needs billing.

  1. Where: landing-zone, the domain’s project factory, roots/project-factory/<journey>-<domain>/variables.tf. Add the product’s name to billed_products.
  2. Who approves: the platform team, then the gate.
  3. The merge’s own landing-zone pipeline applies the factory root alone, because a push to main applies only the roots whose directory changed. The factory enables the product’s services and creates a Google Cloud budget on each project from monthly_budget: 70 percent for production and 30 for non-production, with alerts at 80 and 100 percent.
  4. The network root attaches the projects to the shared network on a later landing-zone pipeline. For a new product whose catalog entry merges after the billing, the catalog’s chain of reconcilers runs it, as in the platform’s rehearsal of 2026-09-20. Otherwise run landing-zone’s pipeline on main on demand. GitLab lets only the people allowed to merge on a protected main run a pipeline there: for landing-zone, the platform team’s developers and leads with a GitLab account, and the administrator account. Today those are the maintainer’s own accounts.
  5. Start a new pipeline on the product’s main. The people allowed to merge on the product may. It applies.

The project quota. The billing account can link only so many projects, and each product takes two. At the time of writing the quota is fourteen, and the platform’s own projects use nine. Raising it is a request to Google, filed by hand by the administrator account through a form in the Google Cloud console, and recorded with Google’s answer in landing-zone’s docs/bootstrap-log.md.

The order matters, because Google Cloud refuses to detach a project while anything in it still uses the shared network.

  1. Stop the product’s runtime first. The landing-zone product lifecycle guide gives the way to stop a running product without removing it: retire it, in a catalog merge request that sets its lifecycle to retiring with a retire_after far away. That merge request carries the lifecycle label and needs two approvals: one from the team and one from a domain lead or the platform team; today both come from the maintainer’s own accounts. The retirement scales its services to zero and withdraws its routes.
  2. Wait. Cloud Run releases the network addresses its services reserved up to two hours after they stop, and until then the projects cannot leave the shared network.
  3. Where: landing-zone. Remove the product from billed_products, in a merge request the platform team and the gate approve. Its own pipeline applies the factory root alone.
  4. Have someone allowed to run pipelines on landing-zone’s main (section 3, step 4) run two there on demand: in the first, the factory lets the projects go and the network root detaches them; in the second, the factory unlinks billing and drops the budget. The daily schedule only plans and never applies.

The projects, identities, secrets and keys stay; the hostnames stop answering. The pipeline’s plans still run and its applies fail at the first service that needs billing. While the product’s Terraform state still holds resources that need billing, its plans fail and the cost check refuses every merge request.

The cost dashboard. Each product’s folder in Grafana has <product> cost: the month to date and the forecast against monthly_budget, orange at 80 percent and red at 100. A daily job, cost:report, reads the billing export and sends each product’s cost as metrics. Its figures are what Google bills, net of credits. The platform folder’s platform cost holds the organization against its budget. See Observability.

The Google Cloud budgets. Each billed product project has a budget from monthly_budget. Today their alerts go by email to the billing account’s administrators and users, Google’s default; reaching the product’s team is designed. The billing account has its own budget of USD 150 a month, alerting the platform group at 50, 80 and 100 percent.

What is outside the merge request’s figure. The preview, which is temporary; usage-based resources, such as requests, Cloud Run time, backups and log data; and resources Infracost does not price. The note counts the last two. The platform’s shared costs, such as the edge and the runners, belong to no product. The dashboard shows what was really billed.

No demo product is billed today, so none has cost series or project budgets yet.

Where it lives

In the control-plane repository:

In the landing-zone repository:

In the operations repository:

MIT license. Copyright Airframe contributors.

This site describes platform release 2026.10.20-1.