Skip to main content
Redhat Developers  Logo
  • Products

    Featured

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat OpenShift AI
      Red Hat OpenShift AI
    • Red Hat Enterprise Linux AI
      Linux icon inside of a brain
    • Image mode for Red Hat Enterprise Linux
      RHEL image mode
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • Red Hat Developer Hub
      Developer Hub
    • View All Red Hat Products
    • Linux

      • Red Hat Enterprise Linux
      • Image mode for Red Hat Enterprise Linux
      • Red Hat Universal Base Images (UBI)
    • Java runtimes & frameworks

      • JBoss Enterprise Application Platform
      • Red Hat build of OpenJDK
    • Kubernetes

      • Red Hat OpenShift
      • Microsoft Azure Red Hat OpenShift
      • Red Hat OpenShift Virtualization
      • Red Hat OpenShift Lightspeed
    • Integration & App Connectivity

      • Red Hat Build of Apache Camel
      • Red Hat Service Interconnect
      • Red Hat Connectivity Link
    • AI/ML

      • Red Hat OpenShift AI
      • Red Hat Enterprise Linux AI
    • Automation

      • Red Hat Ansible Automation Platform
      • Red Hat Ansible Lightspeed
    • Developer tools

      • Red Hat Trusted Software Supply Chain
      • Podman Desktop
      • Red Hat OpenShift Dev Spaces
    • Developer Sandbox

      Developer Sandbox
      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Secure Development & Architectures

      • Security
      • Secure coding
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
      • View All Technologies
    • Start exploring in the Developer Sandbox for free

      sandbox graphic
      Try Red Hat's products and technologies without setup or configuration.
    • Try at no cost
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • Java
      Java icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • API Catalog
    • Product Documentation
    • Legacy Documentation
    • Red Hat Learning

      Learning image
      Boost your technical skills to expert-level with the help of interactive lessons offered by various Red Hat Learning programs.
    • Explore Red Hat Learning
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

Build lean Node.js container images with UBI and Podman

May 17, 2023
Evan Shortiss
Related topics:
ContainersLinuxNode.js
Related products:
Red Hat build of Node.jsRed Hat Enterprise Linux

