How did I reduce the docker image size by ~40% for a nextjs app?

ashvin kumbhani
2 min readMar 20, 2021

--

Photo by Dominik Lückmann on Unsplash

I have my nextjs application running on node:14-alpine. I am using cloud-run fully managed service to serve this app. It basically scales from zero. That means your app will start from scratch. So, there will be a cold start. And according to cloud run documentation, to reduce cold start timing, the Image size should be minimal. Although I was using an alpine-based image, still it was a big image considering the cold start issue. So, I started to research to minimize it below 100MB.

Here’s my initial docker file was look like.

FROM node:14.0.0-alpineRUN mkdir /my-appWORKDIR /my-appADD package.json .ADD package-lock.json .RUN npm install --productionADD . /my-appCMD ["npm", "run", "deploy"]

So, after reading several resources I experimented few things and implemented them below that helped me to reduce image size drastically.

  1. https://github.com/tj/node-prune -to prune dependencies
  2. Implement multistage docker image.

I decided to have 2 stages for my docker file, first to install and prune dependencies and second to serve my app.

STAGE:1

FROM node:14-alpine as buildCOPY package.json /app-stage-1/WORKDIR /app-stage-1RUN npm install --productionCOPY . /app-stage-1RUN npm run build# We need this to download node-prune. size ~1.5 MB
RUN apk --no-cache add curl
RUN curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | /bin/sh -s -- -b /usr/local/bin# run node prune to prune dependencies. It Helped me to save ~38 MB.
RUN /usr/local/bin/node-prune

STAGE:2

To copy build from stage:1 and serve it.

FROM node:14-alpineWORKDIR /usr/appCOPY --from=build /app-stage-1/node_modules /usr/app/node_modulesCOPY --from=build /app-stage-1/package.json /usr/app/package.jsonCOPY --from=build /app-stage-1/.next /usr/app/.nextCOPY --from=build /app-stage-1/server.js /usr/app/server.jsENV NODE_ENV productionCMD ["npm", "start"]

That’s it. So, here’s how your entire Dockerfile will look like.

# STAGE: 1
FROM node:14-alpine as build
COPY package.json /app-stage-1/WORKDIR /app-stage-1RUN npm install --productionCOPY . /app-stage-1RUN npm run build# We need this to download node-prune. size ~1.5 MB
RUN apk --no-cache add curl
RUN curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | /bin/sh -s -- -b /usr/local/bin# run node prune to prune dependencies. It Helped me to save ~38 MB.
RUN /usr/local/bin/node-prune

# STAGE: 2
FROM node:14-alpineWORKDIR /usr/appCOPY --from=build /app-stage-1/node_modules /usr/app/node_modulesCOPY --from=build /app-stage-1/package.json /usr/app/package.jsonCOPY --from=build /app-stage-1/.next /usr/app/.nextCOPY --from=build /app-stage-1/server.js /usr/app/server.jsENV NODE_ENV productionCMD ["npm", "start"]

You can add stages based on your need and copy content from one stage to another.

Also, I am assuming you already have .dockerignore , .npmignore , etc.

--

--