This is Part 2 of the "This is Cloud Run" series. In Part 1, we covered what Cloud Run is, what's behind the curtain, what you get for free, Cloud Run functions, the platform's boundaries, and the migration path to Kubernetes. Now let's get practical.
In Part 1, the question was "should I use Cloud Run?" Here, the question is "how do I get my code onto it?"
This isn't a step-by-step tutorial. This article is the why behind each option, so you can make informed choices instead of copying commands.
Deployment Options
One of Cloud Run's underrated strengths is how many ways you can get your code onto the platform. Between the CLI, the Console, YAML, Terraform, Cloud Build, GitHub Actions, Cloud Deploy, client libraries, and more, there's no shortage of options. I'm not going to cover all of them. Instead, I'll focus on the ones I find most useful across the projects I work on, from quick prototypes to production deployments.
From Source Code
gcloud run deploy my-service --source . --region us-central1
This is the "I just want this running" command. You point gcloud at your source code directory, and Cloud Run handles everything else. But "everything else" hides a multi-step pipeline that's worth understanding:
-
Upload.
gcloudzips your source directory and uploads it to Cloud Build. -
Detect. Cloud Build runs Google Cloud Buildpacks, which inspect your code to determine the language and framework. Found a
requirements.txt? Python. Foundgunicornin the dependencies? That's your server. -
Build. Buildpacks create a container image: secure base image, installed dependencies, configured entry point. If a
Dockerfileis present in your directory, Cloud Build uses that instead of buildpacks. - Push. The built image is pushed to Artifact Registry, Google Cloud's container registry.
- Deploy. Cloud Run pulls the image and deploys it as a new revision.
All from one command. You don't need to know Docker, you don't need to understand container registries, and you don't even need a Dockerfile.
The trade-off is control. You can't customize the base image or run multi-stage builds. If you don't need any of that, source deploy works perfectly fine in production. Many of my services run this way. But if you need precise control over what's in the container, the next option gives you that.
Best for: Quick iterations during development, prototyping, developers new to containers, and the "I just want this running" moments.
From a Pre-Built Container Image
gcloud run deploy my-service \
--image us-docker.pkg.dev/my-project/repo/my-image:v1.2.3 \
--region us-central1
This is the production path. You build your Docker image however you like (locally, in CI, in Cloud Build), push it to Artifact Registry, and deploy by image URL. Notice the flag: --image instead of --source. No build step happens on Google's side. Cloud Run pulls the image and starts running it, which makes the deployment itself much faster.
The key advantage is full control over the build process. Multi-stage builds to keep images small. Custom base images tuned for your runtime. Build-time secrets for private package registries. Whatever your Dockerfile needs. If you care about minimal images, pinned base image versions, and a small attack surface, this is where you get that.
Best for: Production deployments, teams with existing build pipelines, workloads that need custom build steps.
Cloud Run Functions
gcloud run deploy my-function \
--source . \
--function myEntrypoint \
--base-image python312 \
--region us-central1
We covered Cloud Run functions in depth in Part 1, so here we'll focus on the deployment mechanics. Two flags distinguish a function deployment from a service deployment:
-
--function myEntrypointselects which function in your source code to use as the HTTP entry point. Your source can define multiple functions; each deployment serves one. -
--base-image python312selects a managed base image for the runtime. Google manages these base images, including security patches, so you don't maintain aDockerfile.
Supported runtimes include Node.js, Python, Go, Java, .NET, Ruby, and PHP. You can also deploy Cloud Run functions from the Console UI with an inline code editor, which is handy for quick experiments.
Best for: Single-purpose endpoints, webhooks, event handlers, and the LLM API proxy pattern I described in Part 1.
Console UI
The Google Cloud Console provides a point-and-click interface for deploying Cloud Run services. You select a container image from Artifact Registry, configure settings through a guided form (CPU, memory, scaling, environment variables, networking), and deploy without touching the command line.
One thing the Console is surprisingly good for: discovery. Before you memorize CLI flags, clicking through the Console form shows you every configuration option Cloud Run offers. I've used it more than once to discover a setting I didn't know existed, then replicated it in my deployment scripts.
But the Console has an obvious limitation: it's not scriptable. Every deployment is manual, which means it's not repeatable, not version-controllable, and impossible to code review. You can't git diff a click.
Best for: Exploration, one-off deployments, reviewing and tweaking configurations visually, and learning what options exist before writing automation.
gcloud CLI with Full Configuration
You've already seen gcloud run deploy with minimal flags. But the same command is also a full-featured deployment tool with dozens of configuration options. Every Cloud Run configuration is a flag:
gcloud run deploy my-service \
--image us-docker.pkg.dev/my-project/repo/my-image:v1.2.3 \
--region us-central1 \
--memory 512Mi \
--cpu 1 \
--min-instances 0 \
--max-instances 10 \
--concurrency 80 \
--set-env-vars "DB_HOST=10.0.0.1,ENV=production" \
--set-secrets "API_KEY=my-secret:latest" \
--service-account my-sa@my-project.iam.gserviceaccount.com \
--revision-suffix v1-2-3
Memory? A flag. Secrets from Secret Manager? A flag. VPC connectivity? A flag. This makes gcloud commands scriptable, repeatable, and easy to drop into shell scripts or CI/CD pipelines.
One thing that keeps it practical: configuration is sticky. Once you set a flag, it stays on the service until you explicitly change it. So your first deployment might be the long one with --memory, --cpu, --max-instances, and everything else. But subsequent deployments can go back to the simple gcloud run deploy my-service --image my-image:v2 --region us-central1 and all your previous settings carry over. You only specify what you want to change.
Best for: Scripted deployments, shell scripts, CI/CD integration, and when you need precise, repeatable control over every setting.
Declarative YAML
gcloud run services replace service.yaml --region us-central1
CLI flags are imperative: "change these things." YAML is declarative: "this is what I want." You define your entire service configuration in a file, and Cloud Run makes reality match. If something drifted (someone tweaked a setting in the Console), the YAML corrects it. If nothing changed, nothing happens.
The YAML follows the Knative Serving API v1 schema:
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: my-service
labels:
cloud.googleapis.com/location: us-central1
annotations:
run.googleapis.com/ingress: all
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/minScale: '0'
autoscaling.knative.dev/maxScale: '10'
spec:
containerConcurrency: 80
containers:
- image: us-docker.pkg.dev/my-project/repo/my-image:v1.2.3
ports:
- containerPort: 8080
resources:
limits:
memory: 512Mi
cpu: '1'
env:
- name: ENV
value: "production"
This is the same model as Kubernetes and Terraform: you describe what you want, not how to get there. And because the schema is Knative-compatible, these YAML files are portable between Cloud Run and self-hosted Knative on Kubernetes.
A practical tip: you can export an existing service's configuration with gcloud run services describe SERVICE --format export > service.yaml, modify it, and reapply. This is a great way to bring a service that was originally deployed via the Console or CLI into version control.
One important nuance: IAM policies (who can invoke your service) are managed separately from the service definition. The YAML defines the service configuration; gcloud run services add-iam-policy-binding controls access. This is actually good practice, since access control is often managed by a different team.
Best for: GitOps workflows, infrastructure-as-code, teams that want configuration in version control, and teams migrating from Kubernetes or Knative.
Continuous Deployment from Git
Connect a GitHub, GitLab, or Bitbucket repository to Cloud Run through Cloud Build triggers. Every push to a specified branch automatically builds your container and deploys a new revision. You set it up once and forget it.
You can configure this from the Cloud Run Console UI under "Set up continuous deployment," or manually by creating Cloud Build triggers. Either way, the result is the same: push to main, wait a couple of minutes, and your changes are live. Under the hood, it uses the same buildpack pipeline as --source deploys: your code is auto-detected, built into an image, pushed to Artifact Registry, and deployed as a new revision.
The difference between this and the next two options (Cloud Build and GitHub Actions) is simplicity. Continuous deployment from Git is a pre-built pipeline. You don't write build steps or workflow files. The trade-off is flexibility: you can't run tests before deploying, can't deploy to staging first, and can't customize the build beyond what Cloud Build's auto-detection provides. If you need any of those things, keep reading.
Best for: Teams that want automated deployment on every push without building or maintaining custom CI/CD.
Cloud Build
Cloud Build is Google Cloud's serverless CI/CD platform. Where the previous option gives you a pre-built pipeline, Cloud Build gives you the building blocks to assemble your own.
You define your pipeline in a cloudbuild.yaml file at the root of your repository:
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'us-docker.pkg.dev/$PROJECT_ID/repo/my-image:$COMMIT_SHA', '.']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'us-docker.pkg.dev/$PROJECT_ID/repo/my-image:$COMMIT_SHA']
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args: ['run', 'deploy', 'my-service',
'--image', 'us-docker.pkg.dev/$PROJECT_ID/repo/my-image:$COMMIT_SHA',
'--region', 'us-central1']
Each step runs in its own container. The first step builds the Docker image, tagging it with the commit SHA for traceability. The second pushes it to Artifact Registry. The third deploys it to Cloud Run. You can chain as many steps as you need: run tests, lint code, scan for vulnerabilities, deploy to staging, run integration tests against staging, then deploy to production. The $PROJECT_ID and $COMMIT_SHA are built-in substitution variables that Cloud Build populates automatically.
Trigger this pipeline on every push to a branch, on pull requests, or on-demand with gcloud builds submit. That flexibility is the point: Cloud Build is the pipeline, and you decide what goes in it.
Best for: Complex build pipelines, multi-service deployments, teams that need test-before-deploy workflows, and teams already invested in Google Cloud's CI/CD ecosystem.
GitHub Actions
If your code lives on GitHub and your CI/CD already runs on GitHub Actions, Google provides an official action for deploying to Cloud Run:
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: 'projects/123/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
service_account: 'deployer@my-project.iam.gserviceaccount.com'
- uses: google-github-actions/deploy-cloudrun@v2
with:
service: my-service
image: us-docker.pkg.dev/my-project/repo/my-image:${{ github.sha }}
region: us-central1
Authentication is handled via Workload Identity Federation, which lets GitHub Actions authenticate to Google Cloud without service account keys. Instead of storing a JSON key file as a GitHub secret (a secret that never expires and can be copied anywhere), Workload Identity Federation uses short-lived tokens granted through an identity mapping. No keys to store, no keys to rotate, no keys to accidentally leak in a log.
The google-github-actions/deploy-cloudrun action supports both image-based and source-based deployments, and the resulting service URL is available as a workflow output for downstream steps (useful for posting preview links on pull requests).
Best for: Teams with GitHub-centric workflows who want deployment integrated into their existing CI/CD.
Choosing the Right Deployment Method
Here's a quick reference for the methods we covered:
| Method | Deploy Speed | Build Control | Repeatable | Best Scenario |
|---|---|---|---|---|
| From Source | Medium | Low | Yes (CLI) | Prototyping, quick iterations |
| Pre-Built Image | Fast | High | Yes (CI/CD) | Production, custom builds |
| Cloud Run Functions | Medium | Low | Yes (CLI) | Single-purpose endpoints |
| Console UI | Manual | Medium | No | Exploration, learning |
| gcloud CLI (full) | Fast | High | Yes (scripts) | Scripted deploys, CI/CD |
| Declarative YAML | Fast | High | Yes (GitOps) | Infrastructure-as-code |
| Git Continuous Deploy | Medium | Low | Yes (auto) | Simple auto-deploy on push |
| Cloud Build | Medium | High | Yes (pipeline) | Complex CI/CD pipelines |
| GitHub Actions | Medium | High | Yes (pipeline) | GitHub-centric teams |
In practice, most teams follow a natural progression. You start with source deploy for prototyping: one command, instant feedback. As the project matures, you move to pre-built images for reproducibility and control. When the team grows, you add CI/CD (Cloud Build, GitHub Actions, or continuous deployment from Git) so deployments happen automatically and consistently.
You don't need to pick one and stick with it. Use source deploy for your dev environment and image-based deploys for production. Use the Console to explore, then codify what you learned in YAML. The deployment method is a tool, not a commitment.
These aren't the only options. Cloud Run also supports deployment via Terraform, Cloud Deploy for managed continuous delivery pipelines, Cloud Code for IDE integration, client libraries, and the REST API directly. The Cloud Run deployment documentation covers the full list.
Revisions and Traffic Management
Every time you deploy to Cloud Run, it creates a new revision: an immutable snapshot of your service's configuration and container image. Think of revisions as Cloud Run's built-in version control. Your service might accumulate dozens of revisions over its lifetime, each representing a specific deployment. Old revisions stick around and can serve traffic again at any time. Nothing is deleted unless you explicitly remove it.
By default, Cloud Run auto-generates revision names like my-service-00001-abc. That works, but it's not helpful when you're staring at a list of revisions trying to figure out which one introduced a bug. You can set meaningful names with the --revision-suffix flag:
gcloud run deploy my-service \
--image us-docker.pkg.dev/my-project/repo/my-image:v1.2.3 \
--revision-suffix v1-2-3 \
--region us-central1
Now the revision is named my-service-v1-2-3. In a CI/CD pipeline, you might use the Git commit SHA: --revision-suffix=$(git rev-parse --short HEAD). When something goes wrong, you can immediately tell which commit is running.
Traffic Splitting
But the real power of revisions is traffic splitting. Because revisions are immutable and stick around, you can split traffic between them:
gcloud run services update-traffic my-service \
--to-revisions my-service-v1-2-3=95,my-service-v1-3-0=5 \
--region us-central1
This sends 95% of traffic to the old revision and 5% to the new one. Watch the metrics in Cloud Monitoring. If the new revision's error rate and latency look good, shift more traffic. If something's off, one command puts you back:
gcloud run services update-traffic my-service \
--to-revisions my-service-v1-2-3=100 \
--region us-central1
Instant rollback. No redeployment, no rebuild, no downtime. The old revision is still there, already running, ready to take 100% of traffic again. This is the real power of immutable revisions.
While the CLI works well for scripted rollouts, traffic splitting is one of those things that's often easier to do from the Cloud Run Console. You can see all your revisions, drag sliders to adjust percentages, and watch the changes take effect.
You can also use traffic splitting for:
- A/B testing across different versions of your service
- Gradual rollouts where you shift traffic incrementally (5% → 25% → 50% → 100%)
- Blue/green deployments by deploying the new revision with 0% traffic, testing it via a revision tag (see below), then flipping traffic all at once
Revision Tags
Revision tags give individual revisions their own stable URLs without routing any production traffic to them:
gcloud run services update-traffic my-service \
--set-tags staging=my-service-v1-3-0 \
--region us-central1
This creates a URL like https://staging---my-service-abc123-uc.a.run.app that points directly to that revision. Your QA team can test the new version at that URL while production traffic continues hitting the current revision untouched. When you're satisfied, shift traffic. No separate staging environment needed.
You can have multiple tags active at once: staging, canary, pr-42. Each gets its own URL. This is particularly useful in CI/CD pipelines where you want to run automated tests against a deployed revision before routing real users to it.
Conclusion
Cloud Run gives you many paths to deployment, each designed for a different stage of your project. You don't need to use all of them.
The pattern I see most often: teams start with gcloud run deploy --source . and the default configuration. That gets them running in minutes. As the project matures, they move to pre-built images for reproducibility, add CI/CD for automation, and use revisions and traffic splitting for safe rollouts. Every change takes effect on the next deployment, with zero downtime.
Part 3 is coming soon. We'll dive into the configuration options that let you tune Cloud Run for your specific workload: CPU, memory, scaling, networking, secrets, and security. Follow so you don't miss it.
Resources
- Cloud Run documentation
- Cloud Run pricing
- Cloud Run deployment documentation
gcloud run deployreference- Cloud Build
- Google Cloud Buildpacks
- Artifact Registry
- Cloud Run functions
- Cloud Run deploy functions
- Cloud Run revisions
- Cloud Run traffic splitting (rollouts and rollbacks)
- Cloud Monitoring
- Knative Serving API v1 schema
- Cloud Build triggers
- Continuous deployment from Git
google-github-actions/deploy-cloudrun- Workload Identity Federation
- Cloud Deploy
- Cloud Code
- Terraform Cloud Run resource
- Secret Manager
- Direct VPC egress
- Knative
- Kubernetes
- Terraform
Top comments (0)