10 best practices to containerize Node.js web applications with Docker
Use officially supported and deterministic
image tags

01

CMD “npm” “start
CMD [“yarn”, “start”
CMD “node” “server.js
CMD “start-app.sh”

FROM node:16.17.0-bullseye-slim

02

Avoid pulling devDependencies and non-deterministic package
install like the ones below

Gracefully tear down Node.js apps

Avoid RUN npm ci

RUN npm ci --only=production

03

Some Node.js libraries and frameworks will only enable productionrelated optimization if they detect that the NODE_ENV env var set
to production

Docker defaults to running the process in the container as the rootuser, which is a precarious security practice. Use a low privilegeduser and proper filesystem permissions
USER nod
COPY --chown=node:node . /usr/src/app

sensitive files, such as .npmrc, .env or others, aren’t leaked
into the container image

06

04

Docker base images may include security vulnerabilities
in the software toolchain they bundle, including the Node.js
runtime itself. Scan and fix security vulnerabilities with thefree Snyk Container tool which also provides baseimage recommendations
npm install -g snysnyk aut
snyk container test node:16.17.0-bullseye- slim --file=Dockerfile

a small Docker base image without redundant andunnecessary files.

Mount secrets into the Docker image

10Secrets are a tricky thing to manage. Avoid the following
security pitfalls

async function closeGracefully(signal) {await fastify.close()

process.kill(process.pid, signal);}

process.on('SIGINT', closeGracefully)

Find and fix security vulnerabilities in your Node.js Docker image

ENV NODE_ENV productionDon’t run Node.js apps as root

Iocal artifacts of node_modules/ aren’t copied into thecontainer image

Avoid an abrupt termination of a running Node.js applicationthat halts live connections. Instead, use a process signal
event handler:

Instead, ensure you are installing only production dependencies
in a reproducible way

09

Use .dockerignore to ensure

CMD [“dumb-init”, “node”, “server.js”]

Avoid RUN yarn instal

08

Use .dockerignore

Instead, use a lightweight init system, such as dumb-init,
to properly spawn the Node.js runtime process withsignals support

Avoid RUN npm instal

Use multi-stage builds

Avoid having one big build stage when attempting to clean up
sensitive data from it or dangling dependencies. Instead, use
multi-stage Docker image builds and separate concerns between
the build flow and the creation of a production base image.

Docker creates processes as PID 1, and they must inherently
handle process signals to function properly. This is why you
should avoid any of these variations

Avoid Alpine which isn't officially supported. Avoid other image tags
which have a high software footprint. Prefer a slimmer, up-to-dateand LTS version

Optimize Node.js apps for production

05

terminate a Node.js application

Avoid FROM nod
Avoid FROM node:lt
Avoid FROM node:14-alpine

Install only production dependencies

Properly handle events to safely

passing secrets via build arguments in non multi-stage build
putting secrets inside the Dockerfile

Instead, use the built-in secrets mounting. To mount a .npmrc filefor package install

07

In the Dockerfile: RUN--mount=type=secret,id=npmrc,
target=/usr/src/app/.npmrc npm ci--only=productio
Then build the image with: docker build .
--build-arg NPM_TOKEN=1234 --secret
id=npmrc,src=.npmrc

Authors@liran_tal@goldbergyoni