DEV Community

Cover image for Avoid Using “bloated” Node.js Docker Image in Production!
Puru
Puru

Posted on • Edited on

Avoid Using “bloated” Node.js Docker Image in Production!

🏃‍➡️ TL;DR

  • ❌ Avoid "bloated" image in production: node:22, node:latest, node:lts, node:current
  • ✅ Use "slim" variant image in production, i.e., node:lts-slim, node:22-slim
  • ❌ Avoid "non-LTS" and odd-numbered releases, i.e., node:slim, node:current-slim, node:bookworm-slim, node:23, node:21
  • ✅ Use even-numbered "LTS" release, i.e., node:20-slim, node:22-slim, node-lts-slim

🤦🏻‍♂️ Bad choice

As per Docker Hub, Node.js image gets 9 million weekly image pulls with 1+ Billion image already pulled and counting.

Unfortunately, use of buildpack-deps based version of NodeJS image by default leads to unnecessarily bloated image, full of dev packages, compilers and riddled with CVEs. Avoid using "bloated" image at all cost in production, but why?

  • Slower deployment times: "bloated" images are significantly larger, slows down deployment times.
  • Disk spaces: "bloated" image due to it's size, consumes more disk space when uncompressed.
  • Security risks: With more packages included, higher risk to new security vulnerabilities and supply chain attacks.
  • Slow startup-times: A larger image can slow down startup times.

Ivan from iximiuz Labs did a post mortem analysis of node:22 image and highlights the buildpack-deps:stable and buildpack-deps:scm layer image on-top of Debian base image (bookworm) is where all the "bloat" comes from, including a full Python installation and the GNU Compiler Collection (GCC), which contributes to larger image size.

Source: iximiuz Labs

# 🤯 >1GB in image size 
$ docker images node

REPOSITORY   TAG              IMAGE ID       CREATED       SIZE
node         22               c9d4a6dda881   4 days ago    1.12GB
node         bookworm         8c96be300ba8   4 days ago    1.12GB
node         current          8c96be300ba8   4 days ago    1.12GB
node         latest           8c96be300ba8   4 days ago    1.12GB
node         lts              85f76d7c2b89   2 weeks ago   1.1GB

# 🪲 Riddle with vulnerable libraries
$ trivy image -q node:22

node:22 (debian 12.7)
=====================
Total: 997 (UNKNOWN: 4, LOW: 492, MEDIUM: 419, HIGH: 76, CRITICAL: 6)

# 🤨 Do you need GCC compiler?
$ docker run --rm -it node:22 gcc --version

gcc (Debian 12.2.0-14) 12.2.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

# or 🐍 full Python installation?
$ docker run --rm -it node:22 python3 --version

Python 3.11.2
Enter fullscreen mode Exit fullscreen mode

😌 Be calm and use "slim" variant

Source: iximiuz Labs

As you can see from above image, "bloated" image is perfect as a builder image during build stage and "slim" variant as a runtime image for production use.

"Slim" variant is 80% thinner with lot less build/dev packages, that means faster deploys, less disk space, more secure, faster start up times.

For most Node.js projects running "slim" variant in production is a safer and optimal choice due to it's balanced of size and installed packages.

# Use "slim" variant for production
FROM node:lts-slim
FROM node:22-slim
Enter fullscreen mode Exit fullscreen mode
# 👌🏻 Thinner and smaller image size
$ docker images node
REPOSITORY   TAG              IMAGE ID       CREATED       SIZE
node         22-slim          ddf2ab152dc9   4 days ago    240MB
node         22-alpine        e906dc0e8219   4 days ago    153MB
node         lts-slim         1658b30e8115   2 weeks ago   220MB
Enter fullscreen mode Exit fullscreen mode

⚠️ Be careful with "Alpine" variant

Even though "alpine" variant is 30% thiner than "slim" variant, avoid using it for mission-critical NodeJS applications.

  • Alpine is considered experimental and not officially supported target platform for Node.JS.
  • "Slim" variant uses Debian, while "Alpine" variant uses Alpine Linux.
  • Alpine uses "Musl" C library instead of widespread GNU C Library (glibc) which leads to compatibility issue and unexpected behaviors or bugs.
# Avoid "alpine" variant for mission-critical applications
FROM node:lts-alpine
FROM node:current-alpine
FROM node:22-alpine
Enter fullscreen mode Exit fullscreen mode

🤔 But, when should I use "bloated" image?

A "bloated", "full", "debug" image variant includes a full set of development tools and libraries, making it an ideal choice during development but not for production.

After all, just like a chef who brings every spice to the kitchen, it's great for cooking up ideas but not so much for serving dinner! - AI

A "bloated" image serve as a builder image for the multi-stage builds, and slim image for the final runtime stage, resulting in an optimized, small, secure image that is ready for production use.

# Build stage
FROM node:22-lts as builder
WORKDIR /app
COPY . .
RUN npm install && npm run build

# Runtime stage
FROM node:22-slim
ENV NODE_ENV=production
WORKDIR /app
COPY --from=builder /app/dist ./dist
USER node
CMD ["node", "dist/index.js"]
Enter fullscreen mode Exit fullscreen mode

🙃 Wait, what about Distroless variant?

Well…distroless variant is ~30% thinner compared to "slim" variant, and is more secure with no shell, no package manager. But…

  • "Distroless" variant is not officially supported by Node.js team.
  • "Distroless" variant do not maintain latest LTS version, unless you pay.

Google provides "distroless" variant but you will get outdated Node.js version that is currently in maintenance mode.

# You get maintenance mode version
$ docker run -it --rm gcr.io/distroless/nodejs --version 
v18.15.0

# Slightly smaller than "slim" variant
$ docker images gcr.io/distroless/nodejs
REPOSITORY                 TAG       IMAGE ID       CREATED         SIZE
gcr.io/distroless/nodejs   latest    5fafa8030b0b   19 months ago   161MB
Enter fullscreen mode Exit fullscreen mode

Chainguard also offers distroless variant but only "latest" tag is free to use which uses non-LTS version, and other tags node:22 is only available for paid users.

# Free to use
$ docker run -it --rm cgr.dev/chainguard/node:latest --version
v23.0.0
Enter fullscreen mode Exit fullscreen mode

If you need to run your mission-critical Node.js applications in highly regulated, secure environment then Chainguard is your best option.


👋 Looking for more?

Feel free to follow me on Twitter or LinkedIn for more insightful contents.

P.S. Don't forget to checkout this insightful tutorial by Ivan and get hands-on on iximiuz Labs.

Top comments (0)