Containerize Node.js

  1. Use Deterministic Builds

Avoid :latest: Use explicit versions for consistent builds. Example: node:23.6.1-bullseye-slim@sha256:....

Slim Images: Prefer -slim variants for smaller images and reduced attack surfaces, but avoid node:alpine due to compatibility issues.

  1. Install Production Dependencies Only

Use npm ci --only=production or pnpm install --frozen-lockfile to install only production dependencies for smaller, more secure images.

  1. Set NODE_ENV=production

Enables performance and security optimizations for frameworks like Express. Always define this in the Dockerfile.

  1. Avoid Running Containers as Root

Use the least-privilege user (node).

Ensure proper ownership using COPY --chown=node:node.

  1. Graceful Application Termination

Use tools like dumb-init to handle termination signals (SIGTERM, SIGINT) properly.

Implement signal handlers in Node.js to close resources gracefully.

  1. Multi-Stage Builds

Separate build and production stages to optimize image size and remove sensitive data (e.g., NPM_TOKEN).

  1. Security Scanning

Test images with tools like Snyk for vulnerabilities. Regularly update dependencies and base images.

  1. Exclude Unnecessary Files

Use .dockerignore to avoid including sensitive or unnecessary files like .git, node_modules, and .env.

  1. Handle Secrets Securely

Mount secrets (e.g., .npmrc) securely during the build using Docker secrets. Avoid embedding them in the final image.

  1. Graceful Shutdown and Signal Handling

Use Node.js event handlers to close connections and finish requests before shutting down.

Example Dockerfile:

Key Tools and Recommendations:

Use Snyk: Integrate into CI/CD for continuous security monitoring.

Docker Secrets: Safely handle sensitive information during builds.

dumb-init: Manage PID 1 and signal forwarding.

References:

How to build Node.js Docker images — Snyk
Performance and reliability best practices for Express — Express.js

Last updated

Was this helpful?