How did I reduce the docker image size by ~40% for a nextjs app?
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.
- https://github.com/tj/node-prune -to prune dependencies
- 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 curlRUN 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 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 curlRUN 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: 2FROM 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.