logo

How to Reduce Next.js Docker Image Size

If you are using Docker to deploy your Next.js application, decreasing the image size is crucial.

A smaller image size leads to faster push and pull times, resulting in quicker deployments.

In this tutorial, I will show you how to reduce your Docker image size from 1.96 GB to just 150 MB.

The baseline

Let's start with a basic Next.js project created using the following command:

1npx create-next-app@latest

After that, we added a simple Dockerfile to the root of the project:

1FROM node:20
2
3COPY package*.json ./
4
5RUN npm ci
6
7COPY . .
8
9RUN npm run build
10
11EXPOSE 3000
12
13CMD npm start

Upon checking the Docker image size, it shows a whopping 1.96 GB! 😱

Leaving our application like this would severely impact our pipeline's performance.

Let's see how we can reduce the image size.

Step 1: Use a Smaller Base Image

We are currently using node:20 as our base image.

However, it contains unnecessary code that we don't use.

We can switch to a smaller base image, node:20-alpine, which is based on Alpine Linux.

1FROM node:20-alpine
2
3COPY package*.json ./
4
5RUN npm ci
6
7COPY . .
8
9RUN npm run build
10
11EXPOSE 3000
12
13CMD npm start

This single change reduces our image size by nearly half, bringing it down to 999 MB. But we're not done yet.

Step 2: Multi-Stage Builds

We can further reduce the image size by utilizing multi-stage builds. This approach creates a final runner image that contains only the necessary files.

1FROM node:20-alpine AS deps
2
3COPY package*.json ./
4
5RUN npm ci
6
7FROM node:20-alpine AS builder
8
9COPY . .
10COPY --from=deps /node_modules ./node_modules
11RUN npm run build
12
13FROM node:20-alpine AS runner
14
15COPY --from=builder /next.config.mjs ./
16COPY --from=builder /public ./public
17COPY --from=builder /.next ./.next
18COPY --from=builder /node_modules ./node_modules
19
20EXPOSE 3000
21
22CMD ["node_modules/.bin/next", "start"]

With this configuration, we have managed to reduce the image size to 703 MB.

Step 3: Next.js Output Configuration

Now, let's achieve a significant reduction. Our goal is to bring the image size down to just 150 MB.

By adding standalone to the output field in next.config.js, we can generate a standalone build.

next.config.js
1module.exports = {
2 output: 'standalone',
3}

During the build process, Next.js uses @vercel/nft to statically analyze import, require, and fs usage, determining all files that a page might load. To run our application, only this standalone folder is required.

To utilize the standalone build, we need to update our Dockerfile:

1FROM node:20-alpine AS deps
2
3COPY package*.json ./
4
5RUN npm ci
6
7FROM node:20-alpine AS builder
8
9COPY . .
10COPY --from=deps /node_modules ./node_modules
11RUN npm run build
12
13FROM node:20-alpine AS runner
14
15COPY --from=builder /.next/standalone ./
16# COPY --from=builder /public ./public
17# COPY --from=builder /.next/static ./.next/static
18
19EXPOSE 3000
20
21ENV PORT 3000
22
23CMD HOSTNAME="0.0.0.0" node server.js
With this approach, you cannot access the .next/static and ./public folders. For best practices, upload them to a CDN. If that's not possible, comment out the two lines.

Step 4: Add .dockerignore

Don't forget to add a .dockerignore file and include .git in it.

Excluding unnecessary files from Docker context can significantly reduce the time it takes to copy files into the image.

.dockerignore
1.git

Conclusion

By following these steps, we have successfully reduced our Docker image size from 1.96 GB to just 150 MB.

If you have any questions or need further assistance, feel free to email me.