Deploying Node (PM2 & Docker)
Deploying a Node app means running it reliably in production: loading config from environment variables, keeping the process alive with a manager like PM2 or a container, exposing a health check, and shutting down gracefully on SIGTERM.
Learn Deploying Node (PM2 & Docker) in our free Node.js course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Node.js course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll set NODE_ENV, load config safely from env vars, supervise your app with PM2 (cluster, logs, restart, reload), containerize it with a cache-friendly Dockerfile and health check, and handle graceful shutdown so deploys never drop a request.
What You'll Learn in This Lesson
1️⃣ Environment & Config
Production code should never hard-code secrets or settings. Instead it reads environment variables — process.env.PORT , process.env.DATABASE_URL , process.env.NODE_ENV — so the same image runs in dev, staging, and prod with different values. A good config loader applies defaults and fails fast : if a required variable is missing, it crashes at startup, not later under load.
2️⃣ Keeping It Alive: PM2 & Graceful Shutdown
A plain node app.js stops the instant it crashes or the box reboots. PM2 is a process manager that supervises your app: it restarts on crash, runs one worker per CPU core in cluster mode , aggregates logs, and can reload with zero downtime. These are shell commands you run on the server:
When PM2 (or Docker) stops your app it sends SIGTERM first. Catch it and shut down gracefully : stop accepting new connections, let in-flight requests finish, then exit. The block below runs for real and sends itself SIGTERM so you can see the sequence:
3️⃣ Containerizing with Docker + Health Checks
Docker packages your app and its exact Node version into an image that runs identically anywhere. The key trick is layer caching : copy package*.json and run npm ci before copying your source, so the slow install step is cached when only your code changes.
The Dockerfile's HEALTHCHECK pings a /health route so the orchestrator knows whether this instance should receive traffic. That route is just a cheap endpoint in your app:
Your turn. Fill in the two ___ blanks so the config helpers read the port and mode correctly, then run it.
No blanks this time — just a brief and an outline. Write it yourself, run it, and check your output. This is the exact pattern that keeps deploys from dropping live requests.
📋 Quick Reference — Deployment
Practice quiz
Why should production code read settings from environment variables?
- It makes the code run faster
- The same image can run in dev, staging, and prod with different values and no hard-coded secrets
- Environment variables are encrypted automatically
- It removes the need for a database
Answer: The same image can run in dev, staging, and prod with different values and no hard-coded secrets. Reading from env vars keeps secrets out of code and lets one image run anywhere with different values.
What does a 'fail fast' config loader do when a required variable is missing?
- Substitutes a random default
- Logs a warning and keeps running
- Crashes at startup instead of later under load
- Retries every few seconds forever
Answer: Crashes at startup instead of later under load. Failing fast means crashing at boot so a misconfigured deploy is obvious immediately, not later.
What does setting NODE_ENV=production do?
- It encrypts all responses
- It signals Express and many libraries to switch into optimized production behavior
- It opens a new port automatically
- It disables the database
Answer: It signals Express and many libraries to switch into optimized production behavior. NODE_ENV=production tells Express and libraries to cache views, skip verbose errors, and optimize.
What is the main job of a process manager like PM2?
- It compiles your TypeScript
- It writes your Dockerfile
- It minifies your JavaScript
- It keeps your app alive, restarting it on crash and after reboots
Answer: It keeps your app alive, restarting it on crash and after reboots. PM2 supervises the app: restart on crash, cluster mode, log handling, and relaunch after reboot.
Which signal do PM2, Docker, and Kubernetes send to ask your app to stop?
- SIGTERM
- SIGKILL
- SIGHUP
- SIGUSR1
Answer: SIGTERM. They send SIGTERM first; catch it to shut down gracefully before the platform force-kills with SIGKILL.
What does a graceful SIGTERM handler call to stop accepting new connections while finishing in-flight ones?
- process.exit() immediately
- server.close()
- app.disable()
- process.kill()
Answer: server.close(). server.close() stops new connections but lets existing requests finish, then you exit cleanly.
Why copy package*.json and run npm ci BEFORE copying the rest of the source in a Dockerfile?
- To make the image larger
- So the slow install layer is cached and reused when only your code changes
- Because npm requires it
- To hide your source code
Answer: So the slow install layer is cached and reused when only your code changes. Copying manifests first lets Docker cache the expensive npm ci layer across builds where deps didn't change.
Why use the array form CMD ["node", "app.js"] instead of the string form in a Dockerfile?
- It is shorter to type
- It runs the app twice for safety
- So Node runs as PID 1 and receives SIGTERM directly for graceful shutdown
- It enables cluster mode
Answer: So Node runs as PID 1 and receives SIGTERM directly for graceful shutdown. The exec/array form makes Node PID 1 so SIGTERM reaches it; the string form wraps it in a shell that may swallow the signal.
What is the purpose of a /health endpoint and the Dockerfile HEALTHCHECK?
- To return server logs
- To restart the database
- To serve the homepage
- To let the orchestrator know whether the instance should receive traffic
Answer: To let the orchestrator know whether the instance should receive traffic. A cheap /health route lets the load balancer or orchestrator decide if the instance is alive and ready.
Which PM2 command reloads with zero downtime by draining old workers first?
- pm2 reload api
- pm2 restart api
- pm2 stop api
- pm2 kill api
Answer: pm2 reload api. pm2 reload drains old workers before swapping in new ones, giving zero-downtime deploys.