Minimising Risk: Node.js Dockerfile with Amazon Linux
At work, getting production releases green-lit requires keeping systems clear of software vulnerabilities. This applies to both programming language packages and operating system components, even for internal-only systems.
Like many modern systems, the current project is Kubernetes-based and deployed to a cloud service. The enterprise vulnerability scanner running across the private container image repositories detects more vulnerabilities than what is reported on https://hub.docker.com. An image appearing clean on Docker Hub might still fail to receive production approval.
Initially, we addressed OS-level vulnerabilities by switching to whichever images came up clean on the scanner. We tried official Ubuntu, official Node.js, OpenJDK images… but this proved time-consuming and problematic. For instance, when OpenJDK images were retired, we needed to find alternatives.
After trying various options, we found Amazon Linux images consistently had the fewest OS vulnerabilities. For Java applications, Amazon provides images with Corretto, their supported binary distribution of OpenJDK, requiring no additional Java installation.
However, Amazon Linux does not ship an image for Node.js. This means we need to install Node.js before setting up our JavaScript services. Here’s an example Dockerfile that demonstrates our approach, typically placed in the root of the Node.js application repository:
Let us examine parts of this Dockerfile:
This base image pulls from Docker Hub (docker.io
). To pull directly from Amazon Linux,
you would use public.ecr.aws/amazonlinux/amazonlinux:2023
. For a private container registry
(what we actually use), the syntax would look like FROM 123456789012.dkr.ecr.us-east-1.amazonaws.com/amazonlinux:2023
.
While you can pin versions (e.g., amazonlinux:2023.6.20250107.0
), this creates the additional task of
manually updating for security fixes.
This setting configures the maximum memory size for V8’s largest and most configurable memory heap section. It is used during TypeScript compilation. For more details, see this discussion https://stackoverflow.com/q/48387040. Remember to align this value with your container’s memory limits in Kubernetes.
This step installs and trusts custom SSL/TLS certificates, a common requirement when containers need
to trust internal or corporate certificate authorities. If Node.js has trouble connecting to internal
services, you might need to set the NODE_EXTRA_CA_CERTS
environment variable to these certificates
(in PEM format).
Some corporate proxy environments require disabling unsafe legacy SSL/TLS renegotiation for Dandified YUM (dnf) to function.
This command applies any security updates released since the last image publication. Amazon are pretty good at releasing new images, but we have had occasions where CVE vulnerability’s are identified where a security fix is available but a new image has not yet been published.
Following typical security best practices, this creates a low-privilege user for running the Node.js application. This prevents an attacker from gaining root access if the application is compromised.
This two-step process first adds the NodeSource repository to the system’s package manager, then installs Node.js via dnf.
Continuing with the principle of least privilege, this changes ownership of the application directory from root to our node user.
This commented section shows how to configure access to privately hosted npm packages if required.
For production installations, npm ci
provides advantages over npm install by ensuring consistent,
reproducible builds https://docs.npmjs.com/cli/v11/commands/npm-ci.
This is marked as optional because in Kubernetes environments, the application start command often resides in the deployment manifest instead:
Changes can be made to the Dockerfile to improve its image layers for caching. However, the main advantage of this approach is having fewer operating system vulnerabilities to manage. Building Node.js applications on Amazon Linux requires more initial setup compared to using official Node.js images, but it significantly reduces the ongoing effort of handling security compliance. For teams working in environments with strict security requirements, this trade-off has proven worthwhile in practice.