Introduction to Troubleshooting Cloud Run
Google Cloud Run is a fully managed serverless platform that lets you run stateless containers without worrying about the underlying infrastructure. While Cloud Run abstracts away much of the operational complexity, developers still encounter issues related to deployment, networking, performance, and configuration. This tutorial walks you through the most common Cloud Run problems and provides practical, tested solutions.
What Is Cloud Run Troubleshooting?
Troubleshooting Cloud Run involves diagnosing and resolving issues that arise during the container lifecycle on Google's serverless platform. This includes problems with container builds, deployment failures, runtime errors, cold starts, networking misconfigurations, IAM permission issues, and scaling behavior. Because Cloud Run sits between your container and Google's infrastructure, debugging requires understanding both your application code and the platform's behavior.
Why It Matters
Cloud Run's abstraction layer can make debugging harder than traditional VM-based deployments. When something breaks, you cannot simply SSH into a server. Instead, you rely on logs, metrics, and platform-specific signals. Understanding common failure modes helps you:
- Reduce mean time to resolution (MTTR) during incidents
- Prevent deployment blockers that delay releases
- Optimize cost by fixing misconfigured scaling and memory settings
- Improve user experience by addressing cold starts and latency
- Maintain security by resolving IAM and authentication issues
Common Issue 1: Container Fails to Start
One of the most frequent Cloud Run errors is a container that fails to start within the platform's timeout. Cloud Run requires your container to listen on the port defined by the PORT environment variable (defaulting to 8080) within approximately four minutes. If the container does not bind to that port in time, the deployment fails.
Diagnosing the Problem
Check the Cloud Run service logs in the Google Cloud Console or via the gcloud CLI:
gcloud run services describe my-service \
--region=us-central1 \
--format="value(status.conditions)"
To view recent logs:
gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=my-service" \
--limit=50 \
--format="value(textPayload)"
Common causes include:
- Application listening on the wrong port
- Container crashing on startup due to missing environment variables
- Entrypoint command incorrectly specified
- Container exceeding memory limits during initialization
Solution: Bind to the Correct Port
Your application must read the PORT environment variable and bind to it. Here is a Node.js Express example:
const express = require('express');
const app = express();
const port = process.env.PORT || 8080;
app.get('/', (req, res) => {
res.send('Hello from Cloud Run');
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
For a Python Flask application:
from flask import Flask
import os
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello from Cloud Run'
if __name__ == '__main__':
port = int(os.environ.get('PORT', 8080))
app.run(host='0.0.0.0', port=port)
Notice the use of 0.0.0.0 as the host. Binding to 127.0.0.1 or localhost will cause Cloud Run to reject the container because the platform cannot reach the service from outside the container's loopback interface.
Solution: Fix the Entrypoint
If your container starts but immediately exits, the entrypoint may be wrong. Verify your Dockerfile:
FROM node:18-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["node", "server.js"]
Avoid using the shell form of CMD because it can cause signal handling issues and unexpected exits:
# Avoid this
CMD node server.js
# Use this instead
CMD ["node", "server.js"]
Common Issue 2: Deployment Failures
Deployment failures often stem from IAM permission problems, invalid container images, or misconfigured service definitions. The error messages can be cryptic, so understanding the root causes is essential.
Permission Denied Errors
If you see an error like Permission denied on resource project your-project, the deploying account lacks the necessary IAM roles. The deploying user needs at least roles/run.admin and roles/iam.serviceAccountUser.
# Grant Cloud Run Admin role
gcloud projects add-iam-policy-binding your-project \
--member="user:developer@example.com" \
--role="roles/run.admin"
# Grant Service Account User role
gcloud projects add-iam-policy-binding your-project \
--member="user:developer@example.com" \
--role="roles/iam.serviceAccountUser"
If your Cloud Run service needs to access other Google Cloud services, you must attach a service account with the appropriate roles. The default compute service account may not have sufficient permissions.
# Create a dedicated service account
gcloud iam service-accounts create my-run-sa \
--display-name="Cloud Run Service Account"
# Grant it access to Cloud Storage
gcloud projects add-iam-policy-binding your-project \
--member="serviceAccount:my-run-sa@your-project.iam.gserviceaccount.com" \
--role="roles/storage.objectViewer"
# Deploy with the service account
gcloud run deploy my-service \
--image=gcr.io/your-project/my-image \
--region=us-central1 \
--service-account=my-run-sa@your-project.iam.gserviceaccount.com
Image Not Found Errors
If deployment fails with Image not found, verify the image exists in Artifact Registry or Container Registry and that the Cloud Run service account can pull it:
# List images in Artifact Registry
gcloud artifacts docker images list us-central1-docker.pkg.dev/your-project/repo
# Ensure the Cloud Run runtime service account can pull
gcloud artifacts repositories add-iam-policy-binding repo \
--location=us-central1 \
--member="serviceAccount:my-run-sa@your-project.iam.gserviceaccount.com" \
--role="roles/artifactregistry.reader"
Common Issue 3: Cold Start Latency
Cold starts occur when Cloud Run spins up a new container instance to handle a request. This can add hundreds of milliseconds or even seconds of latency. While you cannot eliminate cold starts entirely on a serverless platform, you can minimize their impact.
Diagnosing Cold Starts
Use Cloud Monitoring to identify cold start patterns. Look at the instance_count metric and request latency distributions. You can also log instance startup events:
const express = require('express');
const app = express();
console.log(`Instance started at ${new Date().toISOString()}`);
app.get('/', (req, res) => {
res.json({ time: Date.now() });
});
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`Listening on ${port}`);
});
Solutions for Reducing Cold Start Impact
1. Optimize container image size. Smaller images load faster. Use multi-stage builds and slim base images:
# Build stage
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM node:18-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/server.js"]
2. Use minimum instances. Setting --min-instances keeps at least one instance warm, eliminating cold starts for most traffic:
gcloud run deploy my-service \
--image=gcr.io/your-project/my-image \
--region=us-central1 \
--min-instances=1 \
--max-instances=10
Be aware that minimum instances incur continuous billing even when there is no traffic. Use this feature judiciously.
3. Defer non-critical initialization. Move expensive startup logic outside the request path or lazy-load modules:
let heavyModule = null;
app.get('/process', async (req, res) => {
if (!heavyModule) {
heavyModule = await import('./heavy-module.js');
}
const result = heavyModule.process(req.body);
res.json(result);
});
4. Increase CPU allocation. By default, Cloud Run allocates CPU only during request processing. Enabling CPU always-on helps with background tasks and faster startup:
gcloud run deploy my-service \
--image=gcr.io/your-project/my-image \
--region=us-central1 \
--cpu-boost
Common Issue 4: Request Timeouts and 503 Errors
Cloud Run has a default request timeout of 5 minutes (configurable up to 60 minutes). If your request exceeds this, Cloud Run returns a 503 or 504 error. Long-running operations like file processing or database migrations frequently trigger this.
Diagnosing Timeout Issues
Check the request timeout setting on your service:
gcloud run services describe my-service \
--region=us-central1 \
--format="value(spec.template.spec.timeoutSeconds)"
Look for timeout-related log entries:
gcloud logging read "resource.type=cloud_run_revision AND severity>=ERROR" \
--limit=20 \
--format="value(textPayload)"
Solutions
1. Increase the timeout. For long-running operations, increase the service timeout:
gcloud run deploy my-service \
--image=gcr.io/your-project/my-image \
--region=us-central1 \
--timeout=3600
2. Offload long tasks to background services. Instead of processing synchronously, use a task queue pattern. The Cloud Run service accepts the request, enqueues a task in Cloud Tasks, and returns immediately:
const { CloudTasksClient } = require('@google-cloud/tasks');
const client = new CloudTasksClient();
async function enqueueTask(payload) {
const queuePath = client.queuePath(
process.env.PROJECT_ID,
process.env.REGION,
process.env.QUEUE_NAME
);
const task = {
httpRequest: {
httpMethod: 'POST',
url: process.env.WORKER_URL,
headers: { 'Content-Type': 'application/json' },
body: Buffer.from(JSON.stringify(payload)).toString('base64'),
},
};
const [response] = await client.createTask({ parent: queuePath, task });
return response.name;
}
app.post('/upload', async (req, res) => {
const taskName = await enqueueTask(req.body);
res.status(202).json({ taskId: taskName, status: 'processing' });
});
3. Implement client-side retries. For transient 503 errors caused by scaling, implement exponential backoff:
async function fetchWithRetry(url, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url);
if (response.ok) return response;
if (response.status !== 503) throw new Error(`HTTP ${response.status}`);
} catch (error) {
if (attempt === maxRetries - 1) throw error;
}
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
Common Issue 5: Memory and CPU Limits
Cloud Run containers that exceed their configured memory limit are terminated with an OOM (Out of Memory) error. Similarly, CPU-throttled containers can become unresponsive between requests.
Diagnosing Resource Issues
Check current resource limits:
gcloud run services describe my-service \
--region=us-central1 \
--format="value(spec.template.spec.containers[0].resources)"
Monitor memory usage in Cloud Monitoring by looking at the container/memory/bytes_used metric. If usage consistently approaches the limit, you need to increase it or optimize your application.
Solutions
1. Increase memory and CPU. Cloud Run allows up to 32 GiB of memory and 8 vCPUs:
gcloud run deploy my-service \
--image=gcr.io/your-project/my-image \
--region=us-central1 \
--memory=2Gi \
--cpu=2
2. Enable CPU always-on. This prevents CPU throttling between requests, which is critical for applications that do background work or maintain in-memory caches:
gcloud run deploy my-service \
--image=gcr.io/your-project/my-image \
--region=us-central1 \
--cpu=2 \
--no-cpu-throttling
3. Profile memory usage. Add memory logging to identify leaks:
setInterval(() => {
const used = process.memoryUsage();
console.log(JSON.stringify({
rss: `${Math.round(used.rss / 1024 / 1024)} MB`,
heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)} MB`,
heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)} MB`,
external: `${Math.round(used.external / 1024 / 1024)} MB`,
}));
}, 30000);
Common Issue 6: Networking and VPC Connector Issues
When your Cloud Run service needs to access resources in a VPC network (such as Cloud SQL, Memorystore, or internal APIs), you need a VPC connector. Misconfigurations here are a common source of connection failures.
Diagnosing VPC Connector Problems
Common symptoms include connection timeouts to internal resources or DNS resolution failures. First, verify the VPC connector exists and is in the same region:
gcloud compute networks vpc-access connectors list \
--region=us-central1
Check that your service is configured to use the connector:
gcloud run services describe my-service \
--region=us-central1 \
--format="value(spec.template.spec.vpcAccess)"
Solutions
1. Create a VPC connector.
gcloud compute networks vpc-access connectors create my-connector \
--region=us-central1 \
--network=default \
--range=10.8.0.0/28
2. Deploy with the connector.
gcloud run deploy my-service \
--image=gcr.io/your-project/my-image \
--region=us-central1 \
--vpc-connector=my-connector \
--vpc-egress=private-ranges-only
3. For Cloud SQL connections. Use the Cloud SQL connector directly instead of a VPC connector for simpler setup:
gcloud run deploy my-service \
--image=gcr.io/your-project/my-image \
--region=us-central1 \
--add-cloudsql-instances=your-project:us-central1:your-instance
In your application, connect using the Unix socket path:
const { Pool } = require('pg');
const pool = new Pool({
host: '/cloudsql/your-project:us-central1:your-instance',
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
});
Common Issue 7: Authentication and Authorization
By default, new Cloud Run services require authentication. Developers often encounter 403 errors when trying to invoke a service from another application or from the public internet.
Making a Service Public
If your service should be publicly accessible (for example, a web frontend or a webhook endpoint), you need to allow unauthenticated invocations:
gcloud run deploy my-service \
--image=gcr.io/your-project/my-image \
--region=us-central1 \
--allow-unauthenticated
Alternatively, update an existing service:
gcloud run services add-iam-policy-binding my-service \
--region=us-central1 \
--member="allUsers" \
--role="roles/run.invoker"
Service-to-Service Authentication
When one Cloud Run service calls another, use identity tokens for authentication. Here is how to call an authenticated Cloud Run service from another service:
const { GoogleAuth } = require('google-auth-library');
const auth = new GoogleAuth();
async function callSecureService() {
const client = await auth.getIdTokenClient(
'https://my-service-abc123-uc.a.run.app'
);
const response = await client.request({
url: 'https://my-service-abc123-uc.a.run.app/api/data',
method: 'GET',
});
return response.data;
}
Ensure the calling service's runtime service account has the roles/run.invoker role on the target service:
gcloud run services add-iam-policy-binding target-service \
--region=us-central1 \
--member="serviceAccount:caller-sa@your-project.iam.gserviceaccount.com" \
--role="roles/run.invoker"
Best Practices for Cloud Run Troubleshooting
Implement Structured Logging
Structured logs are easier to query and filter in Cloud Logging. Use JSON-formatted log entries:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [new winston.transports.Console()],
});
logger.info('Request received', {
method: req.method,
path: req.path,
userId: req.user?.id,
duration: Date.now() - startTime,
});
Set Up Health Checks
Cloud Run does not support custom health check endpoints, but you can implement a lightweight liveness endpoint that verifies critical dependencies:
app.get('/health', async (req, res) => {
try {
await pool.query('SELECT 1');
res.status(200).json({ status: 'healthy' });
} catch (error) {
res.status(503).json({ status: 'unhealthy', error: error.message });
}
});
Use Cloud Monitoring Alerts
Set up alerts for key metrics so you are notified before users are affected:
# Create an alert for high error rate
gcloud monitoring policies create --policy-from-file=alert-policy.yaml
Example alert policy YAML:
displayName: Cloud Run High Error Rate
conditions:
- displayName: Error rate above 5%
conditionThreshold:
filter: |
resource.type="cloud_run_revision"
AND metric.type="run.googleapis.com/request_count"
AND metric.label.response_code_class="5xx"
comparison: COMPARISON_GT
thresholdValue: 0.05
duration: 300s
notificationChannels:
- projects/your-project/notificationChannels/channel-id
combiner: OR
Version Your Deployments
Use revision annotations to track what changed between deployments. This makes rollback and debugging easier:
gcloud run deploy my-service \
--image=gcr.io/your-project/my-image:v1.2.3 \
--region=us-central1 \
--update-labels=version=1.2.3,commit=abc123 \
--revision-suffix=v1-2-3
To roll back to a previous revision:
gcloud run services update-traffic my-service \
--region=us-central1 \
--to-revisions=my-service-v1-2-2=100
Test Locally with Cloud Run Emulation
Use the Cloud Code extension or cloud-run-proxy to test locally before deploying. You can also use Docker to simulate the Cloud Run environment:
docker run -p 8080:8080 -e PORT=8080 \
-e DB_USER=user -e DB_PASSWORD=pass \
gcr.io/your-project/my-image:latest
Conclusion
Troubleshooting Cloud Run effectively requires understanding the platform's architecture, knowing where to find diagnostic information, and having a systematic approach to resolving common issues. By following the solutions outlined in this tutorial—fixing port bindings, configuring IAM correctly, optimizing cold starts, managing resource limits, setting up VPC connectors, and implementing proper authentication—you can resolve the majority of Cloud Run problems quickly. Combine these techniques with structured logging, monitoring alerts, and disciplined deployment practices to build resilient serverless applications that are easy to debug and maintain. Remember that Cloud Run's serverless model trades some operational control for simplicity, so investing time in observability and proactive monitoring is the best long-term strategy for minimizing production incidents.