{"schemaVersion":"1.0","type":"TechArticle","types":["Article","TechArticle"],"slug":"docker-vs-pm2-which-one-does-your-node-app-actually-need-egnl2","url":"https://zyvop.com/docker-vs-pm2-which-one-does-your-node-app-actually-need-egnl2","title":"Docker vs PM2: Which One Does Your Node App Actually Need?","subtitle":"They both keep your Node app alive, but they solve different problems. Here's how to pick one without overthinking it.","tldr":"PM2 and Docker get compared a lot, but they aren't really competitors. One manages processes, the other packages and isolates your whole environment. Here's what each does well, where each falls short, and when to use them together.","keywords":["Docker","PM2","deployment","DevOps","node.js","Web Development"],"entities":["Sanju Singh","Docker","PM2","deployment","DevOps","node.js","Web Development","ZyVOP"],"keyTakeaways":["It sounds like a fair comparison, but it's a bit like asking whether you need a toolbox or a screwdriver.","They overlap in a few places, and that overlap is exactly why people get confused.","The way I make sense of it is to split the job into three layers: Supervision: keep the process alive, restart it when it dies, use every core."],"headings":["What PM2 does","What Docker does","Where they overlap","The cluster mode question","Deployments and zero downtime","When things go wrong","Shutdown is your job","Logs and monitoring","Using them together","So which one should you pick?"],"outboundLinks":[],"contentText":"Every few months someone asks the same question: should I use Docker or PM2 to run my Node app? It sounds like a fair comparison, but it's a bit like asking whether you need a toolbox or a screwdriver. They overlap in a few places, and that overlap is exactly why people get confused. The way I make sense of it is to split the job into three layers: Supervision: keep the process alive, restart it when it dies, use every core. Packaging: make the environment reproducible and keep the app isolated from everything else on the machine. Orchestration: run copies across several machines, roll out new versions safely, replace machines that die. PM2 lives in layer one. Docker lives in layer two and borrows a little of layer one through its restart policy. Layer three is where Kubernetes, Swarm and managed container platforms live. Most of the confusion comes from tools reaching into each other's layer. What PM2 does PM2 is a process manager for Node.js. You install it with npm, hand it your app, and it keeps that app running. npm install -g pm2 pm2 start server.js --name api -i max pm2 startup # prints a command; run it once (with sudo) to enable start on boot pm2 saveThose commands get you a lot. Your app restarts if it crashes, it runs one worker per CPU core, and it comes back after a server reboot. You also get logs and a live dashboard with pm2 logs and pm2 monit. When you deploy new code, pm2 reload api restarts workers one at a time, so users don't see downtime as long as your app shuts down cleanly. That only works in cluster mode, which is what -i max turns on. In practice you'll want this in a config file that lives in your repo, so the setup is versioned along with your code: // ecosystem.config.js module.exports = { apps: [{ name: 'api', script: './server.js', instances: 'max', exec_mode: 'cluster', max_memory_restart: '400M', env_production: { NODE_ENV: 'production', PORT: 3000, }, }], };Start it with pm2 start ecosystem.config.js --env production. If your package.json sets \"type\": \"module\", name the file ecosystem.config.cjs instead. For a single server and a single app, that's a very pleasant experience. No new concepts, no extra layer. It's just Node, supervised. The catch is that PM2 only manages processes. It has no opinion about the machine they run on. Your server needs the right Node version, the right system libraries, the right environment variables. If staging has Node 22 and production has Node 24, PM2 will happily run your app on both and let you find out the hard way. What Docker does Docker solves a different problem: it packages your app together with everything it needs to run. The Node version, the OS libraries, your dependencies, your build output, all baked into an image. That image runs the same on your laptop, in CI, and on a production server. FROM node:24-alpine ENV NODE_ENV=production WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev COPY . . USER node EXPOSE 3000 CMD [\"node\", \"server.js\"]docker run -d --restart unless-stopped -p 3000:3000 myappTwo notes on that Dockerfile. Add a .dockerignore that lists node_modules and .git, otherwise COPY . . copies your local node_modules into the image on top of the clean install. And it assumes a plain JavaScript app; with TypeScript you'd add a build stage. You also get isolation (your app can't trample other things on the box), resource limits, health checks, and a clean way to run a database or Redis next to your app with Docker Compose. The price is a learning curve. Images, layers, volumes, networks, registries: there's a whole vocabulary to pick up, and debugging inside a container feels different from debugging on a plain server. Builds also add a step to your workflow. Where they overlap Both can restart a crashed app, start it on boot, collect logs, and react to runaway memory use. That's the core of the overlap, and it's why people treat them as substitutes. Here's the rest side by side: PM2 Docker Main job Supervise Node processes Package and isolate an app and its environment Environment consistency No, depends on the host Yes, the image is the environment Multi-core usage Built in (cluster mode) Run several containers, or use cluster in the app Zero-downtime deploys pm2 reload Needs a reverse proxy or orchestrator Non-Node apps Possible, but not its focus Yes, anything Learning curve Gentle Steeper Scaling past one server Not its job Natural fit with Swarm, Kubernetes, managed container platforms The cluster mode question This is where PM2 feels a bit magical. Node runs your code on a single thread, so on an 8-core machine you're using one core unless you do something about it. pm2 start -i max forks a worker per core and load-balances between them. Done. Docker has no equivalent built in. The usual approach is to run several containers behind a reverse proxy like Nginx, or let an orchestrator manage the replicas. In Kubernetes the convention is one process per container, and you scale by adding more of them. It's more moving parts, but it keeps working when you outgrow a single machine, which PM2 can't do. Here's the Docker version of the same idea with Compose: two replicas of the app behind Nginx. # docker-compose.yml services: api: build: . restart: unless-stopped environment: NODE_ENV: production deploy: replicas: 2 healthcheck: test: [\"CMD\", \"wget\", \"-q\", \"--spider\", \"http://127.0.0.1:3000/health\"] interval: 30s timeout: 3s retries: 3 nginx: image: nginx:alpine ports: - \"80:80\" volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro depends_on: - apiThe api service publishes no ports, so the replicas don't fight over one. Nginx reaches them by service name, using a small nginx.conf: server { listen 80; location / { proxy_pass http://api:3000; } }The health check assumes your app has a /health route. One gotcha: Nginx looks up the name api when it starts, so reload it after you recreate the containers. Deployments and zero downtime With PM2, a deploy is: pull the code, install, pm2 reload. With Docker, it's: build an image, push it, pull it on the server, start the new container, switch traffic, stop the old one. By hand on one server, that means a few seconds of downtime. Doing it properly takes a rolling update from an orchestrator, or a proxy that can shift traffic between old and new, which is more work upfront. When things go wrong The feature lists look similar until something breaks. Here's what each tool does in the situations that actually page people at night: Situation PM2 Docker App crashes Restarts it. After too many quick crashes in a row, it gives up and marks the app errored. Restarts it per the restart policy, waiting longer after each quick failure. App hangs but doesn't exit Doesn't notice. A health check marks the container unhealthy, but plain Docker and Compose won't restart it. An orchestrator will. Memory leak max_memory_restart restarts the process once it passes the limit. A memory limit, if you set one, kills the container when it's exceeded, and the restart policy brings it back. Server reboots Comes back if you ran pm2 startup and pm2 save. Comes back through the restart policy (unless-stopped or always), as long as the Docker daemon starts on boot. Bad deploy Redeploy the old code and reload. Start the previous image tag. The machine dies Your app is gone. Your app is gone, unless an orchestrator moves it to another machine. The hung-app row surprises people: a health check only reports status, so something else has to act on it. Shutdown is your job Both tools stop your app with a signal, and your code has to listen for it. docker stop sends SIGTERM and waits 10 seconds by default before killing the container. PM2 sends SIGINT and waits only 1.6 seconds by default, which you can change with kill_timeout. An app that ignores the signal drops whatever requests are in flight. const server = app.listen(3000); function shutdown() { // stop accepting new connections and let in-flight requests finish server.close(() =&gt; process.exit(0)); // give up if that takes too long setTimeout(() =&gt; process.exit(1), 8000).unref(); } process.on('SIGTERM', shutdown); // Docker process.on('SIGINT', shutdown); // PM2, and Ctrl+CIf you use PM2, set kill_timeout above that 8 seconds, or PM2 will kill the app first. In Docker, you may also want an init process (docker run --init, or init: true in Compose) so signals are forwarded properly and stray child processes get cleaned up. Logs and monitoring PM2 writes each app's output to files under ~/.pm2/logs, split into stdout and error logs. pm2 logs tails them, pm2 monit shows CPU and memory per process, and pm2 install pm2-logrotate keeps the files from eating your disk. Docker flips the convention: your app writes to stdout and stderr, and Docker captures it. docker logs -f follows it, and a logging driver or plugin can ship it somewhere central like CloudWatch or Loki, so your logs stop living on one server's disk. One catch: the default log driver keeps growing its files unless you set max-size and max-file. I use Dozzle for this. It's a small web UI that tails every container's logs live, and it only works because everything writes to stdout. Using them together You can run PM2 inside a container with pm2-runtime, which exists for exactly that: CMD [\"pm2-runtime\", \"start\", \"ecosystem.config.js\", \"--env\", \"production\"]It's a reasonable move if you want cluster mode on a single host and you like PM2's config file (you'll need to install pm2 in the image, since the base Node image doesn't ship with it). But it's often redundant. Docker's restart policy already handles crashes, and an orchestrator already handles scaling. Put PM2 on top and you have two things deciding when to restart your app, which gets confusing fast. If you do combine them, set instances to a fixed number rather than 'max'. Inside a container, 'max' can count the host's cores instead of the CPU limit you gave the container. So which one should you pick? Here's how I'd decide. Pick PM2 if you have one or two apps on a single VPS, you control the server, your team is small, and you want to ship today without learning a new toolchain. It's simple and it works, and there's no shame in that. Pick Docker if you run more than one service, need a database or cache alongside your app, deploy through CI/CD, or work on a team where “it works on my machine” has come up. It's also the safer bet if you expect to move to a cloud platform or Kubernetes at some point, since plenty of hosting platforms expect a container anyway. Use both if you want PM2's cluster mode and config inside a container on a single host. Just be sure you actually need it. I run my own Node apps in Docker, and it's what I'd reach for by default on a new project. My setup is more than the app: a Postgres database, Dozzle for reading logs, Cloudflare Tunnels, and my background workers all run in Docker too. A worker is just another container, often the same image started with a different command. PM2 only manages the Node processes, so all of that would have lived outside it. The container has become the standard unit of deployment across the ecosystem, and the habit transfers everywhere. That said, if PM2 is already running your app happily and nothing is hurting, don't rip it out for the sake of it. So it comes down to what you're missing. If you just need a Node process kept alive, PM2 has you covered. If you need your app to behave the same on every machine it touches, that's what Docker is for.","contentHash":"sha256:2078268328f8a483971c6fb727454ba13084b60d9d31efb7e8f7c315284001cf","authorName":"Sanju Singh","authorUrl":"https://zyvop.com/author/sanjay687","authorSameAs":[],"category":"Web Development","tags":["Docker","PM2","deployment","DevOps","node.js"],"audience":"Software engineers and developers building applications with Web Development","tone":"Analytical, comparative, and data-driven","readingTimeMinutes":9,"wordCount":1970,"faqs":null,"primaryTopic":"Web Development","publishedAt":"2026-10-16T07:01:19.432Z","updatedAt":"2026-10-16T07:01:19.432Z","canonicalUrl":"https://zyvop.com/docker-vs-pm2-which-one-does-your-node-app-actually-need-egnl2"}