Cloud
·7 min read·↗

Moving from Vercel Next.js to Google Cloud Run: A Cost and Architecture Guide

Main cover illustration for article: Moving from Vercel Next.js to Google Cloud Run: A Cost and Architecture Guide
Summarize with AI:

Let’s be honest: I love Vercel. It is an amazing platform. The developer experience is easily one of the best you can find in modern web development. If you are starting a new project or need to move fast, I still recommend it without hesitation.

However, as I started hosting more active projects—like personal tools, client side-projects, and my sister’s website—my monthly bill started to climb. Those unpredictable edge execution and bandwidth overages can be an unwelcome surprise at the end of the month.

Since I work daily with Google Cloud Platform (GCP) in enterprise environments, I decided it was time to migrate my blog and side apps to Google Cloud Run. I wanted total architectural control, zero vendor lock-in, and predictable $0 hosting costs.

Here is the complete step-by-step technical journey of how I did it, developer to developer.

Let's begin with packaging our Next.js application into a lightweight, production-ready container.


1. Preparing the Next.js Standalone Build 📦

The biggest mistake developers make when containerizing Next.js is copying the entire node_modules folder into Docker. This results in bloated 2GB+ images and slow deployment times.

Next.js includes a built-in standalone output mode. It uses dependency tracing to bundle only the exact files needed to run your app in production:

// next.config.ts
import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  output: "standalone",
};
 
export default nextConfig;

The Optimized Multi-Stage Dockerfile

Here is the battle-tested, multi-stage Dockerfile that builds a production image of roughly 120MB:

# 1. Base Dependencies
FROM node:20-alpine AS base
RUN apk add --no-cache libc6-compat
WORKDIR /app
 
# 2. Dependencies installation
FROM base AS deps
COPY package.json package-lock.json* ./
RUN npm ci
 
# 3. Builder
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
 
# 4. Production Runner
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
 
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
 
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
 
USER nextjs
EXPOSE 3000
 
CMD ["node", "server.js"]

Now let's explore why Google Cloud Run is the ideal hosting target for this container.


2. Why Google Cloud Run? (The "Goldilocks" Service) ☁️

GCP can feel overwhelming. You have App Engine (rigid and legacy), Compute Engine (too much manual Linux maintenance), and GKE (Kubernetes is overkill for a web app).

Google Cloud Run hits the sweet spot: it is a fully managed, serverless container platform that abstracts away server administration while giving you full Linux container flexibility.

The Developer Free Tier Advantage

Cloud Run offers one of the most generous free tiers in the cloud:

  • 2 Million Requests per month free.
  • 180,000 vCPU-seconds and 360,000 GiB-seconds per month free.
  • Scale-to-Zero: When nobody is visiting your site, CPU usage drops to 0 and costs nothing.
  • 120 Free Build Minutes/Day via Cloud Build.

For a personal blog or side project, this means your monthly bill will almost always be $0.00.

Now let's walk through the deployment commands to get your app running.


3. Building and Deploying with Cloud Build & Artifact Registry 🚀

Instead of building images locally and consuming upload bandwidth, we use Google Cloud Build with Artifact Registry:

Step 1: Create an Artifact Registry repository

gcloud artifacts repositories create blog-repo \
  --repository-format=docker \
  --location=us-central1 \
  --description="Next.js Docker Repository"

Step 2: Build the container remotely

gcloud builds submit \
  --tag us-central1-docker.pkg.dev/YOUR_PROJECT_ID/blog-repo/danywalls-blog .

Step 3: Deploy to Cloud Run

gcloud run deploy danywalls-blog \
  --image us-central1-docker.pkg.dev/YOUR_PROJECT_ID/blog-repo/danywalls-blog \
  --region us-central1 \
  --platform managed \
  --allow-unauthenticated \
  --port 3000 \
  --memory 512Mi \
  --cpu 1 \
  --min-instances 0 \
  --max-instances 5

Your Next.js application is now live on a secure, auto-scaling HTTPS endpoint managed by Google!

Next, let's explore how to handle sensitive environment variables safely.


4. Managing Secrets Securely with Google Secret Manager 🔐

Instead of hardcoding environment variables into your deployment YAML or container image, store sensitive values (API keys, database URLs) in Google Secret Manager:

# 1. Create secret
echo -n "your-secret-api-key" | gcloud secrets create RESEND_API_KEY --data-file=-
 
# 2. Grant Cloud Run access to the secret
gcloud secrets add-iam-policy-binding RESEND_API_KEY \
  --member="serviceAccount:YOUR_PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
  --role="roles/secretmanager.secretAccessor"
 
# 3. Mount secret to Cloud Run
gcloud run services update danywalls-blog \
  --region us-central1 \
  --set-secrets=RESEND_API_KEY=RESEND_API_KEY:latest

This keeps your credentials entirely separated from code commits and build artifacts.

Now let's configure your custom domain and DNS routing.


