Build and release
How first-party code builds, how its images and charts publish and deploy, how Renovate updates dependencies, and which CI checks run on a pull request.
First-party code lives in apps/ (services), packages/ (shared code and charts) and images/ (base and tool images). GitHub Actions tests it, publishes its images and charts, and opens the pull requests (PRs) that deploy them.
Parts#
| Part | Job | Where it lives |
|---|---|---|
| Bun workspace | Runs lint, typecheck, build and test in each workspace through Turborepo | package.json, turbo.json |
| Image list | Lists each Dockerfile's image under build or ignore, and under deploy the manifests that pin it | .github/containers.json |
| Build entry | An image's watch paths, context, aliases and build options, and the turbo workspace of an image built from turbo prune | <dir>/build.json |
| Image build | Builds each build image and pushes ghcr.io/jonpulsifer/<image> from main | .github/workflows/containers.yml |
| Talos images | Builds folly's signed Talos installer, ISO, iPXE and HookOS. See Kubernetes. | .github/workflows/talos-images.yml |
| Charts | Helm charts that Flux installs | packages/charts/ |
| Renovate | The dependency-update bot. Its packageRules set which updates merge unreviewed and how long each waits. | .github/renovate.json5 |
| Runners | GitHub-hosted, and the ARC (Actions Runner Controller) pools infra-folly and infra-offsite for manual dispatches | clusters/base/apps/arc/ |
Continuous delivery#
Image selection#
On a push to main, containers.yml builds images whose watch paths change.
For a workspace image, a change only to package.json, bun.lock or turbo.json needs an additional check. .github/scripts/image-affected.sh asks turbo ls --affected whether that workspace changes. If Turbo cannot answer, the image builds.
Delivery pull request#
- Each build records its image digest.
- The
deliverjob createscd/update-digestsfrommain. - It updates the built images in their declared
deploytargets. - It preserves the digests waiting in the open delivery PR, then force-pushes the branch.
- The PR merges when its checks pass.
One run opens or updates one PR. Its merge commit includes [skip ci], so no workflow runs for that commit on main.
The PR checks are the gate. The TypeScript workflow skips its test job when only image digests change in declared delivery targets.
Digest ordering and retries#
The delivery job does not write a digest older than one on main or in the open delivery PR.
A per-image cd/update-<image>-digest PR stays open until it merges or a newer delivery closes it.
caution
A workflow rerun uses the workflow from that run. A run that lacks the delivery job can open a per-image PR with an older digest.
Use workflow_dispatch with images to republish named images.
The daily run rebuilds images whose pins are behind main. For workspace images, image-affected.sh compares the pinned commit with the newest commit touching the watch paths. A lockfile change that the push skips therefore does not trigger a daily rebuild.
A build adds nothing to the PR if its filesystem and configuration match every pin on main and the delivery branch. The daily run treats it as current.
Build triggers#
| Change or request | Result |
|---|---|
PR changes containers.yml, detect-containers.sh, image-affected.sh or .github/containers.json | Build every image without publishing. |
main changes how the build job builds images | Rebuild every image. |
main changes .github/containers.json | Build images whose build entries or deploy targets change. |
main changes only detect-containers.sh or image-affected.sh | Build nothing. |
workflow_dispatch with images | Republish the named images. |
Change only under clusters/ | Do not run containers.yml. |
Helm charts#
.github/workflows/spindrift-charts.yml publishes packages/charts/kthx-engine/ and packages/charts/kthx-app/ to GHCR, and their OCIRepository objects pin a version tag. Flux loads the other charts in packages/charts/ from the infra GitRepository with reconcileStrategy: Revision, so it re-renders them on each commit to main. Under clusters/, Renovate skips first-party images and charts.
Renovate#
Renovate merges stable npm minor and patch updates as one weekly PR, so the Bun images rebuild once a week for them. Majors and 0.x updates arrive as their own PRs.
Chainguard node base digests (:latest and :latest-dev) move together in one PR each Monday and Thursday, so a base-image fix reaches the images within about four days. The kthx CLI pin in images/mate-sandbox/Dockerfile and the verifier pin in apps/kthx-engine/Dockerfile move weekly, and the owner reviews each. The Fedora base digest in images/keylime-server/Dockerfile and the rust-keylime commit and base digests in images/keylime-agent/Dockerfile move by hand.
Renovate rebases a PR only when it conflicts with main. Terraform and Talos PRs rebase whenever they fall behind, because Atlantis applies the PR's own plan.
CI checks#
A path that no workflow lists runs no checks, and its PR still passes. .github/scripts/validation-impact.sh routes paths to OpenTofu roots and nix flake check.
| Workflow | Checks |
|---|---|
nix-ci.yaml | nix flake check on PRs, and host and image builds on main |
typescript.yml | The Bun workspace, when a path on its list changes. A change that only moves a first-party image digest in a deploy target of .github/containers.json skips it. |
kustomize.yml | Renders each Flux Kustomization path, and templates in-repo charts with their HelmRelease values. It skips charts from a HelmRepository or OCIRepository. |
go.yml, rust.yml | The Go and Rust modules on their lists |
Rules#
- Put each new image on the
buildorignorelist, orcontainers.ymlfails. - Give a deployed image
deploytargets, or nothing deploys it. - Keep the
CD_APP_IDvariable andCD_APP_PRIVATE_KEYsecret set, orGITHUB_TOKENopens the digest PR, which runs no checks and never merges. - Keep the CD App's
<slug>[bot]login inonly-me.rego, or Atlantis blocks every digest PR. - Test each new Go module in
go.ymlor its own workflow, or no CI runs its tests.apps/orgpolicyauditorhas none. - Bump an OCI chart's
versionand its OCIRepositoryref.tagtogether, or the tests inapps/kthx-engine/test/conformance/fail. - Make a new OCI chart public on GHCR after its first push, or its OCIRepository fails to pull.
Related#
Linked from 6
- kthxThe lab's hosting product on the offsite cluster, which the owner and agents use to serve a directory at <name>.kthx.dev or to build a repository and deploy it to a cluster or a cloud.
- How changes shipThe path each kind of change takes from a pull request to the running system, and the GitOps rule that no one changes live state by hand.
- PlatformThe shared systems that the apps and hosts depend on, and what applies each layer from git.
- WorkloadsWhat runs on the folly and offsite Kubernetes clusters, the files that list it, and the workloads that have their own page.
- GlossaryThe lab-specific terms that the wiki pages use, what each means, and the page that defines it.
- Test a changeRun the local check for each kind of change before you open a pull request, with the CI workflow that runs the same check.