---
title: "Moving from Vercel Next.js to Google Cloud Run: A Cost and Architecture Guide"
date: "2026-03-11"
slug: "migrating-nextjs-from-vercel-to-gcp"
author: "Dany Paredes"
canonical: "https://danywalls.com/migrating-nextjs-from-vercel-to-gcp"
description: "Why I migrated my Next.js projects from Vercel to Google Cloud Run, how the standalone Docker build works, and how to stay on the free tier."
---


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:

```typescript
// 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:

```dockerfile
# 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

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

### Step 2: Build the container remotely

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

### Step 3: Deploy to Cloud Run

```bash
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**:

```bash
# 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:

```bash
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:

```yaml
# .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 ⚖️

| 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:
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](/nextjs-on-cloud-run-production-guide) 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](/fix-newsletter-spam-with-cloudflare-email-routing) and the [Docker Step-by-Step Guide for Beginners](/how-to-get-started-with-docker-a-step-by-step-guide-for-beginners)!

Happy building!

