Reducing Docker image size of a Node.js application
Working on a Node.js application I noticed that deploying its image sometimes takes more time then I want it to. I started digging into the problem and here are two steps to drop Docker image size down from 948MB to 79MB!
This is the results of my attempts:
The application is a typical web application that has frontend part (React.js) and backend part (Node.js server on Express.js.) The build process consists of these steps:
NPM build ---> Run tests ---> Build Docker image ---> Publish to hub.docker.com
The application Dockerfile before changes (located in the root of application directory):
FROM node:8.10.0 RUN mkdir -p /usr/app/build WORKDIR /usr/app COPY ./build /usr/app/build COPY ./node_modules /usr/app/node_modules COPY ./package.json /usr/app/package.json EXPOSE 3000 CMD [ "npm", "run", "start" ]
This Dockerfile does several things:
/usr/appas application directory
- copies build files to the application directory
- copies required Node.js modules to the application directory.
Step 1: Replace base Node.js image with a smaller one (948MB to 206MB)
Node.js images repository provides several image tags for each Node.js version. For example, version 8.10.0 has 6 different image tags:
8.10.0– 266MB compressed
8.10.0-alpine– 23MB compressed
8.10.0-onbuild– 266MB compressed
8.10.0-slim– 92MB compressed
8.10.0-stretch– 343MB compressed
8.10.0-wheezy– 202MB compressed.
An interesting thing there is the alpine version. This is the smallest of available images because it based on Alpine Linux project. Alpine uses musl libc instead of glibc inside, but Node.js usually uses the latter on a typical developer system. It may break some libraries you use but there were no issues with my Express.js based application. Switching to alpine:
# change the first line from: FROM node:8.10.0 # to: FROM node:8.10.0-alpine
docker build and in my case, the size of the image drops down to 206MB, it’s 78% less than
the initial size!
(Read more about pros/cons of alpine image here.)
Step 2: Use NPM
--production flag (206MB to 79MB)
npm install installs all dependencies including
--production flag that makes it possible to install only the
dependencies section from package.json.
I keep build systems, testing utils, and other dev tools in the
I’m used to keeping my React.js libraries and other UI dependencies under the
section in package.json, but it doesn’t look correct,
because I have webpack to make a bundle of all my UI dependencies.
Hence, the right solution here is to move all dependencies, which are not going to be directly used
on the production server, to the
The rule is: if the dependency is only needed during the build, move it to the
I don’t make a bundle for server files, so I left all server dependencies in
dependencies section as they were before.
That means that the working process should contain following steps:
- build UI bundle
- copy UI bundle to the Docker image
- copy server files to the Docker image
- copy package.json to the Docker image
npm install --productioninside the image.
The final version of the Dockerfile I have:
FROM node:8.10.0-alpine RUN mkdir -p /usr/app/build WORKDIR /usr/app COPY ./build /usr/app/build COPY ./package.json /usr/app/package.json RUN cd /usr/app && npm install --production EXPOSE 3000 CMD [ "npm", "run", "start" ]
docker build again and in my case, the size of the image drops down to 79MB
and this time it’s 91% less than the initial size!
Two simple steps to get image size dropped from 948MB to 79MB. Now container deployment process takes much less time. Compressed image sizes on hub.docker.com look even better:
Thanks for reading. I’d be glad to get any feedback!