Dockerize Node.js: Ship APIs That Actually Behave in Containers
Node.js is deceptively simple to containerize — until you hit PID 1 signal problems, zombie processes, and 1.2 GB images running as root. We build Node.js containers that handle SIGTERM gracefully, run as non-root, and weigh under 150 MB with all dependencies included.
Need this done for your project?
We implement, you ship. Async, documented, done in days.
Why Dockerize Node.js
Node.js servers need consistent runtime versions, native module compilation, and proper signal handling. Without Docker, you end up with nvm version mismatches, different OpenSSL builds between staging and production, and deployments that ignore SIGTERM because Node was not running as PID 1.
Containerizing Node also enables horizontal scaling — spin up identical replicas behind a load balancer without worrying about shared filesystem state or port conflicts.
Our Docker Implementation for Node.js
We use a multi-stage Dockerfile with node:20-alpine as the base:
- Stage 1 — install: Copies
package.jsonandpackage-lock.json, runsnpm ci --omit=devfor production deps only. Native modules that need build tools use a separatebuild-baseinstall. - Stage 2 — runner: Copies app source and production
node_modules. Creates anodeuser withaddgroup/adduser, setsUSER node. Usesdumb-initortinias the entrypoint to handle PID 1 reaping.
The Compose file defines health checks using wget --spider http://localhost:3000/health, sets stop_grace_period: 30s, and passes secrets via env_file. We configure logging with json-file driver and size rotation.
What You Get
- Multi-stage
Dockerfilewith non-root user and init process - Graceful shutdown handler wired to
SIGTERMandSIGINT docker-compose.ymlwith health checks, logging config, and restart policy.dockerignoreexcluding tests, docs, and dev configs- CI/CD build step with layer caching for
node_modules
Best Practices We Follow
No root: Every container runs as a non-root user — USER node is set after copying files with correct ownership.
Signal handling: tini as PID 1 ensures SIGTERM reaches your Node process. We also add a graceful shutdown hook that drains HTTP connections before exiting.
Layer caching: package*.json files are copied and installed before source code, so dependency layers are only rebuilt when the lockfile changes.
No secrets in layers: Environment variables are injected at runtime, never baked in with ENV or ARG.
Why Anubiz Engineering
Ready to get started?
Skip the research. Tell us what you need, and we'll scope it, implement it, and hand it back — fully documented and production-ready.