Published on

Dieting NodeJS Docker Containers

Authors

An environment I am working in is locked down. As a result, I use a docker container that is pre-bundled with everything I need to get deployments in that environment done. This particular container uses NodeJS internally to do its thing.

Unfortunately getting this container to the environment involves saving it locally:

# build the container locally
docker build -t anOrg/my-container:0.1 -f ./path/to/Dockerfile

# saving the built container to a folder called build (this is saved as a tar file)
mkdir -p build
docker save -o build/my-container-0.1 anOrg/my-container:0.1

This saves the built container as a tar file. But being a NodeJS app this weights in (in my case) at 1.5GB. This is a problem as I have to copy this container to the target server every time I need to make a change.

As a result of this, I started looking at ways to cut this image size down. The best approaches to take are from the top to the bottom.

Cut Down Your node_modules Folders

node_modules-heaviest-thing-in-the-universe

Source: Reddit - ProgrammerHumour

Anyone who has worked with Node will have a good chuckle on seeing this meme. When working with Node apps, the node_modules folder is generally the heaviest piece in your app's directory.

There are a few ways to lighten this:

  • Remove unneeded dependencies.
  • Move dependencies to dev dependencies
    • These are the dependencies that are only used in tests and during builds

Once you have done these two steps you can tell npm/yarn do build excluding dev dependencies:

# in yarn
yarn --prod

# npm
npm run build --prod

Switch to the Node Alpine Container

This is seemingly easy, simply append -alpine to the end of one of the official node images you are using.

For example node:10.17.0 becomes node:10.17.0-alpine.

The problem is this alpine container does not have the build tools you need to build the node image. You will need to use a dockerfile similar to the below:

FROM node:10.17.0-alpine

WORKDIR /root/build
COPY . .

RUN apk --no-cache add yarn python make g++
RUN yarn --prod && yarn run build
RUN apk --no-cache del yarn python make g++

ENTRYPOINT ["yarn","start"]

This will:

  • install the build tools you need.
  • yarn or npm install.
  • run any build scripts you need.

Use Multistage Builds

In my case, the run script behaved strangely when using multi-stage builds. But this should work in general for other apps. The above dockerfile will change as follows:

FROM node:10.17.0-alpine as builder

WORKDIR /root/build
COPY . .

RUN apk --no-cache add yarn python make g++
RUN yarn --prod && yarn run build

FROM node:10.17.0-alpine as runner

WORKDIR /home/root
COPY --from=builder /root/build /home/root/
ENTRYPOINT ["yarn", "start"]

This final step gives you an even lighter run container that only has the bare minimum needed to run NodeJS applications.