Cloud Build
Cloud Build is the managed CI service on Google Cloud. It triggers from Git events, runs build pipelines defined in YAML where each step is a Docker container, and produces artifacts that land in Artifact Registry. Native integration with the rest of GCP — IAM, secret access through Secret Manager, downstream triggers to Cloud Deploy — is what differentiates it from generic CI services that run on top of GCP rather than inside it.
The central practical decision around Cloud Build is not how to use it, but whether to use it at all. Many teams arrive at GCP with an existing CI stack (GitHub Actions, GitLab CI, Jenkins) that already builds and pushes to Artifact Registry. That works. Cloud Build earns its place when deep GCP integration matters — private-pool builds with VPC access to production data, native SLSA provenance, IAM-scoped service accounts per build, and direct triggering of Cloud Deploy without webhooks.
Managed CI on GCP
A Cloud Build run consists of one or more steps, each step being a container image with a command. The platform pulls each step's image, mounts the source workspace, and runs the command. Steps share /workspace — the build directory persists across steps within a run. This container-as-step model is more flexible than legacy CI languages (Jenkinsfile DSLs, GitLab YAML extensions): if a tool runs in a container, it runs in Cloud Build, no plugin required.
Triggers and Git Integration
Cloud Build triggers respond to events from connected Git repositories — GitHub, GitLab, and Bitbucket through repository connections (Developer Connect) or webhook integration. (Cloud Source Repositories, the old native option, has been closed to new customers since 2024.) Triggers can fire on push to a branch, on pull request open or merge, or on tag creation, with filter expressions to scope to specific paths or branches.
Triggers also fire from non-Git events: Pub/Sub messages, manual button clicks, scheduled runs via Cloud Scheduler. The trigger is the entry point; everything downstream is the same build pipeline regardless of what kicked it off.
Build Steps and the Container Model
A minimal Cloud Build pipeline:
- Step 1 — pull a Go builder image, run
go teston the workspace. - Step 2 — pull a Docker builder, build the image from the Dockerfile.
- Step 3 — push the resulting image to Artifact Registry.
Substitution variables ($_BRANCH, $REPO_NAME, custom user-defined) parameterize the YAML so the same file works across branches and environments. Steps run sequentially by default; waitFor declarations allow parallel steps where dependencies permit, which is how Cloud Build handles fan-out for multi-arch builds, parallel test matrices, or independent linters.
Private Pools for VPC-bound Builds
By default, Cloud Build runs on Google's shared worker pool — fast, billed per build-minute past a monthly free tier of 2,500 build-minutes, but with no VPC access. When the build needs to reach a private Cloud SQL instance, an internal Artifact Registry mirror, or any resource that lives inside a VPC and rejects public traffic, you need a private pool. Private pools are worker fleets that live inside your VPC; builds running on them have direct private connectivity. The cost is higher (private pools are billed per worker-minute with no free tier) but the alternative — exposing internal resources to the public internet for the build to reach them — is worse.
Build Provenance and SLSA
Cloud Build automatically generates SLSA (Supply-chain Levels for Software Artifacts) provenance metadata for every Docker image it builds: the source repository, commit SHA, builder identity, and a signed attestation. Downstream consumers — Binary Authorization, Container Analysis, security audits — can verify that an image was actually built by your Cloud Build pipeline from the source revision claimed. This is the supply-chain story that connects "we trust our build" to "we can prove our build." Enable provenance on production pipelines; the operational cost is near zero.
Cloud Build vs External CI
Cloud Build — deep GCP integration. Private pools with VPC access, IAM-scoped service accounts per build, native SLSA provenance, direct Cloud Deploy triggering. The right default when the workload runs primarily on GCP and the CI/CD pipeline touches private GCP resources.
GitHub Actions / GitLab CI / etc. — runs alongside the source code, broad ecosystem of community-built actions, portable across cloud providers, faster onboarding for teams that already know it. The right default when portability matters, when the team is already on the external CI, or when the build does not need GCP-internal network access.
Many teams use both — external CI for build and test (where ecosystem matters), Cloud Build for the production push and Cloud Deploy trigger (where GCP integration matters). Mixing is reasonable.
- Cloud Build's default service account left over-privileged. A compromised build can reach broad swaths of GCP — exactly the supply-chain attack pattern this whole chapter exists to prevent.
- No SLSA provenance enabled on production pipelines. The image is in Artifact Registry; the audit trail back to source is missing.
- Builds running in the shared pool when they need VPC access. The workaround is usually opening a firewall hole or exposing internal resources publicly — both worse than just using a private pool.
- No build caching. Layer caches, language-specific caches (npm, pip, Maven), and Docker buildx caches all reduce build time dramatically; without them, every build is from scratch.
- One enormous build YAML that does everything in sequence. Modular pipelines — build, test, push, trigger — are easier to maintain and parallelize.
- Cloud Build used only as a thin wrapper around
docker build. The native GCP integrations (Secret Manager access, IAM-scoped service accounts, Cloud Deploy triggering) are the actual value; using Cloud Build only fordocker buildwastes most of what it offers.
- Scoped service account per build with the minimum permissions the build actually needs. Never the default high-privilege Cloud Build service account.
- SLSA provenance enabled on every production build. The cost is near zero; the value at audit time is significant.
- Private pools for builds that need VPC-internal access. Pay for the pool; do not punch holes in your network.
- Explicit build caching — Docker layer cache, language-specific caches. Cloud Build supports them; use them.
- Modular pipelines: separate build, test, and deploy stages with clear hand-offs. The "one mega-YAML" pattern resists changes.
- Build status notifications through Pub/Sub into the team's SIEM or ChatOps channel. Failed builds need to be visible.
Knowledge Check
How does Cloud Build model build steps?
- As a declarative YAML DSL with built-in keywords for each supported language and tool, much like a Jenkinsfile, where the platform provides a fixed vocabulary of stages you fill in
- As a sequence (or parallel graph) of Docker containers — each step is a container image plus a command, sharing the workspace directory across steps
- As a directed graph of standalone bash scripts that the Cloud Build runner parses and interprets line by line at runtime
- As individual Python functions, each of which the platform invokes inside a managed Python interpreter it provisions per build
When does a build need a Cloud Build private pool?
- For any build that produces a Docker image, since the shared pool lacks a Docker daemon entirely and private pools are required for the docker build step to run at all
- When the build needs network access to private GCP resources (Cloud SQL on private IP, internal artifact servers, internal services) that reject public traffic
- For builds longer than 30 minutes, because the shared pool enforces a hard 30-minute wall-clock timeout that only a private pool can extend
- For all production builds, since the shared pool is restricted to non-production and test workloads by Google's service terms
What does SLSA provenance give you at production audit time?
- A continuously refreshed list of every known CVE present across the image's full transitive dependency tree, regenerated automatically each day as new advisories are published upstream by vendors
- A signed attestation linking the deployed image to the source repository, commit SHA, and builder identity that produced it — verifiable by Binary Authorization and security audits
- A formal mathematical proof that the image contains no malicious code, produced by static analysis of every layer during the build
- A signed billing receipt confirming the build ran at the standard per-minute rate, used for internal cost recovery between teams
When does Cloud Build earn its place over external CI like GitHub Actions?
- Always, because Cloud Build's free tier grants unlimited build-minutes with no daily cap whatsoever, which makes any external CI provider strictly more expensive and therefore the worse choice in every conceivable scenario regardless of integration needs
- When the build needs private VPC access, IAM-scoped service accounts per build, native SLSA provenance, or direct Cloud Deploy triggering — the deep GCP integrations that external CI cannot replicate without significant glue code
- When the team already has Jenkins experience, since Cloud Build is Jenkinsfile-compatible and runs existing Jenkins pipelines unchanged, so the migration cost is essentially zero
- When the workload is written in Python or Go specifically, because external CI providers like GitHub Actions have weak support for those two languages compared to Cloud Build
A compromised Cloud Build pipeline pushes a malicious image and uses the default Cloud Build service account to grant itself IAM access to production. What is the root cause?
- Cloud Build was misconfigured at the project level by an administrator, since this privilege-escalation attack is simply not reachable when the service is left at its safe out-of-the-box default settings
- The default Cloud Build service account was left with broad permissions; production builds should use a scoped service account with the minimum permissions required for the build's actual work
- SLSA provenance generation was disabled on the pipeline, so the malicious image carried no attestation and the escalation could not be detected or blocked after the fact
- The private pool was never enabled, and only builds that run inside a private pool enforce service account scoping, leaving shared-pool builds free to escalate
You got correct