You've spent hours configuring a server for an app that gets 10 visits a day. What if tomorrow you get 10,000? Cloud Run eliminates that problem. At Meteora Web, we've been working with containers since 2017 and chose Cloud Run as our go-to for projects that need to scale without warning, without the infrastructure headache. This guide goes straight to the point: how to prepare a container, deploy it on Cloud Run, and manage it in real-world scenarios, with the tricks we've learned on the field.
What Is Cloud Run and Why You Should Care
Cloud Run is a serverless compute service from Google Cloud that runs HTTP containers. No servers to manage, no Kubernetes to configure. You pay only for the CPU and memory consumed during requests. If your app receives no traffic, you pay nothing. For us, this was a game-changer: instead of keeping a VM running 24/7 for an API called 100 times a day, we put it on Cloud Run and the costs dropped to near zero.
Common mistake: thinking Cloud Run is only for microservices. No, we've deployed monolithic applications too, as long as they are stateless and respond over HTTP. We even containerized WooCommerce (with some tweaks). But let's proceed step by step.
What You Need to Get Started
- A Google Cloud account with billing enabled
- gcloud CLI installed and configured
- Docker installed (for local build or via Cloud Build)
- A container that listens on a port (default 8080)
Preparing a Container for Cloud Run (Golden Rules)
Cloud Run is not Docker on any machine. It has specific constraints. Ignoring them leads to 10-second cold starts and instances dying for no reason.
Stateless by Design
The container's filesystem is ephemeral. Each request may be served by a different instance. Do not save files to the local disk if they need to persist. Use Cloud Storage, Cloud SQL, or Redis.
Port and Declaration
The container must expose an HTTP port. By default, Cloud Run expects port 8080, but you can use 8000 or 3000 as long as you declare it in the deploy command. We always use the environment variable PORT (Cloud Run sets it automatically). Here's a sample Dockerfile for a Node.js app:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
ENV PORT=8080
EXPOSE 8080
CMD ["node", "server.js"]
The server.js file must read process.env.PORT and start the server on that port. If you hard-code the port, it won't work on Cloud Run.
Minimize Image Size
Large images = slow cold starts. Use lightweight base images (alpine, distroless). We've seen containers over 1.5 GB with all dev dependencies included. Result: 8 seconds to start. After optimization (multi-stage build) we got down to 150 MB and 1.2 seconds.
Deploy: From Build to Running
You can deploy in two ways: from your terminal (gcloud run deploy) or via Cloud Build (CI/CD). We recommend the latter for production, but let's start with the simplest.
Manual Deploy with One Command
# Make sure your project is set
gcloud config set project YOUR_PROJECT_ID
# Build and push the image (using Cloud Build)
gcloud builds submit --tag gcr.io/YOUR_PROJECT_ID/my-app:latest
# Deploy to Cloud Run
gcloud run deploy my-service \
--image gcr.io/YOUR_PROJECT_ID/my-app:latest \
--platform managed \
--region europe-west1 \
--allow-unauthenticated
In less than a minute you get a public URL like https://my-service-xxxxx-ew.a.run.app. It works.
CI/CD with Cloud Build
To automate, create a cloudbuild.yaml file in your repo root:
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'gcr.io/$PROJECT_ID/my-app:$COMMIT_SHA', '.']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'gcr.io/$PROJECT_ID/my-app:$COMMIT_SHA']
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- 'run'
- 'deploy'
- 'my-service'
- '--image'
- 'gcr.io/$PROJECT_ID/my-app:$COMMIT_SHA'
- '--region'
- 'europe-west1'
- '--allow-unauthenticated'
Then connect your repo to Cloud Build (trigger on push). Every commit deploys. We use this for all clients: no more SSH into servers, everything from GitHub.
Scaling, Concurrency, and Cold Starts
Cloud Run scales to zero when there is no traffic. The first request after an idle period starts a new instance (cold start). The time depends on image size and app startup. We reduced cold starts to under 200ms using min-instance (keep 1 instance always warm) for critical services. Costs a bit more but for production APIs it's essential.
# Deploy with minimum 1 instance always active
gcloud run deploy my-service --image IMG --min-instances 1 --max-instances 10 --concurrency 80
Concurrency: how many simultaneous requests a single instance can handle. Typical values: 80 for Node.js/Python, 10 for heavy apps. Do not exceed 250 to avoid timeout risks.
Connecting Cloud Run to Other GCP Services
Cloud SQL (Database)
Cloud Run does not have direct VPC access by default. To connect to Cloud SQL you must use Cloud SQL Auth Proxy or enable Serverless VPC Access. We prefer the proxy because it works immediately. Add this to your deploy command:
gcloud run deploy my-service \
--add-cloudsql-instances PROJECT:REGION:INSTANCE \
--image IMG
In your code, connect via /cloudsql/PROJECT:REGION:INSTANCE using a Unix socket. Many ORMs support this natively.
Secret Manager
Never put passwords in your Dockerfile or public environment variables. Use Secret Manager and mount secrets as environment variables or volumes. Example:
gcloud run deploy my-service \
--update-secrets=DB_PASSWORD=my-db-password:latest
Custom Domains and SSL
Cloud Run provides a default URL with an auto-managed SSL certificate. To use your own domain (e.g., api.meteoraweb.com) verify ownership and create a mapping:
gcloud beta run domain-mappings create \
--service my-service \
--domain api.meteoraweb.com \
--region europe-west1
Then add an A record in your DNS with the IP provided by the command. Google manages the SSL certificate automatically. No more expired Let's Encrypt.
Monitoring and Logging
Cloud Run integrates natively with Cloud Logging. Every container log (stdout/stderr) is collected. You can create custom metrics with Error Reporting to get alerts on exceptions. We also use Google Cloud Monitoring for request count and latency.
Costs: What a Small Business Really Pays
Take an API handling 1000 requests per day, each 200 ms, with 256 MB RAM. Estimated cost: around $0.50/month (thanks to the generous free tier: 2 million requests/month free). If you grow to 1 million requests/month, we're talking $5–10. Much less than an always-on VM.
Common Mistakes (and How to Avoid Them)
- Wrong port: server listens on 3000 but Cloud Run sends traffic to 8080. Use the PORT variable.
- Request timeout: requests longer than 60 minutes (max configurable) are closed. For long jobs use Cloud Run Jobs.
- Non-persistent filesystem: you save data to /tmp and then complain it disappears.
- Untagged images: use SHA or commit tags, not just
:latest.
In Summary — What to Do Now
- Prepare a stateless app that listens on the port specified by
$PORT. - Build an optimized Dockerfile (multi-stage, lightweight image).
- Do a test deploy with
gcloud run deployand test the URL. - Set minimum 1 instance for critical services, enable custom domains.
- Set up Cloud Build for automatic deploys from GitHub.
- Monitor logs and set alerts on errors.
At Meteora Web, we use Cloud Run daily for clients across Italy. For more on monitoring, check out our guide on GA4 with Google Tag Manager — another tool we use to track performance of apps deployed on Cloud Run.
Sponsored Protocol