How to Optimize Your Next.js Dockerfile
Jun 27 2024
As I’ve been writing Dockerfiles for my Next.js projects, I’ve noticed that the build size can be quite large. Today, I want to share three strategies to optimize your Dockerfile and reduce the overall image size.
1. Choosing the Right Base Image
The node:18-alpine image is small and efficient, which helps reduce the overall size of your Docker image. Alpine images are known for their minimalistic design, making them much smaller compared to other images. If you want to use a different version, you can replace 18 with 16 or 20.
2. Use multi-stage builds
Introduce
Docker introduced the Multi-Stage Builds feature in version 17.05. Before version 17.05, achieving multi-stage builds would require multiple Dockerfiles. If the build environment was complex, maintaining multiple Dockerfiles might be necessary. However, Multi-Stage Builds can address the issue of needing to maintain multiple Dockerfiles.
How to use it
To utilize multi-stage builds, we need to use multiple FROM statements in a single Dockerfile. Each FROM instruction can use a different base image and starts a new stage of the build.
3. Use Standalone Application in Next.js
Automatically Copying Traced Files
Next.js can automatically create a standalone folder that copies only the necessary files for a production deployment including select files in node_modules. To leverage this automatic copying you can enable it in your next.config.js:
// next.config.js
module.exports = {
output: 'standalone',
}
This will create a folder at .next/standalone which can then be deployed on its own without installing node_modules.
Additionally, a minimal server.js file is also output which can be used instead of next start. This minimal server does not copy the public or .next/static folders by default as these should ideally be handled by a CDN instead, although these folders can be copied to the standalone/public and standalone/.next/static folders manually, after which server.js file will serve these automatically.
Final Dockerfile
# Base Stage
FROM node:18-alpine AS base
# Deps Stage
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Builder Stage
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
COPY .env.staging.sample .env.production
RUN npm run build
# Runner Stage
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
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
CMD HOSTNAME=localhost node server.js
Troubleshooting
If you encounter any issues while building your Docker image, here are some common problems and solutions:
Conclusion
By choosing the right base image, utilizing multi-stage builds, and leveraging Next.js standalone applications, you can significantly reduce the size of your Docker images and improve your deployment process. These strategies help keep your applications lightweight, efficient, and easier to manage.