The first product
This page is the how-to for a team’s first product on Airframe. It names the file you write at each step, who must approve it, and what to watch while the platform builds the rest. It follows the same path as a product’s life, one level closer to the keyboard, and it assumes the product’s domain and context already exist.
The example is a new product of the demo organization’s store team, giftcards, in the shop/store/ordering context.
1. Write the entry
Section titled “1. Write the entry”Where: the catalog repository, one new file named by the product’s path, products/shop.store.ordering.giftcards.yaml.
product: giftcardsjourney: shopdomain: storecontext: orderingteam: store-corecriticality: 3data_classification: internalrto: 4hrpo: 1hmonthly_budget: 10template: fullstackstack: nodeslo: { availability: 99.0, latency_p95_ms: 400 }sandbox: truelifecycle: { status: active }What a product file holds explains each field. Three of them choose what you get: template: fullstack asks for a repository rendered from the golden path’s template, stack picks its application (node, python-fastapi or java-spring), and sandbox: true adds a permanent environment for the product’s consumers.
Before you open the merge request, check what the catalog’s lint will check:
- the team exists and lives in this context, and at least one person who may merge on the product has a GitLab account;
- a criticality 1 or 2 product’s team has two members with an on-call role;
- the name has 2 to 10 characters, is unused in the domain, and was never used by a removed product;
- the budgets of all products and playgrounds still fit the organization’s monthly budget in
catalog.yaml.
Run npm run lint in a clone of the catalog to see the same messages on your laptop.
2. Open the merge request and get the approvals
Section titled “2. Open the merge request and get the approvals”Who approves: one person from the product’s team, the context’s leads or the domain’s leads, who is neither the author nor a committer, and a domain lead, because a new entry’s budget counts as a raise from zero. One domain lead’s approval covers both. The gate checks those approvals against the catalog’s ownership model and adds its own, which security-policies’ approval policy requires before the catalog can merge (enforced).
Some fields bring in other approvers:
- a
monthly_budgetabove zero, which every new entry has, and any later raise, needs a domain lead; - a subscription in
consumesneeds an owner of the publisher’s product, unless the topic is marked as inner source; - a target in
callsneeds an owner of the called product; - a service in
servicesneeds a domain lead.
3. Watch the progress note
Section titled “3. Watch the progress note”After the merge, the catalog’s pipeline starts the chain of reconcilers and a job, progress:track, that writes one note on the merged merge request and keeps it up to date. The note shows each pass of the chain with its time and link, then the new repository, its first pipeline, and the moment the hostname answers. If something fails, the note names the failing job and the next step. The job stops when the product is live or has failed, or after three hours, and says which.
In the platform’s rehearsal of 2026-09-20, the chain took about thirty minutes from the merge to the repository.
4. Bill it
Section titled “4. Bill it”Where: landing-zone, the domain’s project factory: add the product’s name to billed_products in roots/project-factory/shop-store/variables.tf.
Who approves: the platform team.
The product’s two projects exist without billing, with their identities and permissions. Until they are billed, its pipeline plans for real and its apply stops at the first service that needs billing. Billing is kept apart because the billing account can link only so many projects, so taking two more is a decision of its own.
This step can come first. In the rehearsal, the billing merge request merged three minutes before the catalog entry, so the product’s first pipeline applied straight away. If you bill afterwards, follow Cost and billing, steps 3 to 5. The billing merge’s own pipeline applies only the factory root; the network root attaches the projects to the shared network only on a later landing-zone pipeline, which someone allowed to run pipelines on landing-zone’s main starts on demand. Start a new pipeline on the product’s main only after that one has applied: the people allowed to merge on the product may.
5. The first deploy
Section titled “5. The first deploy”Nobody approves this step separately: the repository’s first commit is rendered by the platform from the entry, and the catalog merge request is its approval. The gate admits it as a product repository’s first commit generated from the catalog.
That commit’s pipeline runs on main like every later one. build:request has the trusted builder build the images, and the pipeline plans each root as plan:<root>, checks each plan with policy:<root>, and applies it with apply:<root>: non-production first, then the sandbox, then production with the promoted image. grafana:reconcile and edge:reconcile then ask the gate to run the operations and landing-zone pipelines, which place the product’s dashboards and its hostname on the shared edge.
The product answers at giftcards.store.shop.projectairframe.dev, behind single sign-on for its users group. In the rehearsal, that happened about eighty-five minutes after the catalog entry merged; creating the databases took most of it.
6. The first change
Section titled “6. The first change”Where: the product’s own repository, projectairframe/shop/store/ordering/giftcards.
- Clone it, make a branch, change something in
apps/or a root ininfra/, and runairframe validate. - Open a merge request. Its pipeline runs on the untrusted runner with no cloud credential.
- Who approves: a person the ownership model names for what changed, other than the author: the team for
infra/; the team or the platform team for the pipeline file and.airframe/. - Read the plans and the cost the executor posts, and try the preview at the merge request’s own hostname.
- Merge. The main pipeline applies.
A change’s path follows these steps in detail.
Until two more merge requests land, the gate’s approval is not yet required on the new repository: only the approval of a named person who did not write the change. Where: control-plane’s gate/infra/terraform.tfvars, which adds the repository to ownership_enforced, then security-policies’ .gitlab/security-policies/policy.yml, which adds it to the scope of the required ownership gate policy. Who approves: the platform team in control-plane; a maintainer who did not write it in security-policies. Today the second person in each case is the maintainer’s second account: the mechanism is enforced, but the separation between two people is not yet real.
Where it lives
In the landing-zone repository:
- docs/guides/greenfield.md: a new product, from the entry to the hostname
- docs/guides/product-lifecycle.md: every reconciler, and what waits for billing
In the catalog repository:
In the control-plane repository:
