The Cloud Run container contract
Cloud Run runs any container that follows a short contract. Google’s documentation says requests go to port 8080 by default, the PORT environment variable is set on the ingress container, and the container “must listen for requests on 0.0.0.0” and “should not listen on 127.0.0.1”. It also says Cloud Run “specifically supports the Linux x86_64 ABI format” and that a multi-architecture image must include linux/amd64.
Two of those rules trip up Mac developers: an app that binds localhost works in a local terminal and fails in Cloud Run, and an image built natively on Apple silicon is arm64. Testing the real container locally catches both before you deploy.
Test a Cloud Run service locally on Apple silicon
- Listen on $PORT and 0.0.0.0. Make the server read the
PORTenvironment variable (Cloud Run sends requests to 8080 by default) and bind0.0.0.0, not127.0.0.1. - Build for linux/amd64. Run
docker buildx build --platform linux/amd64 --load -t my-service .. Cloud Run supports the Linux x86_64 ABI, so an arm64 image built natively on your Mac is the wrong target. - Run it as Cloud Run would. Run
docker run --rm --platform linux/amd64 -e PORT=8080 -p 8080:8080 my-service. Oxcart runs the amd64 image through Rosetta. - Call it. Run
curl localhost:8080. To check the PORT handling, start a second copy with-e PORT=9090 -p 9090:9090. - Push to Artifact Registry. Run
gcloud auth configure-docker REGION-docker.pkg.devonce, then tag the image asREGION-docker.pkg.dev/PROJECT/REPO/my-service:TAGanddocker pushit.
What we ran on Oxcart
A small Python service that binds $PORT, run twice with different ports, plus Google’s own sample image:
docker run -d -e PORT=8080 -p 18081:8080 my-service
docker run -d -e PORT=9090 -p 18090:9090 my-service
curl localhost:18081 # hello from PORT=8080
curl localhost:18090 # hello from PORT=9090
docker run -d -e PORT=8080 -p 8081:8080 us-docker.pkg.dev/cloudrun/container/hello
docker image inspect us-docker.pkg.dev/cloudrun/container/hello --format '{{.Os}}/{{.Architecture}}'
# linux/amd64
curl localhost:8081 # "It's running!" Google’s sample is an amd64-only image; Oxcart pulled it and ran it through Rosetta without extra flags. For amd64-only images you build yourself, pass --platform linux/amd64 on docker run as well: in our tests a locally built amd64 image was found only when the platform was given.
Building for linux/amd64 on Apple silicon
# Cloud Run target: amd64
docker buildx build --platform linux/amd64 --load -t my-service:amd64 .
# Both architectures in the local image store (runs locally either way)
docker buildx build --platform linux/amd64,linux/arm64 -t my-service:multi . Oxcart’s default builder lists both linux/amd64 and linux/arm64, and the multi-platform build completes locally with both variants runnable. Pushing a multi-platform image from Oxcart is not supported, so for Cloud Run push the amd64 tag, which is the one it runs. Builds are quick: 341 ms uncached and 121 ms cached for a small local context, the fastest of the three runtimes we tested.
Push to Artifact Registry and deploy
gcloud auth configure-docker us-central1-docker.pkg.dev
docker tag my-service:amd64 us-central1-docker.pkg.dev/my-project/apps/my-service:1.0.0
docker push us-central1-docker.pkg.dev/my-project/apps/my-service:1.0.0
gcloud run deploy my-service \
--image us-central1-docker.pkg.dev/my-project/apps/my-service:1.0.0 --region us-central1 Dependencies with Compose
A Cloud Run service usually talks to a database, a cache or another service. Run them next to it with Compose, set PORT in the service’s environment, and gate start order on healthchecks:
services:
api:
build: .
platform: linux/amd64
environment:
PORT: "8080"
REDIS_URL: redis://cache:6379
ports: ["8080:8080"]
depends_on:
cache:
condition: service_healthy
cache:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 2s
retries: 30 docker compose up -d --wait returns once the cache is healthy and the API has started. The Compose on Apple silicon guide covers healthchecks, amd64 images and ports in depth.
Ports on your Mac
Published ports listen on all of the Mac’s interfaces, as with Docker’s default. If another Mac app already listens on 127.0.0.1:8080, localhost:8080 can reach that app instead of your container, so pick a free host port (-p 18080:8080) and keep PORT as Cloud Run sets it. Oxcart’s Ports view shows every listener on the Mac next to your containers, and explains why a container port is not answering, including a server bound to 127.0.0.1 inside the container, the same mistake Cloud Run rejects. See container networking on a Mac.
Questions
How do I run a Cloud Run service locally on a Mac?
Run its container with the PORT variable set and the port published: docker run -e PORT=8080 -p 8080:8080 IMAGE. That is the method Google documents for local testing with Docker. On Oxcart we verified it with our own image on ports 8080 and 9090 and with Google’s us-docker.pkg.dev/cloudrun/container/hello sample.
Why does my Cloud Run deploy fail with an image built on Apple silicon?
Google documents that Cloud Run supports the Linux x86_64 ABI and that a multi-architecture image must include linux/amd64. A plain docker build on an Apple silicon Mac produces an arm64 image. Build with --platform linux/amd64.
Can I run the amd64 image on my Mac before deploying?
Yes. Oxcart runs linux/amd64 images through Rosetta. Pass --platform linux/amd64 when you run an amd64-only image you built locally; pulled amd64 images, such as Google’s hello sample, ran without it in our test.
My container works locally but Cloud Run says it failed to listen on the port. Why?
The most common cause is binding to 127.0.0.1 or a hard-coded port. Cloud Run’s container contract says the container must listen on 0.0.0.0 on the port in PORT. Test locally with a non-default PORT (for example 9090) to catch hard-coded ports.
Do I need gcloud beta code dev or Cloud Code?
No, they are optional. Google lists plain Docker, Docker with Google Cloud access, the Cloud Code emulator and gcloud beta code dev as ways to test locally. Docker on Oxcart lets you test the image and its PORT handling directly. gcloud beta code dev adds a Kubernetes-based development workflow using minikube or an existing Kubernetes context; choose it when you need that integration.
How do I give the local container access to Google Cloud APIs?
Mount credentials the way Google documents for “Docker with Google Cloud access”, typically Application Default Credentials passed in through a mounted file and GOOGLE_APPLICATION_CREDENTIALS. Outbound connections from containers on Oxcart are opened by your Mac, so your VPN and DNS apply.
Sources and method
- Examples tested with Oxcart 0.1.2, Mac17,14, macOS 27.2, 2026-10-10 (PORT on 8080 and 9090, Google’s hello sample as linux/amd64, Buildx single- and multi-platform builds).
- Cloud Run container contract (port, 0.0.0.0, x86_64): https://docs.cloud.google.com/run/docs/container-contract. Local testing options: https://docs.cloud.google.com/run/docs/testing/local and https://docs.cloud.google.com/sdk/gcloud/reference/beta/code/dev. Checked 2026-10-10.
- Build speed: Oxcart 0.1.2, 2026-10-10, Apple M5 Max, 8 CPUs and 12 GiB, medians of five samples; the other runtimes were measured earlier the same day on the same Mac and harness with the docker 29.4 client.