Skip to content
Innovialabs

How to Deploy a Docker Container to Google Cloud Run

Practice4 min

In short

Build an image that listens on the port from the PORT variable, push it to Artifact Registry and run gcloud run deploy; Cloud Run starts the containers, gives you an HTTPS URL and scales from zero, and you pay mainly for the time spent handling requests.

Short answer: how it works

Cloud Run is Google Cloud's serverless platform for containers. You hand it a Docker image, and it runs the image, gives you an HTTPS URL, scales copies with load and shuts them down to zero when there are no requests.

The path from code to a running service:

  1. A Dockerfile where the app listens on the port from the PORT variable.
  2. Building the image and pushing it to Artifact Registry.
  3. The gcloud run deploy command.
  4. Configuring variables, secrets, scaling and a domain.

Step 1. Prepare the Dockerfile

The key requirement: the app must listen on the port from the PORT environment variable (Cloud Run passes 8080 by default) on 0.0.0.0. A Node.js example:

FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
CMD ["node", "server.js"]

Tips:

  • keep the image small, which speeds up cold starts;
  • do not keep state on local disk: the container file system is temporary and there may be several copies;
  • write logs to stdout and stderr, and they land in Cloud Logging automatically.

Step 2. Build the image and push it to Artifact Registry

Enable the required APIs and create a repository:

gcloud services enable run.googleapis.com artifactregistry.googleapis.com secretmanager.googleapis.com

gcloud artifacts repositories create apps \
  --repository-format=docker --location=europe-west1

gcloud auth configure-docker europe-west1-docker.pkg.dev

Build and push the image:

docker build --platform linux/amd64 \
  -t europe-west1-docker.pkg.dev/PROJECT_ID/apps/api:v1 .
docker push europe-west1-docker.pkg.dev/PROJECT_ID/apps/api:v1

The --platform linux/amd64 flag matters if you build on an ARM-based Mac. Without local Docker, you can build in the cloud with gcloud builds submit --tag <image address>.

Step 3. Deploy the service

gcloud run deploy api \
  --image=europe-west1-docker.pkg.dev/PROJECT_ID/apps/api:v1 \
  --region=europe-west1 \
  --allow-unauthenticated

After deployment the command prints a URL like https://api-....run.app. The --allow-unauthenticated flag makes the service public; for internal APIs leave it out and control access with IAM.

Every deployment creates a new revision. You can split traffic between revisions and roll back to the previous one quickly.

Step 4. Environment variables and secrets

Pass regular settings as variables:

gcloud run services update api --region=europe-west1 \
  --set-env-vars=APP_ENV=production,LOG_LEVEL=info

Keep passwords and keys in Secret Manager, not in variables or the image:

printf 'super-secret' | gcloud secrets create db-password --data-file=-

gcloud run services update api --region=europe-west1 \
  --set-secrets=DB_PASSWORD=db-password:latest

The service account Cloud Run runs as needs the Secret Manager Secret Accessor role on that secret.

Step 5. Concurrency and min instances

  • Concurrency is how many requests one instance handles at the same time. The default is 80. Keep it for lightweight async APIs; lower it for heavy work or single-threaded apps.
  • Min instances is how many instances stay running at all times. A value of 1 or more removes cold starts, but those instances are billed even when idle.
  • Max instances is an upper limit that protects you from an unexpected bill and from overloading the database.
gcloud run services update api --region=europe-west1 \
  --concurrency=40 --min-instances=1 --max-instances=10

Step 6. Custom domain

Ways to attach a domain:

  • Cloud Run domain mapping is the simplest: verify domain ownership, add DNS records, and a certificate is issued automatically. It is not available in every region and has limitations, so check the documentation.
  • Global Application Load Balancer with a serverless NEG is the recommended production option: it works in any region and supports CDN and Cloud Armor.
  • Firebase Hosting with a rewrite to Cloud Run is handy if your frontend lives there too.

How billing works

You pay for vCPU, memory and the number of requests, plus outbound traffic, image storage and secrets. There is a monthly free tier.

Two billing modes:

  • Request-based: CPU is allocated only while a request is being processed. With no requests and min instances at zero, you pay almost nothing.
  • Instance-based: CPU is always allocated and the whole instance lifetime is billed. Needed for background work between requests.

To keep costs in check, set max instances, configure a budget with alerts and avoid min instances where a cold start is acceptable.

FAQ

WebSockets are supported, but the connection is limited by the request timeout. For long background work, Cloud Run jobs are a better fit, since they run to completion without an HTTP request.

We can help with this

If you'd rather get the result than figure it all out yourself — this is what we do every day.

Discuss a project

Read next

Have an idea or a task? Tell us about it

We'll reply within one business day and propose a solution. The consultation is free.