How to Fix: GCP Cloud Run Container Failed to Start

GCPCrashLoopBackOffCritical severity

The Scenario

You just pushed a new image tag to Artifact Registry and deployed to Cloud Run via gcloud run deploy. The deployment succeeds, but within 30 seconds the revision shows “Container failed to start” in the Cloud Console. You’re staring at a CrashLoopBackOff in the logs, and your Friday deploy is now blocking the staging environment.

Symptoms

Root Cause

Cloud Run enforces a strict startup contract: your container must start an HTTP server listening on the port specified by the PORT environment variable within 4 minutes (240 seconds) of the first request. The most common causes are: (1) your app is hardcoded to listen on a port like 8080 instead of reading $PORT, (2) the container image is too large and takes longer than 4 minutes to download and start, or (3) memory limits are too low and the container gets OOM-killed before it can bind the port.

Resolution (Step-by-Step)

  1. Check the PORT environment variable — your app must read $PORT at runtime, not a hardcoded port. Cloud Run injects this dynamically (usually 8080 but can vary).

    # In your Dockerfile or entrypoint script, use:
    PORT=${PORT:-8080}
    # Then bind to $PORT
  2. Verify the container starts locally with the exact same environment Cloud Run uses:

    docker run -p 8080:8080 -e PORT=8080 -e K_SERVICE=test -e K_REVISION=test-001 -e K_CONFIGURATION=test gcr.io/<project>/<image>
  3. Check memory and CPU limits — if your container allocates more than the Cloud Run limit (default 256MB), it gets killed silently:

    # Increase memory limit
    gcloud run deploy <service> \
      --memory 512Mi \
      --cpu 1 \
      --concurrency 80
  4. Verify the health check endpoint — Cloud Run pings GET / on startup. If your app doesn’t respond on that path, it fails:

    # Add a root handler
    app.get('/', (req, res) => res.send('OK'))
  5. Inspect container startup time — if the image is > 1GB, it may exceed the 4-minute startup window:

    # View image size
    gcloud container images describe gcr.io/<project>/<image> --format='value(image_size_bytes)'
    # If > 1GB, optimize your Dockerfile (use slim base images, multi-stage builds)

Why This Sometimes Doesn’t Work

If you’ve verified port binding, memory, and image size, but the container still fails, check for network egress issues. Cloud Run containers that try to connect to external services during startup (e.g., database connection pools, external APIs) can hang if those services are unreachable or timeout. The container never starts listening because it’s stuck on an outbound call. Set a short connection timeout on all external clients (e.g., connectTimeout: 3000 in Node.js or connection_timeout=3 in Go) to avoid this.

Verification

# Deploy with explicit health check
gcloud run deploy <service> \
  --image gcr.io/<project>/<image> \
  --region us-central1 \
  --no-cpu-throttling

# Check revision status
gcloud run revisions describe <revision> \
  --region us-central1 \
  --format='value(status.conditions)'

# Expected output: "Ready" condition with status "True"

Common Follow-up Questions

Q: My container works locally but fails on Cloud Run — why? A: Local Docker doesn’t enforce the same port-binding contract or memory limits. Cloud Run kills containers that don’t listen on $PORT within 4 minutes. Also, local runs may have more memory available.

Q: How do I see the exact error message from the container? A: Run gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=<service>" --limit 10 and look for container.exit_code or container.oom_killed fields.

Q: Can I increase the 4-minute startup timeout? A: No, Cloud Run’s startup timeout is fixed at 4 minutes. You must optimize your image or startup sequence to fit within this window.

Prevent This in the Future

Set up a monitoring stack that tracks Cloud Run revision health and container startup failures in real-time. Better Stack can alert you the moment a revision enters CrashLoopBackOff, so you catch these issues before they block deployments. With structured logging and uptime checks, you’ll know exactly when a container fails to bind its port or runs out of memory.

Set up uptime monitoring and alerts with Better Stack