5. Custom Domains, Cloudflare DNS, and WWW Redirections 🌐

My blog uses both danywalls.dev and danywalls.com. To link your custom domain, you can use Domain Mappings in Cloud Run:

gcloud beta run domain-mappings create --service danywalls-blog --domain danywalls.com

Google provides a list of A and AAAA records. Since I use Cloudflare for DNS management, I added those records directly to Cloudflare.

Handling WWW Redirection and Canonical URLs

One common pitfall after leaving Vercel is that www.danywalls.com may not resolve automatically. Unlike Vercel, Cloud Run treats subdomains as separate entities.

To fix this:

  1. Add a Domain Mapping in Cloud Run for www.danywalls.com (or create a CNAME pointing to ghs.googlehosted.com).
  2. Create a Redirect Rule in Cloudflare: Set a 301 Redirect Rule matching https://www.danywalls.com/* forwarding to https://danywalls.com/$1. This ensures search engines index a single canonical URL and preserves SEO authority.

Now let's automate this entire pipeline with GitHub Actions.


6. Continuous Deployment with GitHub Actions 🤖

To retain the effortless "git push to deploy" developer workflow, set up a GitHub Actions workflow:

# .github/workflows/deploy.yml
name: Deploy to Cloud Run
 
on:
  push:
    branches:
      - main
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
 
      - name: Authenticate with Google Cloud
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_SA_KEY }}
 
      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v2
 
      - name: Build and Push Container
        run: |
          gcloud builds submit \
            --tag us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/blog-repo/danywalls-blog .
 
      - name: Deploy Revision to Cloud Run
        run: |
          gcloud run deploy danywalls-blog \
            --image us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/blog-repo/danywalls-blog \
            --region us-central1 \
            --platform managed

Every commit pushed to main builds automatically in GCP and triggers a zero-downtime rolling update.

Let's review the architectural trade-offs between both platforms.


Vercel vs. Google Cloud Run: Feature Comparison ⚖️

FeatureVercel (Hobby)Google Cloud Run
Monthly Free Requests1 Million2 Million
Max Function Execution10–60 secondsUp to 60 minutes
Build Minutes45 min / month120 min / day
Runtime ControlEdge / Node serverlessFull Docker Linux Container
Setup ComplexityZero-configInitial Dockerfile & CLI setup
Vendor PortabilityVercel ecosystemAny container platform (AWS, Fly, GCP)

Recap & Next Steps 🛠️

Migrating my Next.js site to Google Cloud Run provided three major benefits:

  1. Predictable $0 Invoices: The free tier easily handles tens of thousands of monthly visitors across multiple sites.
  2. True Container Portability: The Docker image runs identically locally, in staging, and across any cloud provider.
  3. Enterprise DevOps Experience: Clean secrets management, remote Cloud Build execution, and full control over infrastructure.

[!TIP] Next in this series: Ready for production? Read Part 2: Next.js on Google Cloud Run: Production Caching, Secrets, and Zero Cold Starts covering Cloud CDN edge caching, Google Secret Manager, and automated Cloud Build CI/CD.

If you are expanding your cloud and infrastructure setup, check out my articles on Fixing Newsletter Spam with Cloudflare and the Docker Step-by-Step Guide for Beginners!

Happy building!

Part of the Cloud Series

Explore more in-depth guides and real-world architectures in the Cloud series.

View Entire Series

Frequently Asked Questions

Why migrate a Next.js application from Vercel to Google Cloud Run?

Google Cloud Run offers a generous free tier (2 million requests/month, 180,000 vCPU-seconds), removes strict serverless timeout limits (up to 60-minute execution), provides 120 free build minutes per day via Cloud Build, and prevents surprise bandwidth and edge function bills across multiple hobby or client projects.

What is Next.js standalone output mode and why is it needed for Docker?

Standalone mode (output: 'standalone' in next.config.ts) automatically traces and bundles only the necessary production dependencies into the .next/standalone folder, shrinking container image sizes from several gigabytes down to around 100MB.

How do you securely manage environment variables and API keys on Cloud Run?

Store your sensitive keys in Google Secret Manager and bind them directly to your Cloud Run service via the --set-secrets flag, granting your service account the 'Secret Manager Secret Accessor' role without baking secrets into Docker images.

How do you handle custom domains and www redirects with Cloud Run?

Map your custom domain using Cloud Run Domain Mappings or Cloud Load Balancing, configure your DNS records in Cloudflare, and create a Cloudflare Redirect Rule (e.g., 301 from www.* to root) to maintain consistent SEO canonical URLs.

Related Articles

Share this article

If you found this guide helpful, consider sharing it with your team or fellow developers.


Real Software. Real Lessons.

I share the lessons I learned the hard way, so you can either avoid them or be ready when they happen.

User avatar
User avatar
User avatar
User avatar
+13K

Join 13,800+ developers and readers.

No spam ever. Unsubscribe at any time.