build-lib — the shared build engine
Every app deploys through Cloud Build. Instead of each repo carrying hundreds of lines of bash, they share u2i/build-lib — a reusable engine that turns each pipeline into a short list of named steps. An app's deploy/cloudbuild/*.yaml becomes ~70 lines of "call this command" instead of ~210 lines of scripting, and the actual logic lives (and gets fixed) in one place.
Two parts
1. A Docker image (the toolbox)
Published at europe-west1-docker.pkg.dev/shared-images-nonprod/docker-images/build-lib:<tag>. It bundles Nushell plus every CLI a pipeline step shells out to — gcloud, kubectl, helm, crane, jq, docker, buildx. It inherits from Cloud Build's docker builder so the privileged buildx contract still works. Every Cloud Build step runs inside this one image.
2. A Nushell library (the commands)
Inside the image, /lib/*.nu exposes a small set of typed commands — kube login, build image, release package-for-config-sync, helm apply-preview, and so on. Your pipeline steps call these instead of hand-writing shell. Per-pipeline state (project, commit SHA, namespace, target) is written once to /workspace/.build-state.json by the first 'boundary' step and read by every step after.
How a step is written
Each step runs the build-lib image with one of two tiny wrapper entrypoints: init-step (the boundary step that writes state) or build-step(everything after). The wrapper imports the library, loads state, and emits start/finished markers — so the step body in the app's YAML is just the operations.
steps:
- name: '${_BUILD_LIB}'
id: 'init'
entrypoint: 'init-step'
args: ['state init-env --target dev']
- name: '${_BUILD_LIB}'
id: 'cluster-setup'
waitFor: ['init']
entrypoint: 'build-step'
args:
- 'cluster-setup'
- |
kube login
kube ensure-namespace
kube bind-workload-identity
substitutions:
_BUILD_LIB: 'europe-west1-docker.pkg.dev/shared-images-nonprod/docker-images/build-lib:0.5.10'Command surface
| Module | Commands | What it does |
|---|---|---|
| state | init-env, init-preview, init-cleanup, load, set | Writes the shared state file once (target, project, SHA, namespace) so later steps read it instead of re-deriving it. |
| build | setup-buildx, image | Builds a container image and pushes it to the app's Artifact Registry (multi-arch via buildx). |
| release | package-for-config-sync, push-chart, tag-chart, prepare-reposync, create-cloud-deploy-release | Packages the Helm chart, pushes it to the config-sync OCI registry, tags it, renders the RepoSync manifest, and creates the Cloud Deploy release. |
| kube | login, ensure-namespace, bind-workload-identity, create-database-secret, run-job, delete-namespace | Cluster operations: authenticate, make sure the namespace exists, wire Workload Identity, manage secrets, run jobs, tear a namespace down. |
| helm | install, oci-login, apply-preview | Helm operations — including apply-preview, the direct apply used for per-PR preview environments. |
| migration | run | Runs database migrations (invoked as a Cloud Deploy postdeploy action). |
| secret / neon / lint / log | secret fetch · neon fork-branch · lint run · log build-metadata | Supporting ops: pull secrets, fork a Neon Postgres branch per preview, run static checks, emit structured build logs. |
A real dev pipeline, end to end
The dev flow is a chain of build-lib steps: initialise state, build the image, package and publish the Helm chart, then hand off to Cloud Deploy — exactly the path described on the tutorial.
state init-env --target dev # boundary step: write .build-state.json
build image # build + push the container image
release package-for-config-sync # helm package -> OCI artifact
release push-chart # push chart to the config-sync registry
release tag-chart # tag the chart version
release prepare-reposync # render RepoSync with that version
release create-cloud-deploy-release # hand off to Cloud DeployWhy it exists
- One place to fix CI logic — change build-lib, and every app's pipeline gets the fix (apps pin a version tag, e.g. 0.5.x, and upgrade deliberately).
- Short, readable pipelines — an app's cloudbuild YAML reads as a list of intentions, not a shell script.
- Consistency — every app builds, packages, and releases the same way, so the tutorial and the example match reality across apps.