Next.js Dockerfile
The Optimized Dockerfile
Let's examine a production-ready Dockerfile for a Next.js application:
# Stage 1: Dependencies
FROM node:18-alpine AS deps
WORKDIR /app
# Copy package files
COPY package.json package-lock.json ./
# Install dependencies
RUN npm ci
# Stage 2: Builder
FROM node:18-alpine AS builder
WORKDIR /app
# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build the application
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
# Stage 3: Runner
FROM node:18-alpine AS runner
WORKDIR /app
# Set to production environment
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
# Add non-root user for security
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy necessary files from builder
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
# Set correct permissions
RUN chown -R nextjs:nodejs /app
# Switch to non-root user
USER nextjs
# Expose the port your app runs on
EXPOSE 3000
# Start the application
CMD ["node", "server.js"]
Breaking Down the Dockerfile
Stage 1: Dependencies
FROM node:18-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
This initial stage focuses solely on installing dependencies. We use node:18-alpine
as our base image because:
- Alpine Linux is extremely lightweight
- It contains only essential packages
- Results in smaller final image size
We use npm ci
instead of npm install
because:
- It's faster and more reliable for production builds
- Ensures exact versions from package-lock.json are installed
- Removes node_modules before installation to prevent conflicts
Stage 2: Builder
FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
The builder stage is responsible for creating the production build. Key points: - Copies dependencies from the previous stage - Includes all source code - Disables Next.js telemetry for privacy - Runs the build process
Stage 3: Runner
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
The final stage creates the production image. We set environment variables:
- NODE_ENV=production
optimizes Node.js for production
- Continues to disable telemetry
Security Considerations
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN chown -R nextjs:nodejs /app
USER nextjs
These security measures are crucial because: - Running as non-root reduces potential security vulnerabilities - System users have limited permissions - Explicit user/group creation ensures consistent permissions across deployments
Optimization Techniques
- Multi-stage builds: By using three stages, we:
- Keep the final image size small
- Include only necessary production files
-
Separate concerns between building and running
-
Installing dependencies first
- Copying source code later
-
Minimizing cache invalidation
-
File copying strategy: We carefully select what to copy:
- Only necessary files are included
- Static assets are preserved
- Standalone output is used for optimal production serving
Conclusion
A well-crafted Dockerfile is essential for deploying Next.js applications in a production environment. The multi-stage approach ensures security, optimization, and reliability while maintaining a small image size. This configuration provides a solid foundation for deploying Next.js applications in containerized environments, whether you're using Kubernetes, Docker Swarm, or any other container orchestration platform.
Remember to adjust the Dockerfile based on your specific needs, such as adding environment variables, secrets, or additional build steps.