Blogging fun by Sergej Jevsejev

Minimal Docker Nodejs image with support for private repositories

· by Sergej Jevsejev · Read in about 5 min · (866 Words)

Problem or why build just another Node image?

As you know the Docker Hub is a wild space. Lots of custom images, hard to choose. Each time you need to compare then, and then you end up building your own “better” version of it. That what happened with node container.

Problems I was solving:

  • Accessing Private repository
  • Lower size of the image
  • Support node-sass

Most important it needs to be fully functional. I tried many non-official containers, it failed because of one or another dependencies

Alpine as base image is not an option

When you hear “minimal Docker image” it is probably about fancy stuff like Alpine Linux. Yes, alpine even has node-js as a package! So I thought yay! It is going to be easy…

Did build the image, launching first project… and it fails - node-sass fails. If you are from frontend world, you probably know about dependency mess in npm world. NPM packages install binaries which require packages, it sometimes even compiles weird stuff from C++ sources. And the pity is that these binaries do NOT support Alpine. There are some images something like alpine-node-sass. Don’t trust them. Doing each npm install it is going to compile sass from the scratch. It seems Alpine Linux will not work only because of compatibility issues.

So what to do? Go with some bloated ubuntu or debian images?

Solution for Smaller Node image

There is a great 38 MB Debian around Docker Hub from Google. It should support all possible binaries and the size really great. Right?

So I did through it as base, and several packages for node-sass

FROM google/debian:wheezy
ARG NODE_MAJOR_VERSION=6

RUN apt-get update && \
    apt-get install -y git ssh-client curl build-essential \
    ...

    # Installing node
    curl -sL https://deb.nodesource.com/setup_${NODE_MAJOR_VERSION}.x | bash - && \
    apt-get install -y nodejs && \
    ...

SSH access

After playing around some time, I needed to setup private NPM package from Git. Node was failing to do it. I tried to do simple thing like forward keys, it didn’t work

-v $HOME/.ssh:/home/1000/.ssh:ro

Probably you know, that in order to use SSH key it must:

  • belong to the same system user
  • have permissions 600

Why I mention that? because when use Docker volumes it uses some user 1000:

docker run -it --rm -v $HOME/.ssh:/root/.ssh sjevs/node ls -l /root/.ssh
...
-rw------- 1 1000 staff  1679 Jan 16  2015 id_rsa
-rw-r--r-- 1 1000 staff   407 Jan 16  2015 id_rsa.pub

Interesting right? By default user inside the container is root. So lets build with user 1000!

# adding user which is used for volumes
adduser 1000 --force-badname
...
USER 1000

And it does the job. Here force is needed because OS does not like numbered names :)

Shared .npm

Quick win load the packages from local cache, just share the local directory:

-v $HOME/.npm:/home/1000/.npm

Other encountered issues

Some dependencies did fail because for some reason google/debian:wheezy does not have default locales set. I solved it by adding:

...
echo "export LC_ALL=en_US.UTF-8" >> /home/1000/.bashrc && \
echo "export LANG=en_US.UTF-8" >> /home/1000/.bashrc && \
echo "export LANGUAGE=en_US.UTF-8" >> /home/1000/.bashrc && \
...

Tests

Very tiny amount of Docker images is covered by tests. When I pull it is always a lottery, will it work or not.

So how to write tests? I will write separate blog post about it. Meanwhile the test for this package looks like this:

...
docker run -v $PWD:/src $dockerImage npm install || (echo "Npm install failed for nodes-sass package" && exit 1)
[ -d node_modules ] || (echo "test node-sass package installation was not successful" && exit 1)
[ -e node_modules/node-sass/bin/node-sass ] || (echo "node-sass package installation binary does not exist" && exit 1)
...

It does npm install for the beast package node-sass. The idea is if this one will success, other packages also will :))) Afterwards it just checks if directory node_modules exists, and binary node_modules/node-sass/bin/node-sass also. This way when building new image we can be sure, that it will work.

Cleanup

At the end of building Docker image lets cleanup:

  • Remove all the non-needed packages

    apt-get purge -y curl adduser
    
  • Cleanup any installation files which are not needed any more.

    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
    

Result

Fully functional image with functionality:

  • Non-root user execution #bestPractises
  • Supports private Git repositories
  • It has Docker image tests
  • Smaller size - 136 MB. Official node - 190 MB
  • Works with node-sass without any problems.
  • Integrated with CircleCI to build sjevs/node, sjevs/node:4, sjevs/node:5, sjevs/node:6

At the end it looks like:

FROM google/debian:wheezy
ARG NODE_MAJOR_VERSION=6

RUN apt-get update && \
    apt-get install -y git ssh-client curl adduser build-essential && \

    # adding user which is used for volumes
    adduser 1000 --force-badname && \

    # Installing node
    curl -sL https://deb.nodesource.com/setup_${NODE_MAJOR_VERSION}.x | bash - && \
    apt-get install -y nodejs && \

    apt-get purge -y curl adduser && \

    echo "export LC_ALL=en_US.UTF-8" >> /home/1000/.bashrc && \
    echo "export LANG=en_US.UTF-8" >> /home/1000/.bashrc && \
    echo "export LANGUAGE=en_US.UTF-8" >> /home/1000/.bashrc && \

    # when removing adduser it removes ssh-client o_O
    apt-get install -y ssh-client && \

    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

WORKDIR /src

USER 1000

Github repository - https://github.com/sjevs/docker-node

Thanks for reading till the end. Happy coding!