CI/CD Pipeline for Cloud Run on GCP
This page shows you how to build a complete CI/CD pipeline for Cloud Run on GCP. You will end up with a working pipeline that runs tests, builds a Docker image, stores it in Artifact Registry, and deploys it to Cloud Run automatically whenever code is merged. Along the way, you will understand when to use Cloud Deploy, how to do safer production releases, and what common mistakes to avoid.
Simple explanation
A CI/CD pipeline is automation that moves your code from your repository to a running service, without anyone running commands by hand. The basic flow is:
- A developer pushes or merges code.
- Automated tests run to check nothing is broken.
- A Docker image is built from the code.
- The image is stored in Artifact Registry.
- Cloud Run is told to use the new image, and a new revision starts.
- More careful teams add a verification or approval step before production traffic shifts.
The result is that every code change that passes your tests ends up running in production, repeatably and with a full audit trail, in a matter of minutes.
Think of a CI/CD pipeline like a parcel courier service. You hand a package to the depot (push code). The depot checks it meets shipping requirements (tests). A tracking label is printed with a unique ID (image tagged with the commit SHA). It goes into the warehouse (Artifact Registry). A driver delivers it to the address (Cloud Run). You never drive it yourself, and every handoff is logged.
How it works
Each pipeline run follows a fixed sequence. Here is the end-to-end flow:
- Developer pushes code to the main branch, or merges a pull request.
- Cloud Build trigger fires and starts the pipeline automatically.
- Tests run first. The pipeline stops here if they fail, before spending time on a build.
- Docker image builds, tagged with the short commit SHA so every image is traceable to a specific change.
- Image pushes to Artifact Registry, the private container registry inside your GCP project.
- Cloud Run is updated using either a direct
gcloud run deploycall, or a Cloud Deploy release creation. - Optional: verification and traffic shift. Smoke tests run against a tagged revision URL, then traffic shifts after they pass.
- Cloud Build logs the full run. Every step is recorded and searchable in the console.
What each component does
Cloud Build
Cloud Build runs your pipeline steps in a managed environment. It has native integration with GCP, so it can push images and deploy services without storing external credentials. See the Cloud Build overview for how triggers and build steps work.
Artifact Registry
Artifact Registry is your private Docker image store. Every image your pipeline builds gets pushed here before deployment. Cloud Run pulls from it directly. It also scans for vulnerabilities and supports fine-grained access control per repository.
Cloud Run
Cloud Run is the deployment target. It runs your container, manages revisions, and handles traffic routing. A new revision starts on each deployment. Traffic shifts to the new revision once it passes startup health checks.
Cloud Deploy
Cloud Deploy handles the promotion of a release through multiple environments. It adds approval gates, a defined promotion workflow, and rollback capability. You only need it if you are managing more than one environment or want manual approval before production.
Why Cloud Run is a good starting point
Cloud Run is one of the simplest GCP services to deploy to from a pipeline. A single gcloud run deploy command updates a running service with a new image. There is no cluster to manage, no deployment manifest to maintain, and no node pool to worry about.
This makes it a practical first target if you are building your first GCP CI/CD pipeline. The core patterns (building images, pushing to Artifact Registry, deploying via gcloud) apply directly when you later move to more complex targets like GKE. You also get zero-downtime deployments and a complete audit trail with no extra configuration.
Complete pipeline example
The example below is a complete cloudbuild.yaml for a Python Cloud Run service. It runs tests first, builds the image only if they pass, pushes to Artifact Registry, then deploys to Cloud Run. Replace YOUR_PROJECT_ID, YOUR_REGION, and YOUR_SERVICE_NAME with your own values before using it.
steps:
# Run tests first — fail fast before spending time building an image
- name: 'python:3.11'
entrypoint: bash
args:
- -c
- |
pip install -r requirements.txt
python -m pytest tests/ -v
id: run-tests
# Build Docker image — only after tests pass
- name: 'gcr.io/cloud-builders/docker'
args:
- build
- -t
- YOUR_REGION-docker.pkg.dev/YOUR_PROJECT_ID/api/api:$SHORT_SHA
- .
id: build-image
waitFor: ['run-tests']
# Push to Artifact Registry
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'YOUR_REGION-docker.pkg.dev/YOUR_PROJECT_ID/api/api:$SHORT_SHA']
id: push-image
waitFor: ['build-image']
# Deploy to Cloud Run
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
args:
- gcloud
- run
- deploy
- YOUR_SERVICE_NAME
- --image=YOUR_REGION-docker.pkg.dev/YOUR_PROJECT_ID/api/api:$SHORT_SHA
- --region=YOUR_REGION
- --platform=managed
waitFor: ['push-image']
images:
- 'YOUR_REGION-docker.pkg.dev/YOUR_PROJECT_ID/api/api:$SHORT_SHA'A few things worth understanding before you adapt this file:
- Tests run first because they are the cheapest thing to run. If they fail, no image is built and no Cloud Build minutes are spent on a Docker build.
$SHORT_SHAtags every image with the commit that produced it. This makes it straightforward to trace any running Cloud Run revision back to a specific change in your repository.waitForenforces strict execution order. Without it, Cloud Build may run steps in parallel. This pipeline must be sequential: tests, then build, then push, then deploy.- The
gcr.io/google.com/cloudsdktool/cloud-sdkbuilder is the current maintained image for running gcloud commands. The oldergcr.io/cloud-builders/gcloudimage is deprecated; do not use it in new pipelines.
For more on structuring test steps and running them efficiently, see automated testing in Cloud Build.
Required permissions
The Cloud Build service account needs three IAM roles to run this pipeline. Grant only these and nothing broader:
# Get the Cloud Build default service account email
CB_SA=$(gcloud builds get-default-service-account --project=YOUR_PROJECT_ID)
# Push images to Artifact Registry
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:${CB_SA}" \
--role="roles/artifactregistry.writer"
# Deploy Cloud Run services
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:${CB_SA}" \
--role="roles/run.developer"
# Act as the Cloud Run service's identity (required for deploy to succeed)
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:${CB_SA}" \
--role="roles/iam.serviceAccountUser"Why each role exists:
artifactregistry.writerlets Cloud Build push the built image to your private registry. Without it, the push step fails with a permission denied error.run.developerlets Cloud Build create new revisions and update the Cloud Run service. It does not grant any broader project-level access.iam.serviceAccountUserallows Cloud Build to act as the Cloud Run service account during deployment. Without it, the deploy step fails even if the other two roles are granted.
The default compute service account has editor-level access to your project. If a Cloud Run service using it is ever compromised, an attacker gains broad access to your infrastructure. Create a dedicated, least-privilege service account for each service and set it explicitly at deploy time. See service accounts on GCP for how to do this.
When to use this
This pattern works well for:
- Small to mid-sized teams shipping containerised APIs or web services to Cloud Run.
- Teams that want fast, repeatable deployments with a full audit trail and no manual steps.
- Projects starting with a single production environment and planning to add staging or dev environments later.
- Teams already using Cloud Build for CI who want to extend the same pipeline to cover deployment.
When this is not enough
The direct Cloud Build approach starts to show its limits when:
- You have multiple environments and need controlled, auditable promotion between them.
- You need a human approval gate before anything reaches production.
- You want rollback capability that is tracked and repeatable from the console or API.
- Your release process involves progressive delivery strategies like canary or blue-green deployments.
- Compliance or change control requirements mean you need a full release history.
In those situations, the next step is managing environments in CI/CD and adding Cloud Deploy to your pipeline.
Cloud Build vs Cloud Deploy for Cloud Run
Both tools can deploy to Cloud Run. The question is how much structure you need around the release process.
Use Cloud Build directly when:
- You have one environment, or you treat each GCP project as a standalone deployment.
- Automated promotion is acceptable, with no approval gates needed before production.
- You want the simplest possible setup to get started quickly.
Add Cloud Deploy when:
- You are promoting releases through dev, staging, and production in a defined sequence.
- You want manual approval before any rollout reaches production.
- You need tracked rollback history: the ability to promote a previous release to a target with a single command.
- You want rollout events and status visible in the Cloud Deploy console, separate from your CI build logs.
The two tools are designed to work together. In a mature pipeline, Cloud Build ends with a step that creates a Cloud Deploy release. Cloud Deploy then takes over, promoting the release through environments with whatever gates you have defined. Cloud Build handles CI; Cloud Deploy handles CD.
You do not need Cloud Deploy on day one. Start with a direct gcloud run deploy step in Cloud Build. You can add Cloud Deploy later without rewriting your build pipeline. The two parts are independent, and adding Cloud Deploy is an extension, not a replacement.
See the Cloud Deploy overview and the guide to deploying with Cloud Build for more detail on each approach.
Safer production releases
The simplest pipeline shifts 100% of traffic to the new revision as soon as it deploys. For low-risk internal services that is often fine. For production APIs, you usually want to verify the new revision before any users reach it.
Cloud Run supports deploying a revision without giving it any traffic. The revision starts and is accessible at a stable tagged URL, but production traffic continues going to the current revision. You run smoke tests against the tagged URL, and only if they pass do you shift traffic across.
Imagine a restaurant opening a refitted kitchen. The old kitchen keeps serving tables throughout the evening. The new kitchen runs on the same shift, but only the head chef eats from it first. If the dishes come out right, the old kitchen switches off and the new one takes over service. No diner ever receives a meal from an unverified kitchen.
# Deploy new revision with no live traffic — accessible at tagged URL only
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
args:
- gcloud
- run
- deploy
- YOUR_SERVICE_NAME
- --image=YOUR_REGION-docker.pkg.dev/YOUR_PROJECT_ID/api/api:$SHORT_SHA
- --region=YOUR_REGION
- --no-traffic
- --tag=canary
# Shift 100% of traffic to the new revision after verification
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
args:
- gcloud
- run
- services
- update-traffic
- YOUR_SERVICE_NAME
- --region=YOUR_REGION
- --to-latestBetween those two steps is where your smoke tests belong. Add a step that calls a health endpoint, runs a lightweight integration test, or simply checks that the service responds at the tagged URL. If the smoke test step fails, the pipeline stops and the traffic shift never happens.
For teams who want a human to approve before traffic shifts, that decision belongs in Cloud Deploy rather than a shell script in Cloud Build. See rollbacks in Cloud Deploy for how approval gates and rollback work together.
For gradual traffic migration rather than a full cutover, see canary deployments and blue-green deployments.
Multi-environment pipelines with Cloud Deploy
When you have dev, staging, and production environments, replace the direct deploy step with a Cloud Deploy release creation. Cloud Deploy then manages promotion through each stage, with any approval gates you have configured:
# Create a Cloud Deploy release instead of deploying directly
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
args:
- gcloud
- deploy
- releases
- create
- release-$SHORT_SHA
- --delivery-pipeline=YOUR_PIPELINE_NAME
- --region=YOUR_REGION
- --images=api=YOUR_REGION-docker.pkg.dev/YOUR_PROJECT_ID/api/api:$SHORT_SHAThis creates a Cloud Deploy release that automatically deploys to your dev environment. Once dev looks healthy, you promote to staging, then to production through the approval gate. The same image SHA that passed dev is what eventually reaches production. Nothing is rebuilt between environments.
PR preview environments
A preview environment deploys each pull request to a unique, temporary Cloud Run URL. A reviewer can test the changes against real infrastructure before the PR merges, without checking out the branch and running it locally.
Cloud Run’s tagged revisions make this practical. You deploy the PR to a service in your dev project, using a tag derived from the PR reference or short SHA. The tagged revision gets a stable URL. Post that URL as a comment on the pull request so reviewers can navigate directly to the preview.
steps:
- name: 'gcr.io/cloud-builders/docker'
args:
- build
- -t
- YOUR_REGION-docker.pkg.dev/YOUR_DEV_PROJECT/api/api:$SHORT_SHA
- .
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'YOUR_REGION-docker.pkg.dev/YOUR_DEV_PROJECT/api/api:$SHORT_SHA']
# Deploy to dev project with a PR-specific tag — no production traffic, ever
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
args:
- gcloud
- run
- deploy
- api-service
- --image=YOUR_REGION-docker.pkg.dev/YOUR_DEV_PROJECT/api/api:$SHORT_SHA
- --region=YOUR_REGION
- --no-traffic
- --tag=pr-$SHORT_SHA
- --project=YOUR_DEV_PROJECTA few things to get right with preview environments:
- Always deploy to a dev project, not production. Preview revisions should be completely isolated from your production service and billing account.
- Write a clean-up workflow. Set up a separate trigger that fires when a pull request closes and removes the tagged revision. Idle preview revisions accumulate and attract a small but unnecessary ongoing cost.
- Never shift production traffic from a PR pipeline. The
—no-trafficflag keeps the preview revision accessible only at its tagged URL. Production users are never affected.
If you do not automate clean-up, tagged revisions from merged or closed PRs will keep accumulating in your dev project. Each one sits idle but still counts toward your Cloud Run instance limits and adds a small amount to your bill. Add a pipeline step triggered on PR close that deletes the revision tag or scales the revision to zero.
For secrets that preview environments need (API keys, database credentials, and similar), see secrets in CI/CD pipelines for how to inject them safely without logging them in the build output.
Common mistakes
Deploying to production on every branch push. Configure your Cloud Build trigger to fire only on pushes to main or your release branch. Feature branches should run tests only. Without branch filters, pushing a work-in-progress branch can trigger a production deployment.
Building the Docker image before running tests. If tests fail, you have spent Cloud Build minutes on an image you will throw away. Put the test step first and use
waitForto ensure the build only starts after tests pass.Using the default compute service account for Cloud Run. The default service account has broader permissions than any individual service needs. Create a dedicated, least-privilege account for each Cloud Run service and set it explicitly at deploy time.
Shifting traffic before verifying behaviour. A Cloud Run startup health check only confirms the container started. It does not verify that your application responds correctly to real requests. Test the tagged revision URL before shifting production traffic.
Running preview environments in the production project. Preview revisions accumulate in your revision list, add noise to your logs, and share billing with production. Use a separate dev project for all PR deployments.
Hardcoding project IDs, regions, and service names without documenting them. When someone needs to reproduce the pipeline in a different environment, undocumented hardcoded values cause confusion. Use Cloud Build substitution variables or add a clear comment whenever a value is environment-specific.
Before you automate deployments, estimate what your workload will cost. Use the Cloud Run cost calculator to model request volume and resource settings so there are no billing surprises after launch.
Summary
- Pipeline order: git push → Cloud Build trigger → tests → build → push to Artifact Registry → deploy to Cloud Run
- Run tests before building the image so the pipeline fails fast at the cheapest possible point
- Tag images with
$SHORT_SHAso every running revision is traceable to a specific commit - The Cloud Build service account needs
artifactregistry.writer,run.developer, andiam.serviceAccountUser - Use
—no-trafficto deploy a new revision without immediately shifting production traffic; shift traffic only after verification - For multi-environment pipelines with approval gates, use Cloud Deploy instead of direct
gcloud run deploycalls - Deploy PR preview environments to a dev project, not production, and clean them up when PRs close
- Trigger production deployments only from the main or release branch, not from every feature branch push
Frequently asked questions
What triggers a Cloud Run CI/CD pipeline?
A Cloud Build trigger watches your source repository for push or merge events. When you push to main, the trigger fires and the pipeline starts automatically. For production, use a tag trigger or require a manual Cloud Deploy promotion instead of auto-deploying on every push.
Should I use Cloud Build or Cloud Deploy for Cloud Run CD?
For a single environment, a gcloud run deploy step in Cloud Build is simpler and needs less setup. For multi-environment pipelines with dev, staging, and production and with approval gates, add Cloud Deploy. Start simple and graduate to Cloud Deploy when the complexity justifies it.
How do I avoid downtime during a Cloud Run deployment?
Cloud Run performs zero-downtime deployments by default — new revisions come up before traffic shifts. For extra safety, use --no-traffic to deploy the revision first, run smoke tests against the tagged URL, then shift traffic only after verification.
What service account should my Cloud Run service use?
Create a dedicated, least-privilege service account for each Cloud Run service. The default compute service account has broader permissions than any individual service needs. Set it explicitly in your gcloud run deploy command or Terraform configuration.
Can I create preview environments for pull requests?
Yes. Cloud Run tagged revisions make PR preview environments practical. A pipeline triggered on pull request events builds and deploys each PR to a unique tagged URL in a dev project. Reviewers test against real infrastructure before the PR merges.