November 18, 20256 min read

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

Dhiraj Kumar

Dhiraj Kumar

Software Engineer & Full-Stack Developer

DockerGCPNode.jsDevOpsCI/CD

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-south1

Lessons 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

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