Dockerizing Your Next.js Application for Production
Dockerizing Your Next.js Application for Production
Containerization with Docker has become the standard for deploying modern web applications. It ensures consistency across environments, simplifies deployment, and makes scaling easier. Here's how to properly dockerize your Next.js application for production.
Why Docker for Next.js?
Consistency: Your application runs the same way in development, staging, and production.
Isolation: Dependencies and runtime are contained, reducing conflicts.
Scalability: Easy to spin up multiple containers for load balancing.
CI/CD Integration: Works seamlessly with modern deployment pipelines.
Understanding Next.js Build Modes
Next.js supports different rendering modes:
- Static Export: Pre-rendered static HTML
- Standalone: Optimized production build
- Server-Side Rendering (SSR): Rendered on each request
- Incremental Static Regeneration (ISR): Hybrid approach
For Docker, we'll focus on the standalone build mode, which is optimized for containerized deployments.
Basic Dockerfile
Let's start with a production-ready Dockerfile:
DOCKERFILE
# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
Copy package files
COPY package.json package-lock.json* ./
Install dependencies
RUN npm ci
Stage 2: Builder
FROM node:20-alpine AS builder
WORKDIR /app
Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY . .
Set environment variables for build
ENV NEXT_TELEMETRY_DISABLED 1
Build the application
RUN npm run build
Stage 3: Runner
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
Copy necessary files
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
Set ownership
RUN chown -R nextjs:nodejs /app
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]
Enabling Standalone Output
To use the standalone build, update your next.config.ts:
TYPESCRIPT
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'standalone',
};
export default nextConfig;
Multi-Stage Build Benefits
The Dockerfile above uses multi-stage builds:
1. deps: Installs dependencies (can be cached separately)
2. builder: Builds the application
3. runner: Minimal runtime image with only production files
This reduces final image size from ~1GB to ~150MB.
Docker Compose for Development
For local development, create a docker-compose.yml:
YAML
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
- /app/.next
environment:
- NODE_ENV=development
Environment Variables
Handle environment variables properly:
DOCKERFILE
# In your Dockerfile
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
ARG DATABASE_URL
ENV DATABASE_URL=$DATABASE_URL
Or use a .env file with docker-compose:
YAML
services:
app:
env_file:
- .env.production
.dockerignore
Create a .dockerignore file to exclude unnecessary files:
TEXT
node_modules
.next
.git
.gitignore
README.md
.env.local
.env*.local
npm-debug.log*
.DS_Store
Health Checks
Add health checks to your Dockerfile:
DOCKERFILE
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js || exit 1
Building and Running
Build the image:
BASH
docker build -t my-nextjs-app .
Run the container:
BASH
docker run -p 3000:3000 my-nextjs-app
Production Considerations
Security
- Use non-root user (already in Dockerfile above)
- Keep base images updated
- Scan images for vulnerabilities
- Use secrets management (Docker secrets, Kubernetes secrets)
Performance
- Use Alpine Linux base images (smaller, faster)
- Enable Next.js standalone mode
- Implement proper caching strategies
- Use CDN for static assets
Monitoring
- Add logging (structured logs)
- Implement health check endpoints
- Monitor container resource usage
- Set up alerting
Common Pitfalls
1. Not using .dockerignore: Leads to large images and slow builds
2. Installing dev dependencies in production: Increases image size
3. Running as root: Security risk
4. Not using multi-stage builds: Unnecessarily large images
5. Hardcoding environment variables: Makes deployment inflexible
Advanced: Docker Compose with Database
For full-stack applications:
YAML
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
depends_on:
- db
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=mydb
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Conclusion
Dockerizing your Next.js application provides consistency, scalability, and easier deployment. The multi-stage build approach keeps images small and secure, while proper configuration ensures optimal performance.
Remember: containerization is just one part of a production-ready deployment. You'll also need proper monitoring, logging, and CI/CD pipelines.
---
*Need help dockerizing your application or setting up production infrastructure? Get in touch to discuss your deployment needs.*
Enjoyed this article?
If you found this helpful, let's discuss how we can help with your next project.
Book a call