Skip to main content

Command Palette

Search for a command to run...

From Local Next.js App to Production Docker Image on Google Cloud

Published
4 min read
A

A Simple Web Developer & Designer

If you’re building a Next.js App Router project with TypeScript, using pnpm and want to deploy it as a Docker container, this guide is for you.

We’ll go from local developmentDocker buildversioned image taggingdeployment to Google Cloud Run, all automated with scripts.


Prerequisites

Make sure you have these installed on your system:

  • Node.js 20+

  • pnpm (comes with Node via Corepack)

  • Docker

  • Google Cloud CLI (gcloud)

Verify your tools:

pnpm --version
docker --version
gcloud --version

If gcloud isn’t installed, follow Google’s Cloud SDK installation guide.

Then log in:

gcloud auth login

Step 1: Local Development Setup

Your Next.js (App Router + TypeScript) project structure should look like this:

my-nextjs-app/
├─ app/
├─ public/
├─ .next/
├─ package.json
├─ pnpm-lock.yaml
├─ tsconfig.json
├─ .eslintrc.json
└─ scripts/
   ├─ build-docker.sh
   └─ deploy-tag.sh

Run locally in development mode

pnpm install
pnpm dev

Open your app at http://localhost:3000 to verify everything works before containerizing it.


Step 2: Create a Production Dockerfile

We’ll use a multi-stage Docker build for performance and security.

Dockerfile

# ================================
# 🏗️ Builder Stage
# ================================
FROM node:20-bullseye-slim AS builder
WORKDIR /app

# Disable telemetry & set production mode
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production

# Install build tools
RUN apt-get update && apt-get install -y --no-install-recommends \
    git bash curl python3 build-essential libc6-dev \
    && rm -rf /var/lib/apt/lists/*

# Enable pnpm
RUN corepack enable pnpm && corepack prepare pnpm@latest --activate

# Copy package files and install dependencies
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile \
    && pnpm add -D eslint@8.57.0 @next/eslint-plugin-next eslint-plugin-react-hooks@4.6.2

# Copy all project files
COPY . .

# Run lint and TypeScript checks
RUN pnpm run lint
RUN pnpm exec tsc --noEmit

# Build Next.js
RUN pnpm run build

# Remove sensitive files
RUN rm -f .env .env.build

# Prune dev dependencies
RUN pnpm prune --prod


# ================================
# 🚀 Runner Stage (Production)
# ================================
FROM node:20-bullseye-slim AS runner
WORKDIR /app

# Minimal runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends libc6-dev \
    && rm -rf /var/lib/apt/lists/* \
    && npm install -g sharp@0.33.0

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV NEXT_SHARP_PATH=/usr/local/lib/node_modules/sharp
ENV HOSTNAME="0.0.0.0"
ENV PORT=3000
ENV NODE_OPTIONS="--unhandled-rejections=strict"

# Create non-root user
RUN groupadd --system nodejs && useradd --system --uid 1001 -g nodejs nextjs

# Copy built assets from builder
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json

USER nextjs
EXPOSE 3000

# Start the app
CMD ["pnpm", "start"]

⚙️ Step 3: Automate Build with build-docker.sh

To simplify the image build process, add this script in your scripts/ folder:

scripts/build-docker.sh

#!/bin/bash

# Usage: ./scripts/build-docker.sh v1.1.0 production

VERSION=$1
ENV=$2

if [ -z "$VERSION" ] || [ -z "$ENV" ]; then
  echo "❌ Usage: ./scripts/build-docker.sh <version> <env>"
  exit 1
fi

IMAGE_NAME="my-nextjs-app:$VERSION-$ENV"

echo "🏗️ Building Docker image: $IMAGE_NAME ..."

docker build -t $IMAGE_NAME .

echo "✅ Build complete!"

Make it executable:

chmod +x scripts/build-docker.sh

Then build your image:

sudo sh ./scripts/build-docker.sh v1.1.0 production

🔖 Step 4: Push & Deploy with deploy-tag.sh

After building, we’ll tag and deploy the image to Google Cloud.

scripts/deploy-tag.sh

#!/bin/bash

# Usage: ./scripts/deploy-tag.sh v1.1.0 production

VERSION=$1
ENV=$2

if [ -z "$VERSION" ] || [ -z "$ENV" ]; then
  echo "❌ Usage: ./scripts/deploy-tag.sh <version> <env>"
  exit 1
fi

PROJECT_ID="your-gcp-project-id"
IMAGE_NAME="gcr.io/$PROJECT_ID/my-nextjs-app:$VERSION-$ENV"

echo "🔑 Authenticating with Google Cloud..."
gcloud auth login
gcloud auth configure-docker

echo "🏗️ Building Docker image..."
docker build -t $IMAGE_NAME .

echo "📤 Pushing image to Google Container Registry..."
docker push $IMAGE_NAME

echo "🚀 Deploying to Cloud Run..."
gcloud run deploy my-nextjs-app \
  --image $IMAGE_NAME \
  --platform managed \
  --region us-central1 \
  --allow-unauthenticated \
  --set-env-vars NODE_ENV=production

echo "✅ Deployment complete! Image: $IMAGE_NAME"

Make it executable:

chmod +x scripts/deploy-tag.sh

🌐 Step 5: Run Everything

Build locally

pnpm build

Build Docker image

sudo sh ./scripts/build-docker.sh v1.1.0 production

Push & Deploy

sudo sh ./scripts/deploy-tag.sh v1.1.0 production

You’ll see output like this:

🔑 Authenticating with Google Cloud...
🏗️ Building Docker image...
📤 Pushing image to registry...
🚀 Deploying to Cloud Run...
✅ Deployment complete! Image: gcr.io/my-project/my-nextjs-app:v1.1.0-production

Now your app is live on a public URL provided by Cloud Run 🎉


Bonus Tips

  • Always remove .env files from your build context.

  • Use .dockerignore to exclude unnecessary files like .next/cache and node_modules.

  • Use versioned tags (v1.1.0, v1.1.1, etc.) for traceable deployments.

  • Add both build-docker.sh and deploy-tag.sh in your CI/CD pipeline for full automation.

Result

You’ve just built a full production-ready pipeline:

  • Develop locally with pnpm dev

  • Build and test your app inside Docker

  • Tag and push versioned images

  • Deploy seamlessly to Google Cloud Run

This setup ensures consistency, security, and reproducibility across all environments — from your laptop to the cloud.