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

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 5Your 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:latestThis 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.comGoogle 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:
- Add a Domain Mapping in Cloud Run for
www.danywalls.com(or create a CNAME pointing toghs.googlehosted.com). - Create a Redirect Rule in Cloudflare: Set a 301 Redirect Rule matching
https://www.danywalls.com/*forwarding tohttps://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 managedEvery 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 ⚖️
| Feature | Vercel (Hobby) | Google Cloud Run |
|---|---|---|
| Monthly Free Requests | 1 Million | 2 Million |
| Max Function Execution | 10–60 seconds | Up to 60 minutes |
| Build Minutes | 45 min / month | 120 min / day |
| Runtime Control | Edge / Node serverless | Full Docker Linux Container |
| Setup Complexity | Zero-config | Initial Dockerfile & CLI setup |
| Vendor Portability | Vercel ecosystem | Any container platform (AWS, Fly, GCP) |
Recap & Next Steps 🛠️
Migrating my Next.js site to Google Cloud Run provided three major benefits:
- Predictable $0 Invoices: The free tier easily handles tens of thousands of monthly visitors across multiple sites.
- True Container Portability: The Docker image runs identically locally, in staging, and across any cloud provider.
- 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!
Explore more in-depth guides and real-world architectures in the Cloud 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
Next.js on Google Cloud Run: Production Caching, Secrets, and Zero Cold Starts
A production guide to running Next.js on Google Cloud Run: configure Cloud CDN asset caching, mount Secret Manager env variables, reduce cold starts, and automate Cloud Build.
How To Get Started with Docker: A Step-by-Step Guide for Beginners
Being ready to deploy your apps with Docker is a higher-demand knowledge today, and this article will guide you (and me) from zero to surviving with Docker. The guide's key features are: Install and run the docker Create and build images Mount vol...
My Newsletter Went to Spam: How I Fixed Email Deliverability for Free with Cloudflare
A friend told me my emails were hitting his Spam folder. Here is how I fixed my deliverability using Cloudflare Email Routing and custom domain authentication for $0.
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.
Join 13,800+ developers and readers.
No spam ever. Unsubscribe at any time.