Share:

    Building a container image for your Node.js application might sound like a trivial task, but there are some pitfalls you’ll want to avoid along the path to containerizing your application. It is all too easy to accidentally include sensitive files, run more than one process in the container, run as root, or ship bloated container images. Because all of these mistakes reflect an image constructed without due care, reducing bloat reduces them all.

    This post focuses on the “bloated container images” mistake that’s pretty easy to make when building Node.js application container images. Keep reading to learn about container image layers, how you can slim down a Node.js container image based on Red Hat’s Universal Base Images by over 70% (see Figure 1), and even more best practices for building containers.

    A graph comparing the size of container images for a Node.js application depending on the build-strategy and base image used.
    Figure 1: Image sizes resulting from a various build approaches using Red Hat's Universal Base Images for Node.js.

    Note: All of the examples and code used in this post can be found in this repository on GitHub. You’ll need Node.js 18 and either Docker or Podman installed to follow along. Podman is the container engine used for the examples in this post. Substitute docker in place of podman in commands if you’re using Docker instead of Podman. 

    Building a Node.js application container image

    The Node.js documentation provides a great overview of how to configure a Containerfile (also known as a Dockerfile) for a basic Node.js application. Let’s use that as a template to create a container image for a Node.js application. This application will use the Fastify framework and TypeScript, but it’s worth noting that this guide is applicable to any Node.js web framework.

    To get started, generate a new Fastify project that uses TypeScript using the Fastify CLI:

    npx fastify-cli@5 generate --lang=ts nodejs-ts-basic
    
    # Change into the project directory and generate a package-lock.json
    cd nodejs-ts-basic
    npm i --package-lock-only


    Create a Containerfile in the new nodejs-ts-basic project directory with the following contents:

    FROM registry.access.redhat.com/ubi8/nodejs-18
    
    WORKDIR /usr/src/app
    
    # Copy in package.json and package-lock.json
    COPY --chown=1001:1001 package*.json ./
    
    # Install dependencies and devDependencies
    RUN npm ci
    
    # Copy in source code and other assets
    COPY --chown=1001:1001 . .
    
    # Compile the source TS into JS files
    RUN npm run build:ts
    
    # Configure fastify behaviour, and NODE_ENV
    ENV NODE_ENV=production
    ENV FASTIFY_PORT 8080
    ENV FASTIFY_ADDRESS 0.0.0.0
    
    EXPOSE 8080
    
    # Set the fastify-cli binary as the entrypoint
    ENTRYPOINT [ "./node_modules/.bin/fastify" ]
    
    # Launch the container by passing these parameters to the entrypoint
    # These parameters can be overridden if you’d like
    CMD [ "start", "-l", "info", "dist/app.js" ]


    Next, create a .dockerignore file in the root of the repository. This works similar to a .gitignore, but is respected by tools like Podman and Docker. It’s used to avoid copying the specified files into a container image:

    # Change this as necessary for your own project(s)
    Containerfile*
    README.md
    dist
    node_modules
    test
    *.log
    .dockerignore
    .taprc
    .npmrc
    .env*
    


    With these files in place, build a container image using the Podman (or Docker) CLI:

    podman build . -f Containerfile -t nodejs-ts-basic
    


    The resulting container image is approximately 831 MB in size. That's 188 MB larger than the UBI Node.js v18 base image! Investigate the size of files and folders by running the du command inside the container:

    podman run --rm nodejs-ts-basic /bin/du -h -d 1
    60K    ./dist
    28K    ./src
    190M    ./node_modules
    190M    .
    


    Clearly the node_modules folder is causing bloat in the image, because both the dependencies and the devDependencies specified in the package.json were installed. 

    Attempting to slim down the Node.js container image

    A seemingly obvious solution to this problem is to remove those devDependencies from the image. Try that by adding npm prune --omit=dev after the npm run build:ts command in the Containerfile:

    RUN npm run build:ts
    RUN npm prune --omit=dev
    


    And building it:

    podman build . -f Containerfile -t nodejs-ts-basic-prune
    


    The container image will be more lightweight, right? Let's check:

    podman images --format '{{.Size}} {{.Repository}}' | grep nodejs
    
    831 MB localhost/nodejs-ts-basic
    832 MB localhost/nodejs-ts-basic-prune
    
    


    This new image is larger than the last one! This is because container images are composed of layers. Each layer stores changes compared to the prior layer it's based on. The npm prune command removed the devDependencies from the final container image layer (you can confirm using the du command shown previously), but the layer containing them is still there. Confirm this using the podman history localhost/nodejs-ts-basic-prune command, noting that the npm ci layer is there, and is over 187 MB in size.

    Multi-stage builds to the rescue

    A great solution to this problem is to use a multi-stage build. Multi-stage builds perform some of the build steps in separate containers, and copy only what‘s needed into the final container image. This reduces the number of layers and overall size of the final container image.

    This is an example of a multi-stage Containerfile that can be used to slim down the Node.js container image:

    # First stage of the build is to install dependencies, and build from source
    FROM registry.access.redhat.com/ubi8/nodejs-18 as build
    
    WORKDIR /usr/src/app
    COPY --chown=1001:1001 package*.json ./
    RUN npm ci
    COPY --chown=1001:1001 tsconfig*.json ./
    COPY --chown=1001:1001 src src
    RUN npm run build:ts
    
    # Second stage of the build is to create a lighter container with just enough
    # required to run the application, i.e production deps and compiled js files
    FROM registry.access.redhat.com/ubi8/nodejs-18
    WORKDIR /usr/src/app
    
    COPY --chown=1001:1001 --from=build /usr/src/app/package*.json/ .
    RUN npm ci --omit=dev
    COPY --chown=1001:1001 --from=build /usr/src/app/dist/ dist/
    
    ENV NODE_ENV=production
    ENV FASTIFY_PORT 8080
    ENV FASTIFY_ADDRESS 0.0.0.0
    EXPOSE 8080
    
    ENTRYPOINT [ "./node_modules/.bin/fastify" ]
    CMD [ "start", "-l", "info", "dist/app.js" ]
    


    A summary of the two stages:

    • The first stage (build) installs all dependencies, and compiles the TypeScript code.
    • The second stage copies the compiled code from the build image and installs only the production dependencies to produce a deployable container image.

    The initial build using this multi-stage Containerfile will be slower, but subsequent builds benefit from cached layers and are only a second or two slower than the single stage build. 

    The multi-stage build results in a container image that's just 661 MB. That’s 20% smaller than the single stage image’s 831 MB. This isn’t bad—but you can do better.

    Going minimal

    There’s one last step you can take to really slim this Node.js container image down; and that’s using a minimal base image. A minimal base image contains as few tools and libraries as possible, which means they have a significantly smaller footprint. 

    Modifying the second stage of the multi-stage build to use Red Hat's minimal Node.js v18 Universal Base Image reduces the final container image size to just 211 MB. All you need to do is change the FROM statement to use the minimal image:

    FROM registry.access.redhat.com/ubi8/nodejs-18-minimal
    


    That 211 MB container image is 75% smaller than the first 831 MB container image you built! Not only is the image smaller, but it also has a lower risk profile since it doesn’t contain tools that could be used for malicious purposes in the event of a security breach.

    Summary and next steps

    Use the following tips to improve your container images for any application runtime:

    • Use a trusted base image, such as Red Hat’s Universal Base Image.
    • Don’t run as root. Using a Red Hat Universal Base Image takes care of this by default.
    • Use multi-stage builds to minimize container image layers.
    • Choose a minimal base image for the final stage in a multi-stage build.
    • Use a .dockerignore file to keep unwanted files being copied into your container images.
    • Handle signals such as SIGINT and SIGTERM, or use tini or dumb-init to manage your process(es).

    Take a look at this post by Bethany Griggs when you’re ready to deploy your lean Node.js container images on the Developer Sandbox for Red Hat OpenShift!

    Last updated: August 14, 2023

    Related Posts

    • Optimize Node.js images with the UBI 8 Node.js minimal image

    • Podman expands to the Desktop

    • Podman basics: Resources for beginners and experts

    • Build lean Java containers with the new Red Hat Universal Base Images OpenJDK runtime images

    • Hello Podman using .NET

    • How to transition from Docker to Podman

    Recent Posts

    • How Trilio secures OpenShift virtual machines and containers

    • How to implement observability with Node.js and Llama Stack

    • How to encrypt RHEL images for Azure confidential VMs

    • How to manage RHEL virtual machines with Podman Desktop

    • Speech-to-text with Whisper and Red Hat AI Inference Server

    What’s up next?

    Podman in action e-book share image

    Read Podman in Action for easy-to-follow examples to help you learn Podman quickly, including steps to deploy a complete containerized web service.

    Get the e-book
    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    Build

    • Developer Sandbox
    • Developer Tools
    • Interactive Tutorials
    • API Catalog

    Quicklinks

    • Learning Resources
    • E-books
    • Cheat Sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site Status Dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue