Deploying Next.js on GCP

Published: (February 21, 2026 at 01:36 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

Why move from Vercel to GCP?

LLM Gateway is a full‑stack application with multiple APIs and frontends. Deploying everything through Vercel added another dashboard, another set of credentials, and another thing to manage. By running on Google Cloud Platform directly we consolidate our infrastructure: APIs, databases, and frontends all live in the same place.

Architecture

All services run on a Kubernetes cluster on GCP. Each component—API, Gateway, UI, Playground, Docs, Admin, and Worker—is deployed as a separate container. Kubernetes handles autoscaling based on resource usage, scaling up during traffic spikes and scaling down when traffic is low.

The build and deployment pipeline is fully automated via GitHub Actions. On every push to main, Docker images for each service are built and pushed to a container registry.

Building a Next.js app in standalone mode

Add the following to next.config.js:

module.exports = {
  output: "standalone",
};

This bundles the app into a self‑contained folder with all dependencies.

Dockerfile

# Builder stage
FROM node:20-slim AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

# Runner stage
FROM node:20-slim AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV HOSTNAME="0.0.0.0"
ENV PORT=80

COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

EXPOSE 80
CMD ["node", "server.js"]

Build and push the image

docker build -t gcr.io/your-project/your-app:latest .
docker push gcr.io/your-project/your-app:latest

Deploy to Cloud Run

gcloud run deploy your-app \
  --image gcr.io/your-project/your-app:latest \
  --platform managed \
  --region us-central1 \
  --allow-unauthenticated

Kubernetes Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: your-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: your-app
  template:
    metadata:
      labels:
        app: your-app
    spec:
      containers:
        - name: your-app
          image: gcr.io/your-project/your-app:latest
          ports:
            - containerPort: 80
          resources:
            requests:
              memory: "256Mi"
              cpu: "100m"
            limits:
              memory: "512Mi"
              cpu: "500m"

Apply with:

kubectl apply -f deployment.yaml

Horizontal Pod Autoscaler

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: your-app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: your-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

This scales the deployment between 2 and 10 replicas based on CPU utilization.

GitHub Actions workflow (build & push)

Save the following as .github/workflows/build.yml:

name: Build and Push

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - uses: docker/setup-buildx-action@v3

      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

Takeaway

If you’re already on GCP, adding Vercel may be unnecessary. Kubernetes and Cloud Run handle Next.js well, and keeping everything in one place simplifies operations while delivering excellent performance and low latency.

0 views
Back to Blog

Related posts

Read more »