06 · CI/CD (Cloud Build, Artifact Registry)¶
Cloud Build runs a sequence of container-based build steps triggered by a git push, and Artifact Registry stores the resulting images (and other package types). Together they form GCP's native CI/CD path from commit to deployed revision.
A build config¶
# cloudbuild.yaml
steps:
- name: gcr.io/cloud-builders/docker
args:
- build
- -t
- us-central1-docker.pkg.dev/$PROJECT_ID/app/orders:$SHORT_SHA
- .
- name: gcr.io/cloud-builders/docker
args:
- push
- us-central1-docker.pkg.dev/$PROJECT_ID/app/orders:$SHORT_SHA
- name: gcr.io/google.com/cloudsdktool/cloud-sdk
entrypoint: gcloud
args:
- run
- deploy
- orders-api
- --image=us-central1-docker.pkg.dev/$PROJECT_ID/app/orders:$SHORT_SHA
- --region=us-central1
images:
- us-central1-docker.pkg.dev/$PROJECT_ID/app/orders:$SHORT_SHA
options:
logging: CLOUD_LOGGING_ONLY
gcloud artifacts repositories create app \
--repository-format=docker \
--location=us-central1
gcloud builds submit --config=cloudbuild.yaml .
# Build log excerpt
# Step #0 - "docker": Successfully tagged us-central1-docker.pkg.dev/.../orders:a1b2c3d
# Step #1 - "docker": a1b2c3d: digest: sha256:... size: 1987
# Step #2 - "gcloud": Service [orders-api] revision [orders-api-00007-xyz] has been deployed
# DONE
$SHORT_SHA and $PROJECT_ID are Cloud Build substitutions, populated
automatically from the triggering commit — tagging images by commit SHA
(not latest) means every deployed revision maps back to an exact commit.
Triggers on push¶
gcloud builds triggers create github \
--repo-name=orders-service \
--repo-owner=my-org \
--branch-pattern="^main$" \
--build-config=cloudbuild.yaml \
--name=orders-main-trigger
A push to main now kicks off cloudbuild.yaml automatically. Use a
separate trigger with a different --branch-pattern (e.g. ^release-.*$)
pointed at a cloudbuild-prod.yaml for a distinct prod pipeline, rather
than branching logic inside one config.
Gotcha — the Cloud Build service account needs explicit deploy
permissions. Cloud Build's default SA
(PROJECT_NUM@cloudbuild.gserviceaccount.com) can build and push images out
of the box, but gcloud run deploy from within a build step fails with a
permission error until you grant it roles/run.admin and
roles/iam.serviceAccountUser (to act as the runtime SA):
gcloud projects add-iam-policy-binding my-project \
--member="serviceAccount:PROJECT_NUM@cloudbuild.gserviceaccount.com" \
--role="roles/run.admin"
gcloud iam service-accounts add-iam-policy-binding \
orders-runtime-sa@my-project.iam.gserviceaccount.com \
--member="serviceAccount:PROJECT_NUM@cloudbuild.gserviceaccount.com" \
--role="roles/iam.serviceAccountUser"
Artifact Registry vs. Container Registry¶
Container Registry (gcr.io) is the older, deprecated image store;
Artifact Registry (*-docker.pkg.dev) is its replacement and also supports
non-container formats — Maven, npm, Python, Go modules — in one place.
gcloud artifacts repositories create app-npm \
--repository-format=npm \
--location=us-central1
gcloud artifacts docker images list us-central1-docker.pkg.dev/my-project/app
# IMAGE DIGEST TAGS
# .../app/orders sha256:a1b2.. a1b2c3d
Gotcha — regional repos don't replicate automatically. An Artifact
Registry repo is pinned to one region (or declared multi-region at
creation, not after). A Cloud Run service in europe-west1 pulling from a
us-central1 repo works but adds cross-region latency to every cold
start — create a repo per region you deploy into for latency-sensitive
services, and push the same tag to each.
Build approvals and manual gates¶
For a prod pipeline, add an approval gate so a build doesn't deploy without a human sign-off:
gcloud builds triggers create github \
--repo-name=orders-service \
--repo-owner=my-org \
--branch-pattern="^release-.*$" \
--build-config=cloudbuild-prod.yaml \
--require-approval \
--name=orders-prod-trigger
Gotcha — approval only pauses execution, it doesn't pre-validate the
diff. --require-approval stops the build before running any steps, but
whoever approves needs to have already reviewed the actual code change
(via the PR) — the approval gate itself shows no diff, just "run this
pipeline: yes/no."
Cheat sheet¶
| Command | Purpose |
|---|---|
gcloud builds submit --config= |
Run a build manually from local source. |
gcloud builds triggers create github |
Auto-run a build config on push/PR. |
$SHORT_SHA, $PROJECT_ID |
Built-in substitutions for commit-based tagging. |
roles/run.admin + roles/iam.serviceAccountUser on Cloud Build SA |
Required for a build step to deploy to Cloud Run. |
gcloud artifacts repositories create |
Create a regional image/package repo. |
--require-approval / gcloud builds approve |
Manual gate before a pipeline proceeds. |
How It Actually Works¶
Cloud Build executes your pipeline as a sequence of Docker containers
run one after another on a build VM that Google provisions on demand and
tears down when the build finishes — you're billed only for build-minutes
actually consumed, and each step's container shares a single /workspace
volume, which is the only thing steps share; environment variables and
in-memory state from one step don't carry to the next, only files written
to /workspace do. This container-per-step model is why a Cloud Build
step can run literally any tool with a public image (Terraform, kubectl,
a custom linter) without Google needing to bake support for every tool
into the platform. Build triggers work by registering a webhook with your
source repository (GitHub, Cloud Source Repositories) — a push event
posts to Cloud Build's endpoint, which matches the event against your
trigger's branch/tag filter and, on a match, starts a build using the
cloudbuild.yaml at the commit's exact revision, which is why in-flight
builds are unaffected by a subsequent push to the same branch.
Exercise¶
Write a cloudbuild.yaml that builds an image, pushes it to a new Artifact
Registry Docker repo, and deploys it to Cloud Run — tagging by
$SHORT_SHA. Grant the Cloud Build SA the two roles it needs, submit the
build manually with gcloud builds submit, and confirm the deployed
revision's image digest matches what Artifact Registry shows via gcloud
artifacts docker images list.