Node.js applications are usually deployed with PM2 or Docker. If you want to deploy with Docker, you may also encounter the problem of how to choose the node image.

  1. Node :

    : This is the official default image, built on Debian, which can be specified as:

    • Debian 10 (Buster) — Current Stable
    • Debian 9 (stretch) — oldstable
    • Debian 8 (Jessie) — Older Stable
    • Debian 7 (Wheezy) — The stable version that was eliminated

    These images are built based on buildpack-deps (see Dockerfile). The advantage of these images is that they have a lot of dependencies to install, such as curl and wget, but the disadvantages are that they are too large.

  2. Node :

    – Slim: This is a compact version image with redundant dependencies removed. Also built on Debian, it is much smaller than the default image, removes many common software packages, and retains only the basic Node environment.

  3. Node :

    -alpine: This version is built on the Alpine image, which is smaller than the Debian Slim version and arguably the smallest Node image. It’s small, but it does a lot of work. Regular Node.js applications will run, but if alpine uses a C ++ extension, it’s not worth using. Alpine uses musl instead of glibc, and some C/C ++ environments may not be compatible.

Next, based on Node.js version 14, download the above three images for comparison:

docker pull node:14-buster
docker pull node:14-buster-slim
docker pull node:14-alpine
Copy the code

Mirror volume size comparison

Run the docker images | grep node:

node           14-buster           70c62b76e4cc        5 hours ago         912MB
node           14-buster-slim      9917d232c3dd        5 hours ago         181MB
node           14-alpine           9db54a688554        5 hours ago         117MB
Copy the code

As you can see, the default mirror image of Node: 14-Buster is 912MB, which is quite large. Node: 14-Buster Slim is much smaller, while Node: 14-Alpine is much lighter.

Container memory usage comparison

Start containers with the images above:

docker run -d --name node-14-buster node:14-buster node -e "require('http').createServer((req, res) => res.end('Hello World')).listen(3030)"

docker run -d --name node-14-buster-slim node:14-buster-slim node -e "require('http').createServer((req, res) => res.end('Hello World')).listen(3030)"

docker run -d --name node-14-alpine node:14-alpine node -e "require('http').createServer((req, res) => res.end('Hello World')).listen(3030)"
Copy the code

Run Docker Stats to see the runtime memory footprint

NAME CPU % MEM USAGE/LIMIT MEM % NET I/O BLOCK I/O PIDS node-14-alpine 0.00% 4.809MiB/1.796GiB 0.26% 0B/0B 0B/0B 7 Node-14-buster 0.00% 4.238MiB/1.796GiB 0.23% 0B/0B 0B/0B 7 Node-14-Buster 0.00% 4.207MiB/1.796GiB 0.23% 0B/0B 4.88 MB / 0 b 7Copy the code

The difference isn’t huge — the Alpine takes up slightly more memory, but both are within acceptable limits.

How to choose?

From the perspective of node.js applications, how should you select an image? The biggest difference between alpine and buster/ bust-slim is the C++ plugin. For example, if you use sharp to process images in your package, alpine mirror won’t work because it’s incompatible. In other cases, if your application and dependencies are written purely in node.js and do not involve C++ plug-ins, alpine mirroring is recommended.

The buster/ bust-slim image can run all node.js projects, including those C++ dependencies, but there is a pitfall that projects started with NPM start cannot listen to docker SIGTERM signals. If the process does not have the corresponding SIGTERM event, docker waits 10 seconds by default and then kills the application forcibly. The test procedure is as follows:

  1. First create a code folder in the root directory and say index.js:

    console.log('pid', process.pid)
    process.on('exit'.(code) = > {
      console.log('Process exit event code:', code)
    })
    process.on('SIGTERM'.(code) = > {
      console.log('SIGTERM', code)
      process.exit(0)})require('http').createServer((req, res) = > res.end('Hello World')).listen(3030)
    Copy the code
  2. Then create package.json:

    {
      "scripts": {
        "start": "node index.js"}}Copy the code
  3. Start container:

    docker run -d --name node-14-buster -v /code:/code node:14-buster sh -c 'cd code && npm start'
    docker run -d --name node-14-buster-slim -v /code:/code node:14-buster sh -c 'cd code && npm start'
    docker run -d --name node-14-alpine -v /code:/code node:14-alpine sh -c 'cd code && npm start'
    Copy the code
  4. Restart the container

    When rebooting buster/ Bust-Slim mirror, it was found that the speed was very slow, reaching the timeout time of 10s, because there was no response to the SIGTERM signal transmitted by docker, which can be tested by the following code:

    docker logs -f node-14-buster
    Then open a new command line
    docker kill -s SIGTERM node-14-buster
    docker restart node-14-buster
    Copy the code

    Neither buster nor Bust-Slim can pick up SIGTERM signals, while Alpine can.

Custom mirror

As it happens, our current project uses both the C++ plugin and the SIGTERM event, so none of the three official images are available. However, it is easy to customize a node.js image for other operating systems, such as Centos7 to create a Dockerfile:

FROM centos:7
RUN curl -L https://dl.yarnpkg.com/rpm/yarn.repo -o /etc/yum.repos.d/yarn.repo
RUN curl --silent --location https://rpm.nodesource.com/setup_14.x | bash -
RUN yum install -y nodejs yarn
WORKDIR /code
EXPOSE 80
CMD npm start
Copy the code

Then build the Node.js image:

docker build -t node:14-centos7 .
Copy the code