Minimize Docker Container for NodeJS

Minimize Docker Container for NodeJS

What is Node.js?

Node.js is a software platform for scalable server-side and networking applications. Node.js applications are written in JavaScript and can be run within the Node.js runtime on Mac OS X, Windows, and Linux without changes.

Node.js applications are designed to maximize throughput and efficiency, using non-blocking I/O and asynchronous events. Node.js applications run single-threaded, although Node.js uses multiple threads for file and network events. Node.js is commonly used for real-time applications due to its asynchronous nature.

Node.js internally uses the Google V8 JavaScript engine to execute code; a large percentage of the basic modules are written in JavaScript. Node.js contains a built-in, asynchronous I/O library for file, socket, and HTTP communication. The HTTP and socket support allows Node.js to act as a web server without additional software such as Apache.

Creating a Dockerfile

Create an empty file called Dockerfile:

touch Dockerfile

it's a good idea to build docker production image from a stable long term support version of nodejs . that's why i am using nodejs 16 LTS docker image.

Now Write Simple Docker Container

standard size of a node js image 900+ MB without compression and minimization.

# we will take the long term release version of docker 
FROM node:16

# we want to be a production image
ENV NODE_ENV=production 

# this is the directory  we will post the app
WORKDIR /app 

# copy the package.json and lockfile 
COPY ["package.json","package-lock.json","./"]

# perform a clean npm install to install dependency
RUN npm ci --production 


# set the user as node,  so we dont run as root 
USER node 

# set the user as node,so we dont run as root 
COPY src/. .

# start the server 
CMD ["node","server.js"]

Nodejs container with nodejs alpine image

alpine image is comparatively smaller than standard nodejs image.

FROM node:16-alpine3.15
WORKDIR /app 
COPY package.json package-lock.json ./

RUN npm ci
COPY . .
RUN npm run build && npm prune --production 
ENV PORT 5050 
EXPOSE 5050 
CMD ["node","build"]

Multi stage nodejs slim docker container build

docker-slim will optimize and secure your containers by understanding your application and what it needs using various analysis techniques. It will throw away what you don't need, reducing the attack surface of your container. What if you need some of those extra things to debug your container? You can use dedicated debugging side-car containers for that (more details below).

we decided to use slim (~100 MB),

# we will take the long term release version of docker 
FROM node:16-alpine3.15 as builder  

# we want to be a production image
ENV NODE_ENV=production 

# this is the directory  we will post the app
WORKDIR /app 

# copy the package.json and lockfile 
COPY ["package.json","package-lock.json","./"]

# perform an npm install to install dependency
RUN npm ci --production 

# set the user as node,  so we dont run as root 
USER 0

# set the user as node,so we dont run as root 
COPY src/. .

FROM node:16.17-slim

USER node 
COPY --from=builder  /app /app 
WORKDIR /app 

# start the server 
CMD ["node","server.js"]

Distroless node js docker Container

"Distroless" images contain only your application and its runtime dependencies. They do not contain package managers, shells or any other programs you would expect to find in a standard Linux distribution.

Distroless images are very small. The smallest distroless image, gcr.io/distroless/static-debian11, is around 2 MiB. That's about 50% of the size of alpine (~5 MiB), and less than 2% of the size of debian (124 MiB).

# we will take the long term release version of docker 
FROM node:16-alpine3.15 as builder  

# we want to be a production image
ENV NODE_ENV=production 

# this is the directory  we will post the app
WORKDIR /app 

# copy the package.json and lockfile 
COPY ["package.json","package-lock.json","./"]

# perform an npm install to install dependency
RUN npm ci --production 

# set the user as node,  so we dont run as root 
USER 0

# set the user as node,so we dont run as root 
COPY src/. .

FROM gcr.io/distroless/nodejs:14 

USER node 
COPY --from=builder  /app /app 
WORKDIR /app 

# start the server 
CMD ["node","server.js"]

Red Hat Universal Base nodejs docker image

Red Hat Universal Base Images (UBI) are OCI-compliant container base operating system images with complementary runtime languages and packages that are freely redistributable.

Node.js 16 available as container is a base platform for building and running various Node.js 16 applications and frameworks. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.

# we will take the long term release version of docker 
FROM registry.access.redhat.com/ubi8/nodejs-14 as builder  

# we want to be a production image
ENV NODE_ENV=production 

# this is the directory  we will post the app
WORKDIR /app 

# copy the package.json and lockfile 
COPY ["package.json","package-lock.json","./"]

# perform an npm install to install dependency
RUN npm ci --production 

# set the user as node,  so we dont run as root 
USER 100

# set the user as node,so we dont run as root 
COPY src/. .

FROM quay.io/chrishayuk/nodejs-base:14.15.4-ubi-minimal

USER node 
COPY --from=builder  /app /app 
WORKDIR /app 

# start the server 
CMD ["node","server.js"]

Distroless nodejs docker image

"Distroless" images contain only your application and its runtime dependencies. They do not contain package managers, shells or any other programs you would expect to find in a standard Linux distribution.

Why should I use distroless images?

Restricting what's in your runtime container to precisely what's necessary for your app is a best practice employed by Google and other tech giants that have used containers in production for many years. It improves the signal to noise of scanners (e.g. CVE) and reduces the burden of establishing provenance to just what you need.

Distroless images are very small. The smallest distroless image, gcr.io/distroless/static-debian11, is around 2 MiB. That's about 50% of the size of alpine (~5 MiB), and less than 2% of the size of debian (124 MiB).

FROM node:16-alpine3.15 AS build-env
COPY . /app
WORKDIR /app

RUN npm ci --omit=dev

FROM gcr.io/distroless/nodejs:18
COPY --from=build-env /app /app
WORKDIR /app
CMD ["index.js"]

For node js docker image

  1. alpine image

  2. NodeJs offical image

  3. debian image

  4. debian slim image

its recomended to use the same type of distro for multi stage build , ie when building with nodejs offical image use debian/debian slim ,

but for smallest image size use alpine image

You can find more information about Docker and Node.js on Docker in the following places: