Deploy Your First Cloud Run Service on Google Cloud
This guide takes you from an empty directory to a live Cloud Run service with a public HTTPS URL. You will write a minimal application, build and push a container image, deploy it, test it, and learn how to update, roll back, and observe it safely. No prior Cloud Run experience needed.
What you will do
By the end of this walkthrough you will have:
- A running Cloud Run service with a stable HTTPS URL
- A container image stored in Artifact Registry
- The ability to deploy updates and roll back to earlier revisions
- Live logs flowing into Cloud Logging automatically
What Cloud Run actually is
Cloud Run is Google Cloud’s serverless container platform. You give it a container image and it handles everything else: provisioning servers, routing HTTPS traffic, scaling up under load, and scaling back down to zero when nobody is using the service.
Think of a Cloud Run service like a phone number at a business. The number never changes — that is your stable URL. But the person who answers the call can be swapped out at any time. Each new deployment creates a new revision (a new person answering), and if the latest revision has a problem, you redirect calls back to the previous one instantly without any caller noticing.
Cloud Run is popular for beginners because there are no virtual machines to configure, no load balancers to wire up, and no Kubernetes clusters to manage. It is also popular for production teams because it scales down to zero, meaning you pay nothing when the service is not handling requests.
The only real constraint: your application must be stateless and must listen
on the port Cloud Run tells it to use via the PORT environment
variable. More on that in Step 3.
To understand how containers work in GCP before you continue, see Container Images in GCP.
Before you start
You need the following before running any commands:
A GCP project with billing enabled. Cloud Run and Artifact Registry are paid services (both have free tiers, but billing must be active).
The gcloud CLI installed and authenticated. Run
gcloud auth loginandgcloud config set project YOUR_PROJECT_ID.Docker installed locally if you want to build images on your machine. You can skip this entirely by using Cloud Build instead. The commands below cover both options.
Required APIs enabled. The first step in the walkthrough enables them for you. If the project already has them enabled, the command is a no-op.
How Cloud Run deployment works
Before jumping into commands, here is what happens at each stage:
You write or already have an app. The only Cloud Run requirement is that it reads the
PORTenvironment variable and listens on it.You package it into a container image. A Dockerfile describes how to build that image. If you would rather skip the Dockerfile entirely, the
—sourceflag does this step automatically using Buildpacks.You push the image to Artifact Registry. This is the image storage service inside your GCP project. It keeps images private, applies IAM controls, and lets Cloud Run pull them securely without needing separate credentials.
You deploy to Cloud Run. The
gcloud run deploycommand tells Cloud Run which image to use, which region to run in, and how much memory and CPU to allocate.Cloud Run creates a revision and gives you a URL. The URL is stable. Future deployments create new revisions but do not change the URL.
You can split traffic between revisions. This enables canary releases and zero-downtime rollbacks. See Cloud Run Scaling Behaviour for how Cloud Run manages instances behind that URL.
Step-by-step: deploy your first Cloud Run service
Step 1: Enable the required APIs
Run this once per project. Cloud Run, Artifact Registry, and Cloud Build all need to be active. If they are already enabled, these commands do nothing.
gcloud services enable run.googleapis.com
gcloud services enable artifactregistry.googleapis.com
gcloud services enable cloudbuild.googleapis.comIf you see a “billing not enabled” error here, you need to enable billing for the project in the GCP Console before continuing. APIs can only be enabled on projects with an active billing account. See API Not Enabled Errors for common issues at this stage.
Step 2: Create an Artifact Registry repository
Artifact Registry is where your container images will be stored. Think of a repository as a named folder for images inside your project — one repository can hold many images across many versions.
gcloud artifacts repositories create my-repo \
--repository-format=docker \
--location=us-central1 \
--description="Container images"
# Allow Docker to authenticate with this registry location
gcloud auth configure-docker us-central1-docker.pkg.devThe configure-docker command adds credentials to your local
Docker config so docker push commands to this registry
authenticate automatically.
Step 3: Write a minimal app
Think of PORT like a desk number in a shared office. Cloud Run
assigns the desk at runtime and tells your app the number via the environment.
If your app ignores that and insists on sitting at desk 8080, everything seems
fine until the day Cloud Run assigns a different number and your app is
listening on the wrong one. Always read from the environment.
This Python example is intentionally minimal. The only thing that matters
for Cloud Run is that the app reads the PORT environment
variable and listens on it.
# app.py
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write(b"Hello from Cloud Run!\n")
if __name__ == "__main__":
port = int(os.environ.get("PORT", 8080))
server = HTTPServer(("", port), Handler)
print(f"Listening on port {port}")
server.serve_forever()Step 4: Write the Dockerfile
The Dockerfile describes how to package your app into a container image. Use a slim base image. Smaller images mean faster cold starts and lower storage costs in Artifact Registry.
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY app.py .
CMD ["python", "app.py"]python:3.12-slim is around 130MB. The full
python:3.12 image is 900MB+. For Cloud Run, smaller images
directly translate to faster cold starts and a smaller attack surface.
Prefer slim or distroless base images for everything you deploy.
Step 5: Build and push the image
Set variables first so the image path is consistent across all commands.
Use a version tag like :v1 rather than only :latest.
This matters for rollbacks later.
# Set these once; they are reused in every command below
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1
export IMAGE=$REGION-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:v1
# Option A: build and push locally (requires Docker installed)
docker build -t $IMAGE .
docker push $IMAGE
# Option B: build in the cloud with Cloud Build (no local Docker needed)
gcloud builds submit --tag $IMAGEIf you push every build to :latest and later need to roll
back, the image the old revision pointed to is gone. Version tags like
:v1, :v2, or a commit SHA let you roll back to
any previous revision reliably.
Cloud Build runs the build on Google infrastructure and pushes the image directly to Artifact Registry. It is a good option when working from a machine without Docker or when you want reproducible builds across a team. For more detail, see Building Docker Images with Cloud Build.
Step 6: Deploy to Cloud Run
This command deploys the image as a Cloud Run service.
—allow-unauthenticated makes it publicly accessible.
Remove it for private services.
gcloud run deploy my-app \
--image=$IMAGE \
--region=$REGION \
--platform=managed \
--allow-unauthenticated \
--memory=256Mi \
--cpu=1The first deployment takes 30 to 60 seconds. Subsequent deployments of the same service are faster because the infrastructure already exists. When the command finishes, it prints the service URL.
—allow-unauthenticated makes the service reachable by
anyone on the internet without a token. Use it for public APIs and
websites. For internal services consumed by other GCP workloads, remove
this flag and grant the Cloud Run Invoker role to the calling service
account instead. See
Cloud Run Security Model
for how to set this up correctly.
Step 7: Test the service URL
Retrieve the stable HTTPS URL and send a request to confirm it is working.
# Get the service URL
SERVICE_URL=$(gcloud run services describe my-app \
--region=$REGION \
--format="value(status.url)")
echo $SERVICE_URL
# Send a test request
curl $SERVICE_URLYou should see Hello from Cloud Run!. The URL is stable. It
does not change between revisions, canary releases, or rollbacks.
Step 8: Deploy an update
Modify the app (change the response message, for example), build a new image with a new version tag, and deploy again.
export IMAGE_V2=$REGION-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:v2
# Build the updated image
docker build -t $IMAGE_V2 .
docker push $IMAGE_V2
# Deploy the new version
# Cloud Run creates a new revision and shifts traffic to it automatically
gcloud run deploy my-app \
--image=$IMAGE_V2 \
--region=$REGIONCloud Run performs a zero-downtime deployment. The old revision continues handling traffic until the new one passes health checks, then traffic switches over.
Step 9: View logs
Cloud Run sends container stdout and stderr to Cloud Logging automatically. No logging SDK or agent is needed. Just print to stdout.
# Stream live logs as requests come in
gcloud run services logs tail my-app --region=$REGION
# Read the last 50 entries
gcloud logging read \
'resource.type="cloud_run_revision" AND resource.labels.service_name="my-app"' \
--limit=50 \
--format="value(timestamp,textPayload)"If your application writes structured JSON to stdout, Cloud Logging parses the fields automatically. This lets you filter logs by severity, request ID, or any custom field you include, with no extra setup. See Monitoring Cloud Run for dashboards and alerting.
Step 10: Roll back if needed
Cloud Run keeps all previous revisions. To roll back, redirect traffic to an older revision by name.
# List all revisions for this service
gcloud run revisions list --service=my-app --region=$REGION
# Canary: send 10% to the new revision, 90% to the previous one
# Useful when validating a new version before fully committing
gcloud run services update-traffic my-app \
--region=$REGION \
--to-revisions=my-app-00002-xyz=10,my-app-00001-abc=90
# Full rollback: send all traffic back to the previous revision
gcloud run services update-traffic my-app \
--region=$REGION \
--to-revisions=my-app-00001-abc=100Revisions reference specific image digests in Artifact Registry. If you delete an image and then need to roll back to the revision that used it, the rollback will fail. Keep old images until you are certain you will never need them.
For advanced deployment patterns like blue-green or progressive canary releases, see Canary Deployments on GCP.
When to use Cloud Run
Cloud Run is a strong fit for:
HTTP APIs and REST services. Stateless request/response workloads that benefit from automatic scaling and scale-to-zero billing.
Internal microservices. Services consumed only by other GCP workloads, secured with IAM authentication rather than API keys.
Lightweight web apps. Low-traffic tools and dashboards that should cost nothing at idle.
Event-driven workloads. Cloud Run integrates natively with Pub/Sub, Eventarc, and Cloud Tasks to process events as they arrive.
Containerised workloads without server management. Anything that runs in a container where you do not want to manage infrastructure.
Side projects and prototypes. The free tier is generous and there is no idle cost when nobody is using the service.
When Cloud Run may not be the right fit
Cloud Run works well for most HTTP-driven workloads, but it is not the right tool for everything:
Long-running background processes. Cloud Run has a maximum request timeout of 3600 seconds. Persistent daemons or indefinite workers need Compute Engine or GKE.
Stateful services. Cloud Run instances do not have persistent local storage. If your app needs to write files that survive across requests, use Cloud Storage, a database, or a persistent disk on GKE or Compute Engine.
GPU workloads. Cloud Run does not support GPU instances. Use Compute Engine or GKE for ML inference and training.
Services that need very low or consistent latency. Cold starts add latency when traffic is sparse. Minimum instances eliminate this but add cost. For strict latency requirements, evaluate GKE or a VM-based approach.
Hundreds of microservices with complex networking. At large scale, GKE gives you more control over service mesh, networking policies, and cluster-level configuration.
See Choosing Between Cloud Run, GKE, and Compute Engine for a full comparison.
Cloud Run vs Cloud Functions
These two services look similar from the outside but serve different purposes:
| Cloud Run | Cloud Functions | |
|---|---|---|
| Unit of deployment | A container | A function (source code) |
| Language support | Any language, any framework | Supported runtimes only |
| Trigger type | HTTP requests (primarily) | HTTP, Pub/Sub, Storage, Eventarc |
| Control over runtime | Full (you build the container) | Limited (Google manages the runtime) |
| Startup time | Slightly slower (container pull) | Faster for small functions |
| Best for | HTTP services, APIs, microservices | Single-purpose event handlers |
The practical rule: if your workload is a self-contained function that reacts to an event, Cloud Functions is simpler. If it is an HTTP service, needs a custom runtime, or involves any complexity beyond a single handler, Cloud Run is the better fit. For a full breakdown, see Cloud Run vs Cloud Functions.
Common beginner mistakes
Hardcoding port 8080 instead of reading
PORT. The default is 8080, which is why hardcoding it usually works until it does not. The contract with Cloud Run is to readPORTfrom the environment. If Cloud Run changes the default or you set a custom port flag, a hardcoded value breaks silently.Using only the
:latesttag. If you push every build to:latestand then need to roll back, the image the old revision pointed to is gone. Tag images with:v1,:v2, or a commit SHA. Cloud Run revisions reference a specific image digest, and the tag is just how you reference it at deploy time.Not testing the container locally before deploying. Run
docker run -p 8080:8080 -e PORT=8080 IMAGElocally first. If the container fails to start locally, it will fail on Cloud Run too. Container startup errors are much faster to diagnose locally than through Cloud Logging. See Cloud Run Container Failed to Start for what to check when something does go wrong.Forgetting to enable billing or APIs. Cloud Run and Artifact Registry will fail or return permission errors if the APIs are not enabled or billing is not active. Run the
gcloud services enablecommands in Step 1 before anything else.Using the wrong region or image path. The Artifact Registry image path includes the region (
us-central1-docker.pkg.dev/…). If you deploy to a different region than where the image is stored, Cloud Run still works but pulling across regions adds latency and egress cost. Keep your registry location and service region consistent.Not setting memory and CPU limits for the workload. The default is 256MB RAM and 1 CPU. If your container needs more memory, it gets OOM-killed, often without a clear error message in the first few log lines. Set
—memoryand—cpubased on what your app actually needs, then tune using Cloud Monitoring data.Misunderstanding public access.
—allow-unauthenticatedmakes the service publicly accessible to anyone on the internet. If your service is internal only, omit this flag. IAM authentication is enforced by default. It is not something you need to add on top.
Summary
- Enable
run.googleapis.com,artifactregistry.googleapis.com, andcloudbuild.googleapis.combefore anything else - Your container must read
PORTfrom the environment and listen on it. Everything else is up to you. - Build locally with Docker or in the cloud with
gcloud builds submit. Both push to Artifact Registry. - Tag images with version numbers so Cloud Run revisions can be rolled back to a known-good image
- The service HTTPS URL is permanent. Traffic splits, updates, and rollbacks all happen without changing it.
- Cloud Run bills only for requests being processed. It costs nothing at idle with no minimum instances set.
Frequently asked questions
What do I need before deploying to Cloud Run?
A GCP project with billing enabled, the Cloud Run and Artifact Registry APIs enabled, and the gcloud CLI authenticated. Your container must read the PORT environment variable and listen on it. Docker is only needed if you are building images locally. You can use Cloud Build to build in the cloud without installing Docker locally.
Can I deploy to Cloud Run without writing a Dockerfile?
Yes. Run gcloud run deploy my-service --source . --region=us-central1 and GCP detects your language, builds a container using Buildpacks, pushes it to Artifact Registry, and deploys it. This source-based approach supports Node.js, Python, Go, Java, Ruby, PHP, and .NET. It is faster for getting started but gives you less control over the base image and build process.
Is my Cloud Run service public by default?
No. By default Cloud Run requires authentication on every request. You must explicitly pass --allow-unauthenticated when deploying if you want the service to be reachable without a token. For internal services or APIs consumed by other GCP services, keeping authentication on and granting the Cloud Run Invoker role is the correct approach.
How do I update an existing Cloud Run service?
Rerun gcloud run deploy with the new image tag. Cloud Run creates a new revision, deploys it, and shifts 100% of traffic to it automatically if the deployment succeeds. The previous revision stays available for rollbacks and the service URL does not change. You can also update just the configuration (environment variables, memory, concurrency) without rebuilding the image by passing the same --image flag with the updated settings.
How do rollbacks work in Cloud Run?
Cloud Run keeps all previous revisions. To roll back, run: gcloud run services update-traffic my-service --region=REGION --to-revisions=REVISION_NAME=100. You can also do a partial rollback by splitting traffic (for example, 90% to the old revision and 10% to the new one while you investigate). Revisions reference specific container image digests, so the image must still exist in Artifact Registry for the rollback to work. Never delete old images until you are sure you will not need to roll back to them.
How much does Cloud Run cost for a small project?
Cloud Run has a generous free tier: 2 million requests, 360,000 GB-seconds of memory, and 180,000 vCPU-seconds per month. Most small projects stay within the free tier. Beyond that, you pay only for the CPU and memory used while a request is being processed, not for idle time. Keeping images small and startup times short reduces both cost and cold start latency.