Containerizing Next.js 15 (App Router) for Local and Production
Dockerize a Next.js 15 project created with the App Router, using multi-stage builds and Docker Compose.
Next.js 15 brings a stable App Router, React 19 support, and first-class TypeScript config (next.config.ts). In this tutorial you'll containerize a Next.js 15 App Router project for both local development (with hot reloading) and lean production builds using Docker multi-stage builds and the official standalone output mode.
1. Creating a Next.js 15 Project
Use the official CLI options:
This instructs the CLI to:
--app: Use the App Router (app/directory).--src-dir: Place source files insidesrc/for cleaner project structure.--typescript: Enable TypeScript from the start.
You'll end up with a folder structure like:
Verify everything runs locally first:
Visit http://localhost:3000 to see the starter page.
Node.js requirement: Next.js 15 requires Node.js 18.17 or later. All Dockerfiles in this guide use
node:23-alpine, which satisfies this requirement and ships the latest LTS-era active release.
2. next.config.ts
Your new Next.js project includes a TypeScript-based config (next.config.ts). A typical minimal configuration looks like:
When you run npm run build or npm run dev, Next.js automatically detects and uses next.config.ts.
Enabling standalone Output (Recommended for Docker)
For the smallest possible production image, add the output: "standalone" option. Next.js will trace only the files your app actually needs and bundle them into .next/standalone:
This is the approach used in Vercel's official
with-dockerexample and is recommended for self-hosted Docker deployments.
3. Dockerfile for Production (Multi-Stage)
3a. Standard Multi-Stage Build
Create a Dockerfile in your project root:
3b. Standalone Output Build (Smallest Image)
If you added output: "standalone" to next.config.ts, use this leaner Dockerfile instead. The standalone build traces every import and bundles only what is needed — no npm install required in the runner stage:
Why
node server.jsinstead ofnpm start? The standalone output generates a self-containedserver.jsat the project root of.next/standalone. It does not neednextinnode_modules, making the final image significantly smaller.
Highlights
| Approach | Pros | Cons |
|---|---|---|
| Standard multi-stage | Simple, zero config changes | Larger image; copies all node_modules |
standalone output | Smallest image; no prod npm install | Requires output: "standalone" in config |
npm civsnpm install:npm ciis preferred in CI/Docker builds — it installs exact versions frompackage-lock.jsonand errors on mismatches, making builds reproducible.- App Router:
.nextincludes server/client components built for Next.js 15's new rendering model. next.config.ts: Copied so the server can reference it at runtime.
4. Optional Dockerfile for Local Development
If you want to develop inside Docker with hot reloading, create a Dockerfile.dev:
Running this container with volume mounts ensures that changes on your host will trigger fast-refresh reloads in the container.
Performance note: On macOS and Windows, Docker Desktop's file-sync overhead can make hot-reload noticeably slower than running
npm run devnatively. For day-to-day development on these platforms, consider running Next.js locally and only using Docker for production parity checks. Linux hosts generally do not have this overhead.
5. Docker Compose Configuration
5.1 Local Development
Create or update docker-compose.yml:
./:/app: Maps your local directory into the container, enabling hot reload./app/node_modules: Anonymous volume prevents the container'snode_modulesfrom being overwritten by an empty host directory.WATCHPACK_POLLING=true: Forces polling-based file watching. Required on some Docker Desktop setups (especially Windows with WSL2) where inotify events do not propagate correctly through the volume mount.
Run:
Then visit http://localhost:3000.
5.2 Production with Docker Compose
Note:
docker-compose(v1 plugin) is deprecated. Usedocker compose(v2, built into Docker CLI) going forward.
6. Build and Run in Production (Without Compose)
If you prefer to manage containers manually:
- Build:
- Run:
Open http://localhost:3000.
7. Common Pitfalls and Edge Cases
Missing public/ directory
Static assets (/public) must be explicitly copied into the final image. Both Dockerfiles above include this step — skip it and images, fonts, and other static files will 404 in production.
.dockerignore — don't skip this
Without a .dockerignore, Docker will COPY your local node_modules and .next cache into the build context, inflating build times and risking stale artifacts. Create .dockerignore at the project root:
Environment variables and secrets
Next.js distinguishes between build-time (NEXT_PUBLIC_*) and runtime environment variables. Variables prefixed with NEXT_PUBLIC_ are inlined at build time — they must be present as ARG/ENV in the builder stage:
Runtime-only variables (without the NEXT_PUBLIC_ prefix) can be injected at docker run time via -e or --env-file and do not need to be present during the build.
Hot-reload performance on macOS/Windows
On Mac and Windows Docker Desktop, volume-mounted file watching relies on polling or kernel event translation that can add latency. If WATCHPACK_POLLING=true doesn't help, consider Mutagen for faster sync or simply run npm run dev natively.
App Router vs Pages Router
The new App Router structure places compiled server and client components inside .next/server/app/. If you maintain a hybrid app (both app/ and pages/ directories), both are bundled into .next — no extra copy steps needed.
next.config.ts in the runner stage
TypeScript config files (next.config.ts) are read by the Next.js server at runtime. If you use the standalone output, this is already bundled. For the standard build, explicitly copy next.config.ts into the runner as shown above.
Non-root user for security
Running containers as root is a security risk. Add a non-root user to your production Dockerfile:
8. Conclusion
By Dockerizing your Next.js 15 project (created with --app, --src-dir, and --typescript):
- You guarantee a consistent environment across dev, staging, and production.
- Multi-stage builds keep images lightweight by excluding build-only tools and dev dependencies.
- The
standaloneoutput mode produces the smallest possible runtime image — nonode_modulesneeded in production. - Docker Compose simplifies orchestration for both hot-reload development and production deployments.
- Proper
.dockerignore, non-root users, and correctNEXT_PUBLIC_*handling keep your containerized app fast, small, and secure.
With this setup you'll have a smooth, reproducible workflow that leverages all of Next.js 15's App Router features — free from environment mismatches or manual deployment surprises.
Damian Hodgkiss
Senior Staff Engineer at Sumo Group, leading development of AppSumo marketplace. Technical solopreneur with 25+ years of experience building SaaS products.