This repository showcases several techniques for creating smaller Node.js Docker images:
-
.dockerignore file
-
multi-stage build
-
smaller base image
-
image hardening
-
cleaning up
node_modules
-
JavaScript bundling
The example is an Express-based REST API packaged in a Docker image with a HEALTHCHECK.
TLDR
1950.0 MB ⇒ 93.4 MB (-95.21%)
The final result with all techniques applied can be found in the main
branch:
The other branches showcase a single technique—the last commit of the branch contains the changes for the specific technique.
|
All branches besides |
# | Branch | Layer Count | Image Size (MB) | node_modules Size (MB) | server.mjs Size (B) |
---|---|---|---|---|---|
0 |
|
|
|
|
|
1 |
|
|
|
|
|
2 |
|
|
|
|
|
3 |
|
|
|
|
|
4 |
|
|
|
|
|
5 |
|
|
|
|
|
6 |
|
|
|
|
|
7 |
|
|
|
|
|
8 |
|
|
|
|
|
9 |
|
|
|
|
|
9 |
|
|
|
|
|
9 |
|
|
|
|
-
baseline with the default variant of the offical NodeJS Docker image for building the image and serving the example REST API with no optimizations
-
use a .dockerignore file and specific
COPY
commandsℹ️The ls_extensions and ls_extensions_git functions might help fine-tuning your
.dockerignore
file. -
use the
alpine
variant of the offical NodeJS Docker image for both building and running -
use
npm ci --omit dev --omit optional --omit peer
instead ofnpm i
ℹ️Depending on your project setup you might not be able to omit the peer dependencies.
-
use the clean-modules npm package to clean up the
node_modules
directoryℹ️clean-modules
will have more impact once there are more dependencies. -
use a multi-stage build
-
use the official Alpine Docker image for serving the example REST API
ℹ️Depending on your project setup you might have to install more packages via
apk add --no-cache
.The dependencies of
docker run --rm alpine apk add nodejs
might be a starting point. -
use UPX to compress the
node
binary -
harden the final
alpine
imageℹ️Hardening does not only decrease the image size but also makes it significantly more secure.
-
bundle the example REST API
ℹ️Bundling will have more impact once there are more source files to bundle.
-
Build the Docker image:
$ npm run docker:build
-
Start the image (HTTP server):
$ npm run docker:start
-
Stop the image:
$ npm run docker:stop
-
Create a self-signed certificate:
$ npm run cert:create
-
Start the image (HTTPS server):
$ npm run docker:start:secure
There are also other build tasks available.
The example exposes two endpoints (OpenAPI 3 Description):
/
-
returns a randomly generated user in JSON format
/-/health/liveness
-
liveness probe
You can use several API Tools to interact with the API.
Apache License, Version 2.0 (LICENSE or www.apache.org/licenses/LICENSE-2.0).
We abide by the Contributor Covenant, Version 2.1 and ask that you do as well.
For more information, please see CODE_OF_CONDUCT.adoc.
Install Docker.
ℹ️
|
This repository uses husky for Git hooks. More information: Husky - Command not found |
if command -v fnm > /dev/null 2>&1; then
eval "$(fnm env --use-on-cd)"
fi
#!/usr/bin/env sh
# vim:ft=zsh
# shellcheck shell=sh disable=SC1091
set -eu
[ -e /etc/zshenv ] && . /etc/zshenv
[ -e "${ZDOTDIR:=${HOME}}/.zshenv" ] && . "${ZDOTDIR:=${HOME}}/.zshenv"
[ -e /etc/zprofile ] && . /etc/zprofile
[ -e "${ZDOTDIR:=${HOME}}/.zprofile" ] && . "${ZDOTDIR:=${HOME}}/.zprofile"
[ -e /etc/zlogin ] && . /etc/zlogin
[ -e "${ZDOTDIR:=${HOME}}/.zlogin" ] && . "${ZDOTDIR:=${HOME}}/.zlogin"
export NVM_DIR="${HOME}/.nvm"
[ -s "${NVM_DIR}/nvm.sh" ] && . "${NVM_DIR}/nvm.sh"
[ -s "${NVM_DIR}/bash_completion" ] && . "${NVM_DIR}/bash_completion"
if command -v nvm > /dev/null 2>&1; then
autoload -U add-zsh-hook
load-nvmrc() {
local nvmrc_path="$(nvm_find_nvmrc)"
if [ -n "${nvmrc_path}" ]; then
local nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")")
if [ "${nvmrc_node_version}" = "N/A" ]; then
nvm install
elif [ "${nvmrc_node_version}" != "$(nvm version)" ]; then
nvm use
fi
elif [ -n "$(PWD=$OLDPWD nvm_find_nvmrc)" ] && [ "$(nvm version)" != "$(nvm version default)" ]; then
echo "Reverting to nvm default version"
nvm use default
fi
}
add-zsh-hook chpwd load-nvmrc
load-nvmrc
fi
#!/usr/bin/env sh
# vim:ft=zsh
# shellcheck shell=sh disable=SC1091
set -eu
[ -e /etc/zshenv ] && . /etc/zshenv
[ -e "${ZDOTDIR:=${HOME}}/.zshenv" ] && . "${ZDOTDIR:=${HOME}}/.zshenv"
[ -e /etc/zprofile ] && . /etc/zprofile
[ -e "${ZDOTDIR:=${HOME}}/.zprofile" ] && . "${ZDOTDIR:=${HOME}}/.zprofile"
[ -e /etc/zlogin ] && . /etc/zlogin
[ -e "${ZDOTDIR:=${HOME}}/.zlogin" ] && . "${ZDOTDIR:=${HOME}}/.zlogin"
export NVM_DIR="${HOME}/.nvm"
if [ -f "${NVM_DIR}/nvm.sh" ]; then
. "${NVM_DIR}/nvm.sh"
if [ -f ".nvmrc" ]; then
nvm use
fi
fi
Install Postman.
Import:
Install and enable the HTTP Client plugin.
Open:
use with the local
or local-secure
environments defined in:
Install and enable the REST Client extension.
Add the following snippet to .vscode/settings.json
:
"rest-client.environmentVariables": {
"local": {
"host": "http://localhost",
"port": "3000"
},
"local-secure": {
"host": "https://localhost",
"port": "3000"
}
}
Open:
Switch (Ctrl+Alt+E / macOS: ⌘ Сmd+⌥ Opt+E) to the local
or local-secure
environment.
$ curl http://localhost:3000/
$ curl http://localhost:3000/-/health/liveness
$ curl --insecure https://localhost:3000/
$ curl --insecure https://localhost:3000/-/health/liveness
Runs the app from the source files (src/js/
); restarting on file changes.
$ npm run start:dev
Removes all containers, volumes, and images previously created by this project.
$ npm run docker:cleanup
Starts the app in a container exposing an HTTP port.
$ npm run docker:start
Starts the app in a container exposing an HTTPS port.
$ npm run docker:start:secure
❗
|
One needs to create the necessary private key and certificate via cert:create. |
Creates a private key and a self-signed certificate.
$ npm run cert:create
⇒ docker/certs/cert.pem
and docker/certs/key.pem
ℹ️
|
The generated certificate is valid for 30 days. |
Check your login keychain in Keychain Access; Secure Sockets Layer (SSL) should be set to "Always Trust":
ℹ️
|
Chrome and Safari need no further configuration. |
You need to bypass the self-signed certificate warning by clicking on "Advanced" and then "Accept the Risk and Continue":
Deletes the private key and the self-signed certificate.
$ npm run cert:delete
You can delete the certificate via Firefox > Preferences > Privacy & Security > Certificates
; click "View Certificates…":
Click on the "Servers" tab: