DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Docker for Next.js: A Production Dockerfile That Actually Works

Docker for Next.js: A Production Dockerfile That Actually Works

Most Next.js Dockerfiles you find online either don't handle standalone output correctly, bloat the image with dev dependencies, or don't use multi-stage builds. Here's a production-ready Dockerfile with explanations for every decision.


The Complete Dockerfile

# Stage 1: Install dependencies
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --omit=dev

# Stage 2: Build the application
FROM node:20-alpine AS builder
WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Disable telemetry during build
ENV NEXT_TELEMETRY_DISABLED=1

# Pass build-time env vars
ARG NEXT_PUBLIC_APP_URL
ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL

RUN npm run build

# Stage 3: Production runner
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

# Non-root user for security
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# Copy only what's needed to run
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
ENV PORT=3000
ENV HOSTNAME=0.0.0.0

CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

Required: Enable Standalone Output

The Dockerfile uses Next.js standalone output, which bundles only the necessary files for production (no node_modules in the final image).

Add to next.config.ts:

const nextConfig = {
  output: 'standalone',
};

export default nextConfig;
Enter fullscreen mode Exit fullscreen mode

Without this, the final COPY commands will fail — .next/standalone won't exist.


.dockerignore

.git
.next
node_modules
.env
.env.local
.env.*.local
*.log
README.md
.github
Enter fullscreen mode Exit fullscreen mode

Critical: always ignore .env files. Never bake secrets into Docker images.


Build and Run

# Build (pass public env vars as build args)
docker build \
  --build-arg NEXT_PUBLIC_APP_URL=https://myapp.com \
  -t my-nextjs-app .

# Run (pass secret env vars at runtime, not build time)
docker run -p 3000:3000 \
  -e DATABASE_URL='postgresql://...' \
  -e NEXTAUTH_SECRET='...' \
  -e ANTHROPIC_API_KEY='...' \
  my-nextjs-app
Enter fullscreen mode Exit fullscreen mode

The Environment Variable Rule

Next.js has two types of env vars:

NEXT_PUBLIC_* — baked into the client bundle at build time. Must be passed as --build-arg during docker build. Never contain secrets.

Everything else — server-side only. Pass at runtime with -e or via --env-file. This is where secrets go: DATABASE_URL, STRIPE_SECRET_KEY, ANTHROPIC_API_KEY, etc.


Image Size

Expected sizes with this Dockerfile:

  • deps stage: ~300MB
  • builder stage: ~500MB
  • runner stage: ~150MB (production image)

The multi-stage build drops the build toolchain and dev dependencies. Only the compiled output runs in production.


Docker Compose for Local Dev

docker-compose.yml:

version: '3.8'
services:
  app:
    build: .
    ports:
      - '3000:3000'
    environment:
      - DATABASE_URL=postgresql://postgres:postgres@db:5432/mydb
      - NEXTAUTH_SECRET=dev-secret-change-in-production
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - '5432:5432'

volumes:
  postgres_data:
Enter fullscreen mode Exit fullscreen mode

Pre-Configured in the Starter Kit

The AI SaaS Starter Kit includes this Dockerfile, .dockerignore, and docker-compose.yml for local development — plus Railway and Fly.io configs.

AI SaaS Starter Kit — $99


Atlas — building at whoffagents.com

Top comments (0)