DEV Community

Mina Golzari Dalir
Mina Golzari Dalir

Posted on

Docker for Developers: From Zero to Running Your First Production-Grade Next.js App in 1 Hour

Hey there! Welcome to the only Next.js + Docker guide you’ll ever need in 2025.
By the end of this single post (and ~60 minutes of your time), you will:

Ship a real Next.js 15 app in a container smaller than 90 MB
Run it exactly the same way on Mac, Windows, Linux, or even in the cloud
Make your entire team happy (no more “it works on my machine”)
Be ready to deploy to Railway, Render, Fly.io, or Kubernetes with zero changes

Let’s go – coffee ready?

What You’ll Build Today
A production-grade Next.js 15 (App Router) app that includes:

  • < 90 MB Docker image
  • Multi-stage build (no dev dependencies in production)
  • Non-root user + health checks
  • Hot reload for development
  • One-command workflow for the whole team
  • Works everywhere: local, CI, GitHub Codespaces, Gitpod

Prerequisites
Make sure you have these installed:

  1. Node.js ≥ 18 (preferably 20 or 22) → https://nodejs.org Check with: node -v
  2. Git → https://git-scm.com/downloads Check with: git --version
  3. Docker Desktop (Windows/Mac) or Docker Engine (Linux) → https://www.docker.com/products/docker-desktop Check with: docker --version and docker compose version (Compose V2 is now built-in) If any of these are missing, install them first.

** Step 1: Create the Next.js App **

# 1. Create the project
npx create-next-app@latest my-nextjs-app --ts --app --eslint --tailwind --src-dir --import-alias "@/*"
# 2. Go into the folder
cd my-nextjs-app
# 3. Open it in your editor
code .    # if you use VS Code
Enter fullscreen mode Exit fullscreen mode

What we just chose and why (important for Docker later):

--app→ App Router (new standard, better for Docker + Server Actions)
--src-dir → Keeps everything clean inside /src
--turbo → Uses Turbopack in dev (faster than webpack)
--import-alias "@/*" → Clean imports like @/components/Button

Test it quickly: npm run devhttp://localhost:3000
your package.json scripts should look like this

"scripts": {
  "dev": "next dev --turbo",
  "build": "next build",
  "start": "next start",
  "lint": "next lint"
}
Enter fullscreen mode Exit fullscreen mode

Initialize Git + create GitHub repo + first commit
Run these commands one by one in your project folder:

# 1. Initialize git
git init

# 2. Create a nice .gitignore (Next.js already has one, but we make it perfect for Docker)

# Docker
.dockerignore
Dockerfile
docker-compose.yml

# Node
node_modules/
.env.local
EOF

# 3. First commit
git add .
git commit -m "feat: initial commit - Next.js app ready for Docker"

# 4. Create the GitHub repo (do this in your browser)
# → Go to https://github.com/new
# Name: my-nextjs-docker-app (or whatever you want)
# Keep it Public or Private — your choice
# DO NOT initialize with README (we already have one)
# Click "Create repository"

# 5. Link and push (replace YOUR_USERNAME with your GitHub username)
git remote add origin https://github.com/YOUR_USERNAME/my-nextjs-docker-app.git
git branch -M main
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

Step 2: The 2025 Production Dockerfile (The One Everyone Copies)
Create Dockerfile(root of project):

# Build stage -> we name this stage so we can copy from it later
FROM node:22-alpine AS builder 
# All following commands run inside /app folder
WORKDIR /app
# Copy ONLY dependency files first → Docker can cache this layer
COPY package*.json ./
#clean install (faster + reproducible, perfect for Docker)
RUN npm ci
# Now copy the rest of your code
COPY . .
# Runs next build → creates the optimized .next folder + standalone server
RUN npm run build

# Prune dev dependencies
FROM node:22-alpine AS pruner
WORKDIR /app
COPY --from=builder /app ./
RUN npm ci --omit=dev && npm cache clean --force

# Final tiny image
FROM node:22-alpine AS runtime
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=pruner --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=pruner --chown=nextjs:nodejs /app/package.json ./
COPY --from=pruner --chown=nextjs:nodejs /app/public ./public
COPY --from=pruner --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=pruner --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs
EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget -qO- http://localhost:3000/api/health || exit 1

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

Magic Line You Must Add (next.config.js)

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone', // This makes Next.js create server.js
};

module.exports = nextConfig;
Enter fullscreen mode Exit fullscreen mode

Tiny Health Check (app/api/health/route.ts)

import { NextResponse } from 'next/server';

export async function GET() {
  return NextResponse.json({ 
    status: 'ok', 
    timestamp: new Date().toISOString() 
  });
}
Enter fullscreen mode Exit fullscreen mode

.dockerignore (Speeds Up Builds 5x)

node_modules
.next
.env*.local
.git
.gitignore
README.md
Dockerfile
docker-compose*.yml
Enter fullscreen mode Exit fullscreen mode

Build & Run It (The Moment of Truth)

docker build -t nextjs-prod .
docker run -d -p 3000:3000 --name prod nextjs-prod
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:3000 → It just works.
Check image size:

docker images nextjs-prod
# → ~84 MB (vs 1 GB+ the "wrong" way)
Enter fullscreen mode Exit fullscreen mode

Removing Container and Image

docker rm -f nextjs-prod 
docker rm -f nextjs-prod-app

docker rmi nextjs-prod 
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml (new file in root)-Two services explained

services:
nextjs-dev:                     # ← Development container
    build: .                      # Use the same Dockerfile
    ports:
      - "3000:3000"               # Host port 3000 → container port 3000
    environment:
      NODE_ENV: development       # Enables hot reload
    volumes:
      - .:/app                    # Your code changes instantly appear inside container
      - /app/node_modules         # Don’t let host node_modules override container ones
      - /app/.next                # Keep build cache on host (faster restarts)
    command: npm run dev          # Override CMD from Dockerfile

  nextjs-prod:                    # ← Real production preview
    build: .
    ports:
      - "3001:3000"               # Different host port so you can run both at once
    environment:
      NODE_ENV: production
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000"]
      interval: 30s
      timeout: 10s
      retries: 3


Enter fullscreen mode Exit fullscreen mode

Now run in your terminal (PowerShell or cmd):

docker compose up --build
Enter fullscreen mode Exit fullscreen mode

You should see it build and start on http://localhost:3000 with hot reload.
Then open a second terminal and run:

docker compose up --build nextjs-prod
docker compose up --build nextjs-dev
Enter fullscreen mode Exit fullscreen mode

if you want both at the same time in one terminal

docker compose up nextjs-dev nextjs-prod
Enter fullscreen mode Exit fullscreen mode

http://localhost:3001 will show the real production version (super fast and tiny image ~90MB).

A common super common on Windows with WSL2 + Docker Desktop when it’s still configured to use Linux containers but somehow tries to pull a Windows image.

no matching manifest for windows/amd64 10.0.26100 in the manifest list entries
Enter fullscreen mode Exit fullscreen mode

Right-click the Docker Desktop whale icon in your system tray → make sure it says “Linux containers” (not “Windows containers”).

DesktopLinuxEngine: The system cannot find the file specified.
Enter fullscreen mode Exit fullscreen mode

This error means Docker Desktop’s Linux engine is not running right now (it probably stopped or crashed after the long prod build).

  1. Look at your system tray (bottom-right near the clock)
  2. Find the Docker whale icon
  3. If it’s gray or has a red dot → Docker is stopped
  4. Just double-click the whale icon → Docker Desktop will restart automatically

Rebuild and run

# Remove old broken images
docker compose down --rmi all

# Build & start dev (this time it WILL have the next binary)
docker compose up --build nextjs-dev
Enter fullscreen mode Exit fullscreen mode

Stop / Start only the dev container

# Stop dev only
docker compose stop nextjs-dev

# Start dev again (instant, no rebuild)
docker compose start nextjs-dev

# Or restart dev in one command
docker compose restart nextjs-dev
Enter fullscreen mode Exit fullscreen mode

Stop BOTH at once

docker compose down
Enter fullscreen mode Exit fullscreen mode

When hot-reload feels weird or .next is corrupted

docker compose down -v
docker compose up nextjs-dev          # fresh .next cache
Enter fullscreen mode Exit fullscreen mode

When you changed Dockerfile or node_modules

docker compose down --rmi all
docker compose up --build nextjs-dev
Enter fullscreen mode Exit fullscreen mode

When your PC is crying for disk space

docker system prune -a               # frees gigabytes in 10 seconds
Enter fullscreen mode Exit fullscreen mode

Start BOTH at once

docker compose up
# or in background (like a real server)
docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Rebuild + start only one

docker compose up --build nextjs-dev
Enter fullscreen mode Exit fullscreen mode

List running containers

docker compose ps
Enter fullscreen mode Exit fullscreen mode

stops + removes containers + removes YOUR volumes

docker compose down -v
Enter fullscreen mode Exit fullscreen mode

Delete ALL volumes created by this docker-compose file (100% safe)

docker compose down --volumes
Enter fullscreen mode Exit fullscreen mode

Volumes from other projects

docker volume ls
Enter fullscreen mode Exit fullscreen mode

** See logs (super useful!)**

docker logs nextjs-dev -f          # -f = follow live
Enter fullscreen mode Exit fullscreen mode

Tag an existing image with multiple tags

docker tag nextjs-docker-app-nextjs-prod username/nextjs-docker-app:latest
docker tag nextjs-docker-app-nextjs-prod username/nextjs-docker-app:v1
Enter fullscreen mode Exit fullscreen mode

See all your local images + their tags

docker images
Enter fullscreen mode Exit fullscreen mode

Folder Structure

my-nextjs-app/
├── .dockerignore
├── .gitignore
├── Dockerfile
├── docker-compose.yml
├── next.config.js
├── package.json
├── app/
│   └── api/health/route.ts
└── public/
Enter fullscreen mode Exit fullscreen mode

Now that you have a perfect Docker setup, here are the real-world ways to share the exact same image with your team (or with CI/CD, hosting, etc.):

Step 3: Push to Docker Hub

# 1. Log in to Docker Hub (create account at `hub.docker.com` if you don't have one)
docker login

# 2. Tag your prod image with your username
docker tag nextjs-docker-app-nextjs-prod username/nextjs-docker-app:latest

# 3. Push it
docker push username/nextjs-docker-app:latest

# Then your entire team (or any server in the world) can start your exact production app in 5 seconds with:

docker run -d -p 3000:3000 username/nextjs-docker-app:latest

# Only download the image (no container starts)
docker pull username/nextjs-docker-app:latest

Enter fullscreen mode Exit fullscreen mode

You just graduated from “Docker curious” to “Docker professional” in under an hour.

I’ve personally used this exact setup on every new Next.js projects.
If this guide saves even one hour of your life, drop a comment below and tell me how it went — I read every single one.

Top comments (2)

Collapse
 
shemith_mohanan_6361bb8a2 profile image
shemith mohanan

Great walkthrough — especially the multi-stage image design and the pruned runtime stage. Getting a Next.js app under ~90 MB with a clean dev/prod workflow isn’t easy, but you explained it in a very practical way. The health check + standalone output tips are gold for anyone deploying across multiple environments. Solid guide.

Collapse
 
mina_golzari_dalir profile image
Mina Golzari Dalir

please share your experience?