Lesson 6 · Build, deploy & trunk-based delivery

Trunk-based deployment

The tbd.* model — from a single shared branch to a deploy in every env×org cell.

Your win: explain trunk-based development, read this repo's full deploy chain (the thing a lot of people get wrong), and see how one deploy fans out across the env×org matrix with an authorization gate.

Trunk-based development — the model CI/CD is built for

In trunk-based development, everyone integrates into one shared branch (the trunk, here develop) in small, frequent changes behind short-lived branches.1 No long-lived release branches drifting apart. That's why the deploy workflows are prefixed tbd.trunk-based deploy.

The full chain (the part people get wrong)

You met this in Lesson 1; now the anchors. A merge does not deploy — a release tag does, and a cron picks it up:

1. push to `develop` └─ tbd.create_release_tag.yaml → cuts a release tag (on: push) 2. cron every 2h, Mon–Fri └─ tbd.auto-deploy-staging.yml → gets latest trunk tag └─ `gh workflow run tbd.build.yaml ... auto_deploy=true` 3. tbd.build.yaml → build image (Lesson 5) └─ dispatches deploy 4. tbd.deploy.yml → deploy per org (Lesson 8)
Anchor — the cron and the dispatch .github/workflows/tbd.auto-deploy-staging.yml:5-7schedule: cron: "0 0-13/2 * * 1-5" (every 2 hours, Mon–Fri). It fetches the latest trunk tag and runs gh workflow run tbd.build.yaml … {"be_release_tag": "…", "auto_deploy": "true"} (:45). So staging deploys on a timer, not on merge. Production is a manual workflow_dispatch of tbd.deploy.yml.
Why a cron, not on-merge? Deploying on every merge would mean many deploys an hour, each a rollout across the whole matrix — noisy and expensive. Batching into a 2-hourly window groups several merges into one staging deploy: fewer rollouts, a clear cadence, and a natural "these changes went out together" boundary. It's a deliberate throughput/latency trade-off, not a limitation.

Fan-out: one deploy, every env×org cell

Anchor — the org matrix A deploy targets one environment (staging / uat / preproduction[=dorp] / production / trial) across many orgs. The deploy-k8s job (.github/workflows/tbd.deploy.yml:360-390) uses a strategy.matrix.organization over {manabie, jprep, tokyo, synersia, renseikai, ga} (:370-371) — one job per org, in parallel — with per-org concurrency: cancel-in-progress: false (:373-375) so a running deploy is never cancelled mid-rollout. This is the env×org matrix from Course 1, realised as a CI matrix.
Anchor — the authorization gate Not everyone can deploy anywhere. Before the matrix runs, a privileges-check step (tbd.deploy.yml:211-213, the tbd.privileges-check action) verifies the actor is allowed to release to that env/org. Note: this is a custom action, not GitHub's native "Environments + required reviewers" feature — a deliberate choice to encode the org's release policy in code.
Read this next

Trunk-Based Development + GitHub Environments

The branching model, and GitHub's native environment protection (the alternative to the repo's custom gate).

trunkbaseddevelopment.com
docs.github.com — Managing environments

Check yourself (from memory)

Q1. In trunk-based development, everyone integrates into…

One trunk (develop), small frequent merges behind short-lived branches. Hence tbd.* = trunk-based deploy.

Q2. Staging deploys are triggered by…

A cron every 2h (Mon–Fri) builds + deploys the latest trunk tag to staging. Prod is manual dispatch.

Q3. One deploy reaches many orgs via…

strategy.matrix.organization fans one deploy into one job per org, gated by privileges-check.
Trunk-based deploy — the chain, the cron, and the fan-out.
recall, then click to reveal
TRUNK-BASED DEV = everyone integrates into ONE shared branch (develop) in small frequent changes; tbd. = trunk-based deploy. CHAIN (merge does NOT deploy): push→develop → tbd.create_release_tag (cut tag) → 2h cron 0 0-13/2 * * 1-5 (tbd.auto-deploy-staging.yml) gets latest tag → gh workflow run tbd.build.yaml auto_deploy=true → build → tbd.deploy.yml. STAGING = cron-auto; PROD = manual workflow_dispatch. WHY cron: batch merges into one deploy (throughput/latency trade-off). FAN-OUT: deploy-k8s uses strategy.matrix.organization over {manabie,jprep,tokyo,synersia,renseikai,ga}, per-org cancel-in-progress:false (never cancel a live deploy) — the env×org matrix as a CI matrix. GATE: tbd.privileges-check action (not GitHub Environments).
Want to see how uat/prod differ from staging (dispatch, config-clone for dorp), or the privileges-check logic? Ask me.

1. Trunk-Based Development; in-repo .github/workflows/tbd.auto-deploy-staging.yml, tbd.deploy.yml.