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:
- Node.js ≥ 18 (preferably 20 or 22) → https://nodejs.org
Check with:
node -v - Git → https://git-scm.com/downloads
Check with:
git --version - Docker Desktop (Windows/Mac) or Docker Engine (Linux) → https://www.docker.com/products/docker-desktop
Check with:
docker --versionanddocker 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
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 dev → http://localhost:3000
your package.json scripts should look like this
"scripts": {
"dev": "next dev --turbo",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
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
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"]
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;
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()
});
}
.dockerignore (Speeds Up Builds 5x)
node_modules
.next
.env*.local
.git
.gitignore
README.md
Dockerfile
docker-compose*.yml
Build & Run It (The Moment of Truth)
docker build -t nextjs-prod .
docker run -d -p 3000:3000 --name prod nextjs-prod
Open http://localhost:3000 → It just works.
Check image size:
docker images nextjs-prod
# → ~84 MB (vs 1 GB+ the "wrong" way)
Removing Container and Image
docker rm -f nextjs-prod
docker rm -f nextjs-prod-app
docker rmi nextjs-prod
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
Now run in your terminal (PowerShell or cmd):
docker compose up --build
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
if you want both at the same time in one terminal
docker compose up nextjs-dev nextjs-prod
→ 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
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.
This error means Docker Desktop’s Linux engine is not running right now (it probably stopped or crashed after the long prod build).
- Look at your system tray (bottom-right near the clock)
- Find the Docker whale icon
- If it’s gray or has a red dot → Docker is stopped
- 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
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
Stop BOTH at once
docker compose down
When hot-reload feels weird or .next is corrupted
docker compose down -v
docker compose up nextjs-dev # fresh .next cache
When you changed Dockerfile or node_modules
docker compose down --rmi all
docker compose up --build nextjs-dev
When your PC is crying for disk space
docker system prune -a # frees gigabytes in 10 seconds
Start BOTH at once
docker compose up
# or in background (like a real server)
docker compose up -d
Rebuild + start only one
docker compose up --build nextjs-dev
List running containers
docker compose ps
stops + removes containers + removes YOUR volumes
docker compose down -v
Delete ALL volumes created by this docker-compose file (100% safe)
docker compose down --volumes
Volumes from other projects
docker volume ls
** See logs (super useful!)**
docker logs nextjs-dev -f # -f = follow live
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
See all your local images + their tags
docker images
Folder Structure
my-nextjs-app/
├── .dockerignore
├── .gitignore
├── Dockerfile
├── docker-compose.yml
├── next.config.js
├── package.json
├── app/
│ └── api/health/route.ts
└── public/
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
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)
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.
please share your experience?