Run with NODE_ENV set to production. This is the way you would pass in secrets and other runtime configurations to your application as well.
If you need to install global npm dependencies, it is recommended to place those dependencies in the non-root user directory. To achieve this, add the following line to your Dockerfile
Node.js was not designed to run as PID 1 which leads to unexpected behaviour when running inside of Docker. For example, a Node.js process running as PID 1 will not respond to SIGINT (CTRL-C) and similar signals. As of Docker 1.13, you can use the --init flag to wrap your Node.js process with a lightweight init system that properly handles running as PID 1.
You can also include Tini directly in your Dockerfile, ensuring your process is always started with an init wrapper.
By default, Docker runs commands inside the container as root which violates the Principle of Least Privilege (PoLP) when superuser permissions are not strictly required. You want to run the container as an unprivileged user whenever possible. The node images provide the node user with uid 1000 for such purpose. The Docker Image can then be run with the node user in the following way:
Alternatively, the user can be activated in the Dockerfile:
Note that the node user is neither a build-time nor a run-time dependency and it can be removed or altered, as long as the functionality of the application you want to add to the container does not depend on it.
If you do not want nor need the user created in this image, you can remove it with the following:
If you need to change the uid/gid of the user, you can use:
If you need another name for the user (ex. myapp), execute:
For alpine based images, you do not have groupmod nor usermod, so to change the uid/gid you have to delete the previous user:
By default, any Docker Container may consume as much of the hardware such as CPU and RAM. If you are running multiple containers on the same host, you should limit how much memory they can consume.
When creating an image, you can bypass the package.json's start command and bake it directly into the image itself. First off, this reduces the number of processes running inside of your container. Secondly, it causes exit signals such as SIGTERM and SIGINT to be received by the Node.js process instead of npm swallowing them.
Here is an example of how you would run a default Node.JS Docker Containerized application:
The Docker team has provided a tool to analyze your running containers for potential security issues. You can download and run this tool from here: https://github.com/docker/docker-bench-security
Here is an example of how you would install dependencies for packages that require node-gyp support on the alpine variant:
And, here's a multistage build example:
To remove npm and Yarn package managers, use a multi-stage build. In the first stage, the package manager builds the app. In the second stage, the app and the node directories are copied, without copying the npm & Yarn package managers. In Docker images based on Node.js >=26, Yarn is already removed.
The result is a smaller and hardened final image with no package managers.
The examples below build with npm.
Alpine example
Debian example