Deploying to Cloud Run and GKE with Cloud Build
Cloud Build can deploy your application to Cloud Run or GKE as the final step in your CI/CD pipeline. After building and pushing a Docker image to Artifact Registry, a deploy step calls gcloud or kubectl to update the running service. This page covers the full deployment flow, the IAM permissions required, safer rollout patterns, and how to decide between deploying directly from Cloud Build or handing off to Cloud Deploy for multi-environment promotion.
Simple explanation
Cloud Build is a task runner. You give it a list of steps in a file called cloudbuild.yaml, and it executes them in order on Google’s infrastructure. Most of those steps involve building and testing your application. The deploy step is simply the last task on that list: run a command that updates a running service with the new image.
For Cloud Run, that command is gcloud run deploy. For GKE, it is kubectl set image or kubectl apply. Cloud Build does not have special knowledge of what a deployment means. It just executes commands. The value is that those commands run automatically, consistently, and without anyone needing to open a terminal.
Think of cloudbuild.yaml as a recipe. Each step is an instruction: mix this, bake that, plate it up. The deploy step is simply the last instruction on the recipe card. Cloud Build follows the recipe the same way every single time, regardless of who triggered it or when.
If you have read the Cloud Build overview, you already understand how triggers and steps work. This page focuses on what happens when one of those steps is a deployment.
How deploying with Cloud Build works
A Cloud Build deployment pipeline follows this sequence from start to finish:
- Trigger fires. A push to the main branch, a new tag, or a manual trigger starts the build.
- Build step. Cloud Build compiles the application and creates a Docker image, tagged with
$SHORT_SHAor a version number for traceability. - Test step. Unit tests, integration tests, or security scans run against the image before it is promoted anywhere. If this step fails, the pipeline stops.
- Push step. The image is pushed to Artifact Registry, where it becomes the versioned source of truth for the deployment.
- Deploy step. Cloud Build calls
gcloud run deployorkubectl set imageto update the target service with the new image. - Traffic shift. Depending on your rollout strategy, traffic moves to the new revision immediately, or you hold it back with
—no-trafficand shift manually after verification.
Each step runs in its own container. Use waitFor to enforce ordering between steps. Cloud Build will only run the deploy step once the push has succeeded. If any step fails, the pipeline halts and the deployment does not happen.
For the build and push phases specifically, see building Docker images with Cloud Build for how those steps work before deployment begins.
When to use Cloud Build for deployments
Cloud Build is a practical choice for deployment when:
- You are deploying to a single environment, such as a Cloud Run service in one region.
- You want a straightforward pipeline: push to main, build, test, deploy, done.
- Your team is small and does not need formal approval gates or staged promotion.
- You are deploying to Cloud Run, where
gcloud run deployhandles revision management and traffic splitting for you.
Cloud Build on its own is less suitable when you need to promote a release through multiple environments with approvals, track what was deployed where, or recover quickly with a managed rollback. For those requirements, the right tool is Cloud Deploy, which integrates with Cloud Build rather than replacing it.
For Cloud Run specifically, the page on CI/CD pipelines for Cloud Run covers the full end-to-end workflow in more detail.
Cloud Build vs Cloud Deploy
These two services are often confused because their names are similar and they work closely together. They are not alternatives to each other. They solve different problems.
Cloud Build is the factory floor. It takes raw materials (your code), runs them through machines (build steps, tests), and produces a finished product (a Docker image in Artifact Registry). Cloud Deploy is the distribution network. It decides which warehouse gets the product (dev, staging, prod), manages approvals before it reaches the shelves, and can pull stock back if something is wrong. The factory does not know anything about the distribution network. It just produces a verified product and hands it over.
Cloud Build is a build and automation platform. It runs steps. It can compile code, run tests, build Docker images, scan for vulnerabilities, and execute deployment commands. When you call gcloud run deploy from a Cloud Build step, Cloud Build is doing the work. But it has no concept of a release, a target environment, or a promotion pipeline. It simply runs the commands you give it.
Cloud Deploy is a continuous delivery service built around the concept of a delivery pipeline: a defined sequence of environments (dev, staging, prod) that a release must pass through. It tracks what version is deployed to each target, supports approval gates before production promotion, provides a rollback mechanism, and records the full release history.
In practice, many teams use both together. Cloud Build builds and tests the application, then triggers a Cloud Deploy release as its final step. Cloud Deploy handles the actual promotion through environments. Cloud Build produces a verified artifact. Cloud Deploy delivers it safely.
If you are deploying to one environment with no approval requirements, Cloud Build alone is simpler. If you need multi-environment promotion, combine both tools. The page on dev vs staging vs production environments covers how to structure those environments in your pipeline.
Deploying to Cloud Run
A Cloud Run deployment pipeline has three stages: build the image, push it to Artifact Registry, then call gcloud run deploy. The deploy step uses the gcr.io/google.com/cloudsdktool/cloud-sdk builder, which is the actively maintained Google Cloud SDK image.
Do not use gcr.io/cloud-builders/gcloud for deploy steps. That builder is deprecated and no longer updated. Use gcr.io/google.com/cloudsdktool/cloud-sdk instead. Pipelines relying on the old image will break without warning when it is eventually removed.
Each step uses waitFor to enforce the correct order, so Cloud Build only deploys once the image has been pushed successfully:
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'europe-west2-docker.pkg.dev/my-app-prod/api/api:$SHORT_SHA', '.']
id: build
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'europe-west2-docker.pkg.dev/my-app-prod/api/api:$SHORT_SHA']
id: push
waitFor: ['build']
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
args:
- gcloud
- run
- deploy
- api-service
- --image=europe-west2-docker.pkg.dev/my-app-prod/api/api:$SHORT_SHA
- --region=europe-west2
- --platform=managed
id: deploy
waitFor: ['push']The $SHORT_SHA variable is automatically available in Cloud Build. Using it as the image tag means every deployed revision can be traced back to a specific commit in your repository. If a bad deploy happens, you know exactly which commit caused it.
For an overview of how Cloud Run manages revisions and traffic, see the Cloud Run overview. If you need to choose between Cloud Run and GKE as your deployment target, the GKE vs Cloud Run comparison covers the trade-offs in detail.
Safer production deployments
By default, gcloud run deploy routes 100% of traffic to the new revision immediately. For production services, this means every user is affected the moment the deploy completes. If there is a startup bug or a regression, every request fails until you notice and roll back.
Deploying with an immediate full traffic shift to an unverified revision is one of the highest-risk patterns in Cloud Run CI/CD. A single startup crash or misconfigured environment variable takes down 100% of production traffic instantly. Always use —no-traffic for production deploys unless you have another validation mechanism in place.
A safer approach is to deploy the new revision with —no-traffic, verify it at its tagged URL, then shift traffic after confirming it is working. The —tag flag assigns a stable URL to the untrafficked revision:
# Deploy new revision without routing any production traffic to it
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
args:
- gcloud
- run
- deploy
- api-service
- --image=europe-west2-docker.pkg.dev/my-app-prod/api/api:$SHORT_SHA
- --region=europe-west2
- --no-traffic
- --tag=new
id: deploy-no-traffic
# Add a step here to run smoke tests against the tagged URL:
# https://new---api-service-xxxx.run.app
# If the test step fails, the pipeline stops before traffic is shifted.
# Shift 100% of traffic to the latest revision after verification
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
args:
- gcloud
- run
- services
- update-traffic
- api-service
- --region=europe-west2
- --to-latest
waitFor: ['deploy-no-traffic']The new revision is accessible at a URL in the format new---api-service-xxxx.run.app. Send automated health checks or smoke tests to that URL. Zero production traffic reaches the new code until you explicitly call update-traffic. If the smoke test step fails, the pipeline halts and the traffic shift never happens.
Beyond the —no-traffic pattern, protect your production pipeline by restricting which events can trigger a production deployment. Triggering production deploys on every push to any branch is one of the most common mistakes in CI/CD pipelines. Use trigger filters in Cloud Build to limit production triggers to the main branch, a release branch, or a specific tag pattern. The page on securing CI/CD pipelines covers trigger protection and least-privilege IAM in more depth.
Deploying to GKE
GKE deployments from Cloud Build use gcloud container clusters get-credentials to fetch cluster credentials and configure kubectl within the build environment. Once credentials are written, a subsequent step can run kubectl commands against the cluster.
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'europe-west2-docker.pkg.dev/my-app-prod/api/api:$SHORT_SHA', '.']
id: build
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'europe-west2-docker.pkg.dev/my-app-prod/api/api:$SHORT_SHA']
id: push
waitFor: ['build']
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
args:
- gcloud
- container
- clusters
- get-credentials
- my-cluster
- --region=europe-west2
- --project=my-app-prod
id: credentials
waitFor: ['push']
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
args:
- kubectl
- set
- image
- deployment/api-service
- api=europe-west2-docker.pkg.dev/my-app-prod/api/api:$SHORT_SHA
- --namespace=production
waitFor: ['credentials']The get-credentials step writes a kubeconfig to the build environment. The kubectl set image command then updates the container image on your Deployment object, which triggers a rolling update in Kubernetes.
Direct kubectl commands from Cloud Build have significant limitations in production. There is no automatic rollback if the new image causes failures. There is no approval gate before the deployment runs. Release history is not tracked. For production GKE workloads, use Cloud Deploy instead. It integrates natively with GKE and provides all of these features without requiring you to build them yourself.
The GKE overview covers how Kubernetes rolling updates work and when they need additional tooling around them. If you are choosing between Cloud Run and GKE as your target, the page on Cloud Run vs GKE vs Compute Engine helps with that decision.
Handing off to Cloud Deploy
When your pipeline needs to promote a release through multiple environments, the recommended pattern is for Cloud Build to produce the artifact and then trigger a Cloud Deploy release as its final step. Cloud Deploy handles promotion through dev, staging, and production targets with any approval gates you have configured.
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'europe-west2-docker.pkg.dev/my-app-prod/api/api:$SHORT_SHA', '.']
id: build
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'europe-west2-docker.pkg.dev/my-app-prod/api/api:$SHORT_SHA']
id: push
waitFor: ['build']
# Create a Cloud Deploy release to promote through dev -> staging -> prod
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
args:
- gcloud
- deploy
- releases
- create
- release-$SHORT_SHA
- --delivery-pipeline=api-pipeline
- --region=europe-west2
- --images=api=europe-west2-docker.pkg.dev/my-app-prod/api/api:$SHORT_SHA
waitFor: ['push']Cloud Build does not manage environments here. It produces the image and creates the release. Cloud Deploy knows about the pipeline targets and handles promotion and rollback from that point. Keeping these two concerns separate is the cleaner long-term architecture for any team that expects to grow beyond one environment.
For guidance on structuring dev, staging, and production as distinct targets, see managing environments in CI/CD.
When you need a single cloudbuild.yaml to serve different environments, use custom substitutions. Define a separate trigger per environment and pass environment-specific values as substitution variables:
substitutions:
_ENV: prod
_REGION: europe-west2
steps:
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
args:
- gcloud
- run
- deploy
- api-service
- --image=europe-west2-docker.pkg.dev/my-app-prod/api/api:$SHORT_SHA
- --region=${_REGION}
- --project=my-app-${_ENV}Each trigger overrides the substitution values for its environment. One config file, multiple targets, no hard-coded project IDs.
IAM permissions
Cloud Build runs as a service account. That service account needs specific roles to deploy, and the most common deployment failures come from missing permissions rather than incorrect YAML.
For Cloud Run deployments, the service account needs two roles:
roles/run.developerto deploy and update Cloud Run services.roles/iam.serviceAccountUserto act as the service identity when the Cloud Run service runs as a specific service account.
The second role is the one teams most often miss. Without roles/iam.serviceAccountUser, the deploy step fails with a permission-denied error even though roles/run.developer is granted. The error message is not always clear about the cause. This is the single most common source of Cloud Run deployment failures from Cloud Build.
# Get the Cloud Build service account email
CB_SA=$(gcloud builds get-default-service-account --project=my-app-prod)
# Grant permission to deploy and update Cloud Run services
gcloud projects add-iam-policy-binding my-app-prod \
--member="serviceAccount:${CB_SA}" \
--role="roles/run.developer"
# Required when the Cloud Run service runs as a specific service account
gcloud projects add-iam-policy-binding my-app-prod \
--member="serviceAccount:${CB_SA}" \
--role="roles/iam.serviceAccountUser"For GKE deployments, the service account also needs roles/container.developer on the project, or a more specific cluster-scoped binding if you are using Workload Identity Federation. For Cloud Deploy releases, it additionally needs roles/clouddeploy.releaser.
Grant only the roles a pipeline actually uses. Broad roles such as roles/editor on the Cloud Build service account are a significant security risk in CI/CD pipelines. The page on securing CI/CD pipelines covers the principle of least privilege in this context.
Common mistakes
Using the deprecated gcloud builder. Use
gcr.io/google.com/cloudsdktool/cloud-sdkfor all gcloud and kubectl steps. The oldergcr.io/cloud-builders/gcloudimage is deprecated and no longer updated. Pipelines relying on it will break without warning when the image is eventually removed.Missing roles/iam.serviceAccountUser. When a Cloud Run service runs as a named service account rather than the default compute service account, the Cloud Build service account must also have
roles/iam.serviceAccountUser. Granting onlyroles/run.developeris not enough, and the error message Cloud Build returns is not always obvious about the cause.Deploying to production on every branch push. Without explicit trigger filters, a push to any feature branch can start a production deployment. Always scope production triggers to a protected main branch, a release branch, or a specific tag pattern.
Using —no-traffic without any verification step. Deploying with
—no-trafficand then immediately shifting withupdate-traffic —to-latestin the very next step without running any checks provides no benefit. The pattern only protects production if you actually run a smoke test or health check between the two steps.Hard-coding environment values in cloudbuild.yaml. Hard-coding a project ID, region, or service name for one environment and then reusing the same file for another environment leads to deployments going to the wrong place. Use substitution variables from the start, even for single-environment projects.
Using direct kubectl commands for production GKE workloads. Running
kubectl set imagedirectly from Cloud Build gives you no rollback mechanism, no approval gate, and no release history. For production clusters, trigger a Cloud Deploy release instead and let Cloud Deploy handle the delivery.
FAQ
Can Cloud Build deploy directly to Cloud Run?
Yes. Add a step using the gcr.io/google.com/cloudsdktool/cloud-sdk builder and call gcloud run deploy. The Cloud Build service account needs roles/run.developer and roles/iam.serviceAccountUser on the project.
Can Cloud Build deploy to GKE?
Yes. Use gcloud container clusters get-credentials to configure kubectl in the build environment, then run kubectl set image or kubectl apply. For production clusters, Cloud Deploy is the better option because it adds approval gates, release tracking, and rollback support that would otherwise need to be built manually.
Should I use Cloud Build or Cloud Deploy?
Cloud Build is a build automation tool that can also run deployment commands. Cloud Deploy is a continuous delivery service designed for managed promotion through environments. For a single environment without approval requirements, Cloud Build alone is simpler. For multi-environment pipelines, use both: Cloud Build builds and tests the artifact, Cloud Deploy delivers it.
What IAM roles does Cloud Build need for Cloud Run deployment?
Two roles: roles/run.developer to deploy and update services, and roles/iam.serviceAccountUser to act as the Cloud Run service identity. Missing serviceAccountUser is the most common cause of permission-denied errors in Cloud Run deployments from Cloud Build, even when run.developer is correctly granted.
How do I deploy without sending traffic immediately?
Add —no-traffic to your gcloud run deploy command. The new revision is deployed but receives no production traffic. Use —tag to assign it a stable URL for testing, then call gcloud run services update-traffic —to-latest to shift traffic once you have confirmed the revision is working correctly.
Summary
- Cloud Build can deploy to Cloud Run, GKE, or trigger a Cloud Deploy release as the final step in a pipeline.
- Use
gcr.io/google.com/cloudsdktool/cloud-sdkfor all gcloud and kubectl steps, not the deprecated gcloud builder. - Cloud Run deployments require both
roles/run.developerandroles/iam.serviceAccountUseron the Cloud Build service account. - Use
—no-trafficto deploy a new revision without affecting live traffic, run a verification step, then shift traffic once confirmed. - Restrict production triggers to protected branches or tags. Never deploy to production on every push.
- For multi-environment pipelines with promotion and approvals, use Cloud Build to produce the artifact and Cloud Deploy to deliver it.
- Direct kubectl commands from Cloud Build work for simple cases, but lack rollback and release tracking for production GKE workloads.
Frequently asked questions
Can Cloud Build deploy directly to Cloud Run?
Yes. Add a deploy step using the gcr.io/google.com/cloudsdktool/cloud-sdk builder and call gcloud run deploy. The Cloud Build service account needs roles/run.developer and roles/iam.serviceAccountUser on the target project.
Can Cloud Build deploy to GKE?
Yes. Use gcloud container clusters get-credentials to configure kubectl in the build environment, then run kubectl set image or apply to update your workload. For production clusters, Cloud Deploy is the better option because it adds approval gates, release tracking, and rollback support.
Should I use Cloud Build or Cloud Deploy for deployments?
Cloud Build suits straightforward single-environment deployments. Cloud Deploy adds controlled promotion through multiple environments, approval gates, release tracking, and rollback. For anything beyond a simple one-step deploy, use Cloud Build to produce the artifact and Cloud Deploy to deliver it.
What IAM roles does Cloud Build need for Cloud Run deployment?
Two roles: roles/run.developer to deploy and update Cloud Run services, and roles/iam.serviceAccountUser to act as the service identity. Missing serviceAccountUser is the most common cause of permission-denied errors even when run.developer is already granted.
How do I deploy a new revision without sending traffic to it immediately?
Add --no-traffic to your gcloud run deploy command. The revision deploys but receives no traffic. You can test it at its tagged URL, then shift traffic with gcloud run services update-traffic --to-latest once you have verified it is working correctly.