Docker + Google Cloud Run: Deploying Node.js Apps to Production

Software Engineer & Full-Stack Developer
After building and deploying three production applications, I've developed a repeatable workflow for shipping Node.js apps using Docker and Google Cloud Run. This post covers the exact setup I use — from Dockerfile to CI/CD pipeline.
Why Cloud Run over traditional VMs?
Cloud Run provides serverless containers — you deploy a Docker image and it auto-scales from zero to thousands of instances. You pay only per request (or per vCPU-second for always-on). No managing Kubernetes clusters, no configuring load balancers. For a solo developer shipping multiple projects, this is gold.
The Dockerfile
Here's the multi-stage Node.js Dockerfile I use. Stage one installs dependencies (including devDeps for build), stage two copies only production artifacts into a slim image:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev
EXPOSE 8080
CMD ["node", "dist/index.js"]CI/CD with GitHub Actions
Every push to main triggers a GitHub Actions workflow that builds the Docker image, pushes it to Artifact Registry, and deploys to Cloud Run:
# .github/workflows/deploy.yml
name: Deploy to Cloud Run
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- run: |
gcloud builds submit --tag gcr.io/project/app:${{ github.sha }}
gcloud run deploy app --image gcr.io/project/app:${{ github.sha }} --region asia-south1Lessons Learned
- Always use multi-stage builds — my final image is ~120MB vs 450MB without it
- Set min-instances=1 for production APIs to avoid cold starts on first request
- Store environment variables in Secret Manager, not in .env files
- Use Cloud Run's built-in revision tagging for easy rollbacks
- Health check endpoint (/health) is required — Cloud Run kills containers that don't respond

Dhiraj Kumar
Dhiraj Kumar is a software engineer and full-stack developer interested in modern web technologies, artificial intelligence, automation, and agentic systems. Through his portfolio and technical writing, he explores practical approaches to building reliable software and AI-powered applications.
Enjoyed this article? Let's connect!
Read More Articles