Google’s own engineering team describes its serverless platform in blunt, functional terms: “a fully-managed compute environment for deploying and scaling serverless containerized microservices,” according to a Google Cloud engineering blog post. Strip away the marketing and the pitch holds up: hand the service a container image, it runs your code, scales it to match traffic, and bills you by the fraction of a second before dropping back to zero cost the moment nobody’s using it.
This tutorial walks through deploying a real application to Google Cloud Run, from a blank Google Cloud project to a production-ready service with a custom domain, tuned autoscaling, injected secrets, and automated deployments from source, in 13 steps, roughly 80 minutes. By the end you’ll know what Cloud Run actually costs at real traffic volumes, how its container-first model compares to AWS Fargate, AWS Lambda, and Azure Container Apps, and which configuration mistakes turn a free-tier side project into a bill you didn’t plan for.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What Is Google Cloud Run, and Why It Matters in 2026
Google Cloud Run is a fully managed platform for running containers without provisioning servers, managing a Kubernetes cluster, or configuring autoscaling rules by hand. You build a standard container image, push it to a registry, and point Cloud Run at it. The platform handles routing, TLS, horizontal scaling, and billing, charging only for the compute time your container actually consumes.
The detail that separates Cloud Run from function-as-a-service platforms like AWS Lambda or Azure Functions is what it accepts as input: any container image, in any language, with any dependencies baked in, as long as it listens on the port Cloud Run assigns through the PORT environment variable. That means an existing Dockerized application, a legacy binary wrapped in a minimal container, or a multi-process service can move to Cloud Run largely unchanged, instead of being rewritten to fit a specific function-handler signature and a narrow list of supported runtimes.
Under the hood, Cloud Run’s architecture traces back to Knative, the open-source Kubernetes-based platform Google built for exactly this pattern. As ZDNet put it when the platform was introduced, “Cloud Run will operate on Google Kubernetes Engine and has built-in Knative, an API and runtime environment.” That heritage is why Cloud Run behaves consistently whether Google is running the control plane for you or you’re running Knative yourself on GKE: the deployment model and API surface stay the same either way.
The feature that does the most work for cost control is scale-to-zero. When a service sits idle, Cloud Run removes running instances entirely rather than keeping a minimum footprint warm and billing for it. One developer walkthrough summarized it simply: “Cloud Run can scale the number of instances down to zero.” For a side project, an internal tool, or a staging environment that only sees traffic during business hours, that single behavior can mean the difference between a few dollars a month and nothing at all.
What makes Cloud Run worth learning specifically in 2026 is the platform’s expansion beyond typical request-response web APIs. Google first took NVIDIA L4 GPU support for Cloud Run to general availability at Google Cloud Next 2025 in April 2025, and by August 2026 that GPU tier was priced at $0.0001867 per second without zonal redundancy, letting teams run AI inference workloads on the same serverless container model instead of standing up a dedicated GKE cluster just to serve a model endpoint. Google pushed further on the AI front on April 22, 2026, announcing that Cloud Run now integrates directly with the Gemini Enterprise Agent Platform and AI Studio, backed by support for at least one NVIDIA Blackwell GPU tier for the heavier inference workloads those agent platforms demand. At Google Cloud Next ’26 in April 2026, Google also pushed further into stateful territory, moving Cloud Run instances into preview specifically for 24/7 agents and other long-lived workloads that need to hold state between requests rather than restart cold every time. Combined with two distinct billing modes, request-based CPU allocation for typical HTTP APIs and instance-based allocation for services doing background work or holding persistent connections, Cloud Run in 2026 covers a wider range of workloads than the “stateless web API” niche it started in.
Cloud Run Services vs. Cloud Run Jobs
Everything through Step 9 of this tutorial deploys a Cloud Run service: a container that stays ready to handle inbound HTTP or gRPC requests, scaling instances up and down as traffic changes. Cloud Run also supports jobs, a separate resource type built for work that needs to run to completion rather than sit and wait for a request, such as a nightly data export, a database migration, or a batch image-processing task. A job runs your container, executes to completion (or to a configured number of tasks in parallel), and exits, with no listening port required at all. Jobs picked up a cost lever of their own in October 2026, when Google added deferred execution that lets a job wait up to 12 hours for lower-priced capacity instead of running immediately at standard rates, a useful trade for batch work that isn’t time-sensitive. Google previewed the idea a year earlier, launching Cloud Run worker pools for Kafka consumers in private preview at Google Cloud Next 2025 in April 2025, and as of the April 2026 Cloud Run 7.0.0-GA release, that resource type graduated to general availability as a third first-class option, worker pools, with the same sandbox isolation that protects services and jobs now extended to cover it, so pull-based background workers get the same security model without pretending to be an HTTP service. The three share the same container images, the same Artifact Registry setup, and the same billing model, so the choice between them comes down to one question: does this workload respond to requests, run once and finish, or pull continuously from a queue?
Prerequisites: What You Need Before You Start
You don’t need every item below for every step, but for the full walkthrough have these ready:
- A Google Cloud account with billing enabled (new accounts typically include trial credit, which comfortably covers everything in this guide)
- The Google Cloud CLI (latest release) installed and authenticated
- Docker (latest Desktop or Engine release) installed and running locally
- Node.js 20 LTS or later for the sample application and complete working project
- A GitHub account if you plan to follow the CI/CD automation step
- A domain name you control, only if you’re following the custom domain step
- Basic familiarity with the command line, Docker, and how HTTP servers handle a port
- Budget: the free tier covers nearly everything in this tutorial. Expect well under a dollar in charges even if you leave test services running for a few days
Time estimate: about 80 minutes for all 13 steps, longer if you’re also registering a new domain or waiting on DNS propagation.
Google Cloud Run Pricing in 2026: What It Actually Costs
Cloud Run bills in two ways, and picking the right one matters more than most people realize before their first bill arrives. Request-based billing only charges for CPU while your container is actively handling a request, which is the default and the cheapest option for typical HTTP APIs with idle time between calls. Instance-based billing keeps CPU allocated for the full lifetime of an instance, which costs more per second but is the only option that makes sense for background jobs, WebSocket connections, or anything that needs to do work between requests. Since Google Cloud Next ’26 in April 2026, both modes can also be capped: Cloud Run billing caps let you set a hard monthly spend limit that automatically stops a service once it’s reached, a safety net worth turning on for any workload where a traffic spike or a runaway loop shouldn’t translate into a surprise invoice (source: Cloud Run pricing).
As recently as July 11, 2026, Cloud Run’s pricing page still listed the Tier 1 request-based rate of $0.000024 per vCPU-second and $0.0000025 per GiB-second alongside the 2-million-free-requests-a-month allotment, according to Officebook.dev. Even earlier, a January 2026 Cloud Run pricing summary from LatestFromTechGuy had already pegged the per-request overage charge at $0.40 per million requests, a figure that held steady through every rate change since. That changed with the pricing page’s August 2026 refresh, which cut default request-based compute pricing to $0.000018 per vCPU-second and $0.000002 per GiB-second, plus $0.40 per million requests beyond the free allotment. The monthly free tier for request-based billing covers 2 million requests, 180,000 vCPU-seconds, and 360,000 GiB-seconds, which is generous enough that a lot of small APIs and internal tools never generate a bill at all. Instance-based billing now lines up with that same per-second rate ($0.000018 per vCPU-second, $0.000002 per GiB-second) but keeps its own separate free tier of 240,000 vCPU-seconds and 450,000 GiB-seconds per month (source: Cloud Run pricing).
| Billing mode | vCPU price | Memory price | Free tier (monthly) |
|---|---|---|---|
| Request-based (Tier 1) | $0.000024/vCPU-sec | $0.0000025/GiB-sec | 2M requests, 180,000 vCPU-sec, 360,000 GiB-sec |
| Instance-based | $0.000018/vCPU-sec | $0.000002/GiB-sec | 240,000 vCPU-sec, 450,000 GiB-sec |
| Requests (beyond free tier) | $0.40 per million requests | 2 million included | |
Put into a concrete example: a service handling 500,000 requests a month, each using 0.5 vCPU and 512 MB of memory for about 200 milliseconds, stays entirely inside the free tier. That’s roughly 50,000 vCPU-seconds and 50,000 GiB-seconds of usage, well under the 180,000 and 360,000 free allotments. Scale that same service to 5 million requests a month and the math changes: around 320,000 billable vCPU-seconds ($7.68), 140,000 billable GiB-seconds ($0.35), and 3 million billable requests ($1.20), for a total of roughly $9.23 a month. For comparison, AWS Lambda’s always-free monthly tier covers 1,000,000 requests and 400,000 GB-seconds (source: AWS Lambda pricing), a smaller request allowance than Cloud Run’s but a comparable compute allotment, so the two are closer in practice than the headline numbers suggest.
Choosing Between Request-Based and Instance-Based Billing
Default to request-based billing unless you have a specific reason not to. It’s cheaper for the overwhelming majority of web APIs, since most requests spend a fraction of a second doing actual CPU work and the rest waiting on a database or an upstream call, and Cloud Run doesn’t charge for that waiting time under request-based billing. Switch to instance-based billing only when your service genuinely needs CPU available outside the request-response cycle: a WebSocket server holding open connections, a background worker pulling from a queue, or anything running a timer or a cache-warming routine between requests. Picking instance-based billing for a plain REST API is the single easiest way to overpay for Cloud Run, since you’d be paying for CPU the container isn’t using.
Step 1: Install and Initialize the Google Cloud CLI
Everything in this tutorial runs through gcloud, the command-line interface for Google Cloud. Install the latest release for your platform (source: Google Cloud CLI install guide), then authenticate and confirm the install:
curl https://sdk.cloud.google.com | bash
exec -l $SHELL
gcloud init
gcloud auth login
gcloud version
gcloud init walks you through picking or creating a project and a default region. If you already have a project you’d rather reuse, point gcloud init at it now. Otherwise, Step 2 creates a fresh one. Run gcloud version periodically throughout this guide (or gcloud components update) since Cloud Run features move quickly and an outdated CLI is a common source of “flag not recognized” errors later on.
Step 2: Create a GCP Project and Enable Billing
Cloud Run resources live inside a Google Cloud project, and every project needs an active billing account linked before you can enable paid APIs, even if you never leave the free tier. Create a dedicated project for this tutorial so cleanup later is a single command:
gcloud projects create my-cloud-run-demo --name="Cloud Run Demo"
gcloud config set project my-cloud-run-demo
gcloud billing accounts list
gcloud billing projects link my-cloud-run-demo --billing-account=XXXXXX-XXXXXX-XXXXXX
Replace the billing account ID with the one returned by gcloud billing accounts list, and swap my-cloud-run-demo for a project ID of your own since project IDs are globally unique across all of Google Cloud. If you don’t have a billing account yet, the Cloud Console prompts you to create one the first time you try to link a project.
Step 3: Enable the Cloud Run, Artifact Registry, and Cloud Build APIs
New Google Cloud projects start with almost every API disabled. Turn on the three services this tutorial needs in one command:
gcloud services enable run.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.googleapis.com
This takes a minute or two the first time. Artifact Registry stores the container images you’ll build in Step 8, and Cloud Build powers both the local-to-registry push workflow and the automated CI/CD pipeline in Step 13.
Step 4: Install and Verify Docker
You’ll build and test the container image locally before it ever touches Google Cloud, so a working Docker install matters (source: Docker get-started guide). Confirm it’s running:
docker --version
docker run hello-world
If hello-world pulls and runs cleanly, printing its confirmation message, Docker is ready. If it hangs or fails to connect, the Docker daemon likely isn’t running yet, the most common culprit if you just installed Docker Desktop and haven’t opened the application itself.
Step 5: Build a Sample Application to Deploy
Create a small Express API to deploy. This is the piece that matters most for Cloud Run compatibility: the server has to read its port from the PORT environment variable rather than hardcoding one, since Cloud Run assigns that value at runtime and expects the container to honor it.
mkdir cloud-run-demo && cd cloud-run-demo
npm init -y
npm install express
Create server.js:
const express = require("express");
const app = express();
const port = process.env.PORT || 8080;
app.get("/", (req, res) => {
res.send("Hello from Google Cloud Run");
});
app.get("/health", (req, res) => {
res.json({ status: "ok" });
});
app.listen(port, () => {
console.log(`Listening on port ${port}`);
});
Two routes are enough for this walkthrough: a root endpoint to confirm the service is reachable, and a /health endpoint you’ll reuse for local testing and later as a target for uptime checks.
Step 6: Write a Production-Ready Dockerfile
Keep the image small and run as a non-root user. A bloated base image is one of the more common reasons Cloud Run cold starts run longer than they need to, since Cloud Run has to pull and start the full image before your first request completes.
FROM node:20-slim AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
EXPOSE 8080
USER node
CMD ["node", "server.js"]
The node:20-slim base strips out documentation, build tools, and other packages you don’t need at runtime. npm ci --omit=dev installs from the lockfile and skips devDependencies entirely, which keeps both the image size and the attack surface down.
Step 7: Build and Test Your Container Locally
Test the exact image you’re about to ship before it ever reaches Google Cloud. This is the fastest way to catch a missing dependency or a port misconfiguration:
docker build -t cloud-run-demo .
docker run -p 8080:8080 -e PORT=8080 cloud-run-demo
In a second terminal, confirm the health endpoint responds:
curl http://localhost:8080/health
{"status":"ok"}
If that output matches, the container behaves the same way locally as it will on Cloud Run, since you’re passing PORT explicitly, the same way Cloud Run does at deploy time.
Step 8: Push Your Image to Artifact Registry
Cloud Run pulls images from a container registry, and Artifact Registry is Google’s current recommended registry for that. Create a repository, authenticate Docker against it, then tag and push your image:
gcloud artifacts repositories create demo-repo \
--repository-format=docker \
--location=us-central1
gcloud auth configure-docker us-central1-docker.pkg.dev
docker tag cloud-run-demo \
us-central1-docker.pkg.dev/my-cloud-run-demo/demo-repo/cloud-run-demo:v1
docker push \
us-central1-docker.pkg.dev/my-cloud-run-demo/demo-repo/cloud-run-demo:v1
Swap my-cloud-run-demo for your actual project ID and pick a region close to your users. Cloud Run deploys to a specific region, and keeping the registry and the service in the same region avoids unnecessary cross-region image pulls on every deploy.
Step 9: Deploy Your Service to Google Cloud Run
This is the step everything else built toward. One command turns a pushed image into a running, publicly reachable service (source: Cloud Run deploy documentation):
gcloud run deploy cloud-run-demo \
--image us-central1-docker.pkg.dev/my-cloud-run-demo/demo-repo/cloud-run-demo:v1 \
--platform managed \
--region us-central1 \
--allow-unauthenticated
Expect output like this:
Deploying container to Cloud Run service [cloud-run-demo] in project [my-cloud-run-demo] region [us-central1]
✓ Deploying new service... Done.
Service [cloud-run-demo] revision [cloud-run-demo-00001-abc] has been deployed
and is serving 100 percent of traffic.
Service URL: https://cloud-run-demo-xxxxxxxxxx-uc.a.run.app
Visit the Service URL and you should see “Hello from Google Cloud Run.” The --allow-unauthenticated flag makes the service publicly reachable, which is fine for a demo API but worth reconsidering for anything internal, since the alternative is IAM-gated access where only explicitly authorized identities can invoke the service. IAM permissions on Cloud Run aren’t just a convenience setting, either: on April 2, 2025, Google patched a Cloud Run IAM flaw that had let attackers access container images across potentially thousands of misconfigured services, per The Hacker News, a reminder that scoping access tightly is a security control, not just a cost-saving one.
Every deploy, including this one, creates a new immutable revision rather than overwriting the previous version in place. That’s worth understanding early, because it’s what makes rollbacks and gradual traffic splitting possible later: a revision is a specific combination of a container image, environment variables, and resource settings, frozen at deploy time. Redeploying doesn’t delete the old revision, it just shifts traffic to the new one, so rolling back a bad deploy is a routing change, not a rebuild.
Step 10: Configure Environment Variables and Secrets
Plain configuration values go in as environment variables. Sensitive values, such as API keys or database passwords, should come from Secret Manager instead of sitting in plaintext on the service configuration:
gcloud run services update cloud-run-demo \
--region us-central1 \
--set-env-vars NODE_ENV=production,LOG_LEVEL=info
gcloud run services update cloud-run-demo \
--region us-central1 \
--set-secrets DB_PASSWORD=db-password:latest
That second command mounts the latest version of a Secret Manager secret named db-password as the environment variable DB_PASSWORD inside the container, readable through process.env.DB_PASSWORD exactly like any other variable. Encryption keys got a similar upgrade on April 22, 2025, when Google took Cloud KMS Autokey to general availability for Cloud Run, giving each service its own automatically provisioned managed encryption key instead of relying solely on Google-managed defaults. One flag behavior worth knowing before it surprises you: --set-env-vars replaces the entire variable list on the revision rather than merging with what’s already there. Use --update-env-vars instead if you only want to add or change one value without wiping the rest. If you’re already managing secrets on AWS, the underlying pattern of granting a runtime identity narrow, revocable read access is the same one covered in our AWS Secrets Manager setup guide, just under a different product name.
Step 11: Connect a Custom Domain and Enforce HTTPS
The default .run.app URL works fine for testing, but a real service needs a domain you control. Map one directly through gcloud:
gcloud run domain-mappings create \
--service cloud-run-demo \
--domain api.example.com \
--region us-central1
You’ll need to verify ownership of the domain in Search Console first if you haven’t already, and add the DNS records Google returns at your registrar. Cloud Run provisions and renews a managed TLS certificate for the mapped domain automatically once verification and DNS propagation finish, so there’s no certificate file to generate or rotate by hand.
Step 12: Tune Autoscaling, Concurrency, and CPU Allocation
Cloud Run’s defaults work fine for a demo, but production traffic patterns usually call for tuning three settings together. Concurrency controls how many simultaneous requests one instance handles: the default is 80 and the maximum is 1,000 per instance (source: Cloud Run concurrency documentation). Max instances caps how far a revision can scale out, defaulting to 100 per revision (source: Cloud Run quotas), and hitting that ceiling under a traffic spike shows up as throttled requests rather than a clean scale-up. Since Google Cloud Next ’26 in April 2026, Cloud Run has also let you drive autoscaling off custom metrics, CPU utilization thresholds and concurrency counts, instead of relying solely on the default per-instance concurrency target, which gives workloads with uneven per-request cost a more accurate scaling signal than request count alone.
gcloud run services update cloud-run-demo \
--region us-central1 \
--concurrency 80 \
--min-instances 1 \
--max-instances 20 \
--cpu-boost
Setting --min-instances 1 keeps one warm instance running at all times, trading a small always-on cost for eliminating cold starts on the first request after idle time. --cpu-boost temporarily grants extra CPU during container startup, which shortens the cold starts you do experience. Raise --max-instances from the default of 100 only once you’ve confirmed your downstream dependencies (a database, a third-party API) can actually handle that much concurrent load. That ceiling exists as much to protect what’s behind Cloud Run as to control cost.
Step 13: Automate Deployments with Cloud Build CI/CD
Manually rebuilding, pushing, and redeploying gets old fast. Add a cloudbuild.yaml file to the project root to automate the whole sequence:
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'us-central1-docker.pkg.dev/$PROJECT_ID/demo-repo/cloud-run-demo:$SHORT_SHA', '.']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'us-central1-docker.pkg.dev/$PROJECT_ID/demo-repo/cloud-run-demo:$SHORT_SHA']
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- run
- deploy
- cloud-run-demo
- --image=us-central1-docker.pkg.dev/$PROJECT_ID/demo-repo/cloud-run-demo:$SHORT_SHA
- --region=us-central1
Then create a trigger so every push to your main branch kicks off that build automatically:
gcloud builds triggers create github \
--repo-name=cloud-run-demo \
--repo-owner=YOUR_GITHUB_USERNAME \
--branch-pattern="^main$" \
--build-config=cloudbuild.yaml
Cloud Build is the native path since it needs no extra credentials beyond authorizing the GitHub connection once. If your team already standardized on a different CI system for other projects, that pipeline can call the same three gcloud commands from Steps 8 and 9 just as easily. Cloud Run doesn’t care which system triggered the deploy. For teams that don’t need a custom container build at all, the Zip Deploy preview Google introduced in May 2026 skips the Docker build step entirely, uploading source directly and cutting typical deploy time from roughly 90 seconds down to 10-20 seconds, a shortcut worth trying once it’s generally available if your app doesn’t need a custom Dockerfile.
Google Cloud Run vs AWS Fargate vs Lambda vs Azure Container Apps
Cloud Run’s closest competitors split into two groups: container platforms that need a task to keep running, and function platforms that bill per invocation. Cloud Run sits in between, billing like a function platform while accepting any container like a task platform.
| Platform | Billing unit | Free tier | Scale to zero | Best fit |
|---|---|---|---|---|
| Google Cloud Run | vCPU-second + GiB-second (or per-instance) | 2M requests, 180,000 vCPU-sec, 360,000 GiB-sec/mo | Yes | Any containerized HTTP/gRPC app, including GPU inference |
| AWS Lambda | GB-second + per-request | 1,000,000 requests + 400,000 GB-seconds/mo | Yes | Short, event-driven functions in supported runtimes |
| AWS Fargate | vCPU-hour + GB-hour while the task runs | None published | No, a task must stay running | Long-running containers, existing ECS/EKS workloads |
| Azure Container Apps | vCPU-second + GiB-second | Free monthly grant included | Yes | Microservices already standardized on Azure |
The practical distinction that matters most is what happens between requests. Lambda and Cloud Run both scale to true zero and bill nothing while idle. Fargate keeps a task running as long as its service definition says it should, which is the right model for something like a long-lived worker process but means you’re paying for the full runtime whether or not it’s doing anything. For a deeper look at that specific tradeoff, our AWS Fargate vs Lambda comparison and ECS vs EKS vs Fargate breakdown cover the AWS side in more depth, and our AWS vs Azure vs Google Cloud comparison covers the three platforms at large.
Container portability is the other differentiator worth weighing. Because Cloud Run and Azure Container Apps both run arbitrary OCI images, moving a workload between them (or to a self-hosted Knative or Kubernetes cluster later) mostly means changing a deploy target, not rewriting handler functions. Lambda’s tighter runtime model buys faster cold starts for small functions in exchange for that portability.
Cloud Run isn’t the right fit for every workload, either. A service that needs to hold thousands of persistent connections indefinitely, run for days without restarting, or rely on kernel-level networking features tends to fit a dedicated GKE or EC2-based deployment better than a platform built around request-driven autoscaling. And teams already running a large, mature EKS or ECS footprint on AWS usually get more value from extending what they have than introducing a second cloud provider just for one new service.
Complete Working Project: A Node.js API Deployed End to End on Cloud Run
Here’s the full project assembled in one place: a small Express API with a real data endpoint, containerized, and deployed with the exact commands from the steps above. Start fresh:
mkdir notes-api && cd notes-api
npm init -y
npm install express
server.js, now with a minimal in-memory notes endpoint alongside the health check:
const express = require("express");
const app = express();
app.use(express.json());
const port = process.env.PORT || 8080;
const notes = [];
app.get("/health", (req, res) => res.json({ status: "ok" }));
app.get("/notes", (req, res) => res.json(notes));
app.post("/notes", (req, res) => {
const note = { id: notes.length + 1, text: req.body.text || "" };
notes.push(note);
res.status(201).json(note);
});
app.listen(port, () => {
console.log(`notes-api listening on port ${port}`);
});
Reuse the same Dockerfile from Step 6 (just swap the working directory contents), then build, tag, push, and deploy in sequence:
docker build -t notes-api .
docker tag notes-api us-central1-docker.pkg.dev/my-cloud-run-demo/demo-repo/notes-api:v1
docker push us-central1-docker.pkg.dev/my-cloud-run-demo/demo-repo/notes-api:v1
gcloud run deploy notes-api \
--image us-central1-docker.pkg.dev/my-cloud-run-demo/demo-repo/notes-api:v1 \
--region us-central1 \
--allow-unauthenticated \
--min-instances 0 \
--max-instances 10
Test it against the returned Service URL:
curl -X POST https://notes-api-xxxxxxxxxx-uc.a.run.app/notes \
-H "Content-Type: application/json" \
-d '{"text":"first note from Cloud Run"}'
{"id":1,"text":"first note from Cloud Run"}
curl https://notes-api-xxxxxxxxxx-uc.a.run.app/notes
[{"id":1,"text":"first note from Cloud Run"}]
That’s a complete, working, publicly reachable API running on Cloud Run’s free tier. The in-memory array resets every time the service scales to zero and a fresh instance starts, which is the correct behavior to expect and exactly why the next section flags persistent local state as a pitfall. Google’s April 2026 ephemeral disk preview does add per-instance local storage for caching or scratch space between requests on the same instance, but it still doesn’t survive a scale-to-zero event, so swap the array for Firestore or Cloud SQL before this becomes anything more than a demo.
Common Pitfalls When Deploying to Google Cloud Run
Most Cloud Run mistakes aren’t dramatic, they’re small configuration choices that quietly cause failed deploys, slow responses, or an unexpected bill. In roughly the order they show up:
- Hardcoding a port instead of reading
PORT. A container that listens on 3000 no matter what Cloud Run assigns will fail its startup probe every time, even though it runs perfectly fine locally. - Treating the container filesystem as persistent. Cloud Run instances are stateless, and anything written to local disk disappears the moment an instance scales down. Use Cloud Storage, Firestore, or a real database for anything that needs to survive.
- Leaving
min-instancesat 0 for latency-sensitive services. It’s the cheapest setting, but the next request after idle time pays a cold-start penalty, and that penalty grows with image size and runtime startup cost. - Shipping a bloated base image. A full general-purpose image with unused build tools and package managers slows down both the build and every cold start compared to a slim or distroless base.
- Enabling
--allow-unauthenticatedout of habit. Fine for a public API, risky for anything internal. Internal services should stay IAM-gated and get invoked through service-to-service authentication instead of sitting open on the public internet. - Assuming
--set-env-varsmerges with existing variables. It replaces the full list on that revision. Use--update-env-varswhen you only want to add or change one value without wiping the others. - Ignoring the default
max-instancesceiling. A traffic spike that hits the default cap of 100 instances doesn’t fail loudly, it just starts returning throttled responses, and that’s usually discovered after users notice, not before. - Picking instance-based billing without needing it. It’s the right call for background work, but applied to a typical request-response API it quietly charges for idle CPU time that request-based billing would never bill in the first place.
Troubleshooting Google Cloud Run: 10 Common Errors and Fixes
Most of these trace back to one of three root causes: a container that isn’t listening on the right port, an IAM identity missing one specific permission, or a resource limit that’s lower than the actual traffic pattern requires. Check those three angles first, and since April 2026 you also have a faster way to check them: Cloud Run’s preview SSH access, invoked with gcloud run services ssh, lets you shell directly into a running container instead of piecing the problem together from logs alone.
| Error or symptom | Likely cause | Fix |
|---|---|---|
| Container failed to start and listen on the port | App hardcodes a port instead of reading the PORT environment variable | Bind to process.env.PORT (or the language equivalent), never a fixed value |
| PERMISSION_DENIED on deploy | Deploying identity lacks the Cloud Run Admin role | Grant roles/run.admin and roles/iam.serviceAccountUser to the deploying account |
| “denied” pushing to Artifact Registry | Docker isn’t authenticated against the registry host | Run gcloud auth configure-docker for the target region before pushing |
| Deploy succeeds but service returns 503 | Container crashes right after startup, often a missing dependency or bad env var | Check the Logs tab in the Cloud Run console for the startup stack trace |
| Requests hang, then return 504 | Backend work exceeds the configured request timeout | Raise –timeout, or move long-running work to a Cloud Run job instead |
| Sudden wave of 429 responses under load | Revision hit its max-instances ceiling | Raise –max-instances, or check why each instance handles fewer requests than expected |
| Cold starts take several seconds | min-instances at 0 combined with a large image or slow runtime init | Set –min-instances 1 for latency-sensitive paths, or enable –cpu-boost |
| Secret not injected, service fails to start | Runtime service account lacks secretmanager.secretAccessor on that secret | Grant the role to the exact service account Cloud Run runs as, not your own account |
| Custom domain mapping stuck “Pending” | DNS records not yet propagated, or domain ownership unverified | Verify the domain in Search Console first, then confirm DNS records with dig |
| Image builds locally but Cloud Build fails | Misconfigured cloudbuild.yaml step, or the Cloud Build service account lacks a permission | Check the build log in Cloud Build history and confirm the service account has roles/run.admin |
Advanced Tips for Production Cloud Run Workloads
Once the basics are running reliably, these changes are what separate a working demo from a service that holds up under real production traffic. Google itself gave part of this a first-party boost in April 2025, when Cloud Run multi-region HA apps entered public preview across at least two regions, and by April 2026 Google had simplified that further, letting Cloud Run service health trigger cross-region failover through readiness probes in just two clicks. The tips below still matter for teams handling redundancy manually or going beyond what that preview covers:
- Use a Serverless VPC Access connector to reach Cloud SQL, Memorystore, or other resources on a private IP, instead of exposing a database on the public internet just so Cloud Run can reach it.
- Split traffic gradually with revision tags. Deploy a new revision without shifting traffic to it immediately, route a small percentage of real users to it as a canary, and only cut over fully once it’s proven stable.
- Use Cloud Run jobs, not services, for batch and cron-style work. Jobs run a container to completion for a defined task instead of waiting to handle inbound HTTP requests, which is a cleaner fit for scheduled data processing. If those jobs are steps in a larger durable workflow, Temporal’s October 2026 pay-as-you-go plan dropped the minimum spend to $0 a month for cloud-based workloads, making it cheaper to trigger Cloud Run jobs from a Temporal workflow without committing to a fixed contract.
- Enable
--cpu-booston any latency-sensitive service so the first requests after a cold start get extra CPU during initialization, shrinking the window where response times spike. - Budget GPU-backed services carefully. NVIDIA L4 support opens up inference workloads on Cloud Run, but GPU-backed instances are priced differently from CPU-only ones, and idle GPU capacity is the fastest way to blow through a budget you sized for a CPU workload.
- Deploy to multiple regions behind a global load balancer for an audience spread across continents, rather than routing every user through a single regional deployment and eating the latency cost.
Frequently Asked Questions
Is Google Cloud Run free?
For low-traffic services, effectively yes. The monthly free tier covers 2 million requests, 180,000 vCPU-seconds, and 360,000 GiB-seconds under request-based billing (source: Cloud Run pricing). Past that, you pay per-second for exactly what your container uses.
What’s the difference between Cloud Run services and Cloud Run functions?
Services deploy a full container image you build yourself and can hold traffic indefinitely or scale to zero between requests. Cloud Run functions is the lighter, source-based deployment model (the evolution of Cloud Functions), billed by memory and vCPU allocation per 100 milliseconds, with a maximum allocation of 32,768 MB and 8 vCPU on the largest tier.
Does Cloud Run support GPUs?
Yes, Cloud Run supports NVIDIA L4 GPUs for services that need accelerated inference, which lets teams serve AI models on the same serverless container model covered in this tutorial instead of provisioning a dedicated GKE cluster.
What programming languages does Cloud Run support?
Any language or runtime that can be packaged into a container. Cloud Run runs standard container images directly rather than restricting deployments to a fixed list of managed language runtimes, which is the core difference from function-as-a-service platforms. Even the managed-runtime side keeps pace, though: Google added Python 3.13 support on April 21, 2025, bringing the number of supported Python runtime versions for Cloud Run functions up to four, and in September 2026 Google brought that same momentum to Go, taking Go 1.27 to general availability as a supported Cloud Run runtime.
How does Cloud Run pricing compare to AWS Lambda?
Cloud Run’s request-based free tier covers 2 million requests, 180,000 vCPU-seconds, and 360,000 GiB-seconds a month. Lambda’s always-free tier covers 1,000,000 requests and 400,000 GB-seconds a month (source: AWS Lambda pricing). Cloud Run’s request allowance is larger, Lambda’s compute allowance is comparable, and the bigger practical difference is that Cloud Run runs any container while Lambda expects code in a supported runtime.
Can Cloud Run connect to a database?
Yes. For Cloud SQL, the Cloud SQL Auth Proxy pattern or a Serverless VPC Access connector are the two common paths, letting a Cloud Run service reach a private database instance without exposing it publicly.
Does Cloud Run scale to zero?
Yes, when min-instances is left at its default of 0. An idle service costs nothing beyond the image storage sitting in Artifact Registry until the next request arrives and a new instance starts.
How many concurrent requests can one Cloud Run instance handle?
80 by default, configurable up to a maximum of 1,000 per instance (source: Cloud Run concurrency documentation). The right number depends on whether your workload is CPU-bound or mostly waiting on I/O like database calls.
Getting a container onto Cloud Run takes maybe twenty minutes. Getting it production-ready, tuned concurrency, secrets pulled from Secret Manager instead of hardcoded, a custom domain with managed TLS, and deploys that happen automatically on every push, is the rest of this guide. Start with Step 1’s CLI setup, keep the image slim from Step 6 onward, and the defaults Google ships will carry you further than most tutorials give them credit for.
For more cloud infrastructure tutorials and comparisons, browse the full Cloud Computing section.





