Creating an Optimized Next.js Docker Image
Using Docker with Next.js applications is an excellent way to facilitate team development and simplify deployment — a strategy even suggested by the framework’s official documentation.
To get started, let’s create a basic Docker image for a Next.js application. Since Next.js runs on Node.js, we’ll use a Node.js Docker image as our base. Additionally, we’ll perform these steps to make the application work on Docker:
- Copy the
package.json
file into the Docker environment - Install dependencies
- Copy the project files
- Build the application
- Run the application
Here’s what the resulting Dockerfile might look like:
FROM node:22
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
While functional, this Dockerfile can be significantly optimized to reduce the image size. A large Docker image can lead to slower builds, higher storage costs, and longer deployment times. Below, I’ll walk through some strategies to slim it down.
Strategies to reduce image size
Strategy 1: use a smaller base image
The node:22
image is versatile but also heavy because it’s built on a full Linux distribution. By switching to a smaller variant like node:22-alpine
, we can significantly reduce the base size of the image
FROM node:22-alpine
With this small change, the size of the image has decreased dramatically. From 2.23 GB to 1.27 GB - a reduction of almost 50%!
Strategy 2: Multi-stage builds
Multi-stage builds let us separate the build process from the final runtime environment, ensuring that only the essential files end up in the final image:
FROM node:22-alpine AS base
# Stage 1: Install dependencies
FROM base AS deps
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci
# Stage 2: Build the source code
FROM base AS builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN npm run build
# Stage 3: Run the app
FROM base AS runner
WORKDIR /app
COPY --from=builder /app/next.config.ts ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
EXPOSE 3000
CMD ["npm", "start"]
By doing this, we’re excluding unnecessary files from the final image, such as development dependencies and source files that aren’t required for runtime.
Now our image has 874MB. Much better! But we can still improve a bit more...
Strategy 3: Next.js standalone output
Next.js offers a standalone
output mode, which separates only the necessary files at build time and creates a Node.js server for deployment. Here’s how to configure it:
const nextConfig = {
output: "standalone"
};
export default nextConfig;
Final Dockerfile
The standalone mode creates a /standalone
directory in the .next
output, so we need to copy it into the Docker image:
FROM node:22-alpine as base
# Install dependencies
FROM base as deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
# Build the source code
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Run the application in production mode
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
# server.js is created by next build from the standalone output
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
This optimized Dockerfile results in a final image size of 232MB. Compared to our original 2.23GB image, that’s a staggering reduction of nearly 90%!!! 🚀
It’s worth noting that this version closely mirrors the example provided in the official Next.js repository
Conclusion
Using Docker to run Next.js applications is vital for modern development workflows, and optimizing Docker image sizes can dramatically improve build times, reduce storage costs, and streamline deployments. The strategies outlined here can help you achieve leaner and more efficient Docker images for your Next.js projects.
I hope you found this guide helpful! 🚀