“Docker doesn’t have to be scary — especially when it’s built for developers, not DevOps pros.”
If you’ve ever tried deploying a Node.js app and ended up tangled in a mess of configs, bloated images, or painful restarts — this one’s for you. In this guide, I’ll show you how to Dockerize your Node.js app the right way:
✅ Clean, production-ready images
✅ Seamless local dev with hot-reloading
✅ Zero-downtime restarts using PM2
✅ No DevOps overkill
🚀 Why Docker?
Let’s break the usual cycle:
- “Works on my machine” issues 🧯
- Environment drift 😵💫
- Downtime every time you push code 😫
With Docker:
- You define exactly how your app runs.
- You get reproducible builds.
- You can deploy with zero downtime using tools like PM2.
🧱 Project Structure
my-app/
├── Dockerfile
├── ecosystem.config.js
├── docker-compose.yml
├── package.json
├── tsconfig.json
├── src/
│ └── server.ts
└── dist/ # for production build
We’ll write:
- A clean multi-stage Dockerfile
- A PM2 setup for graceful reloads
- A docker-compose.yml for local dev
📦 Multi-Stage Dockerfile
# ----- Base image (dependencies) -----
FROM node:18-slim AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
# ----- Build stage (TypeScript) -----
FROM node:18-slim AS builder
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
# ----- Production image -----
FROM node:18-slim
WORKDIR /app
# Copy deps & dist only
COPY --from=base /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY ecosystem.config.js .
# Use PM2 in cluster mode
RUN npm install -g pm2
CMD ["pm2-runtime", "ecosystem.config.js"]
✅ Clean separation
✅ Smaller, faster image
✅ Dev dependencies stay out of production
🧠 ecosystem.config.js (PM2 Setup)
module.exports = {
apps: [{
name: "my-app",
script: "./dist/server.js",
instances: "max",
exec_mode: "cluster", // zero-downtime restarts
watch: false
}]
};
pm2-runtime keeps your app alive with auto-restart & zero downtime.
⚙️ docker-compose.yml (Dev Convenience)
version: "3.8"
services:
node:
build:
context: .
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
command: npm run dev
Add this to package.json:
"scripts": {
"dev": "nodemon --watch src --exec ts-node src/server.ts",
"build": "tsc"
}
Now just run:
docker-compose up
✅ Instant hot-reload dev experience
✅ Consistent across machines
🔥 Zero-Downtime with PM2
Most people run:
pm2 restart my-app
🚨 This causes downtime.
Instead, use:
pm2 reload my-app
✅ It:
- Spins up new instance
- Waits until it’s ready
- Gracefully shuts down old one
🔐 .dockerignore
Add one to keep your build lean:
node_modules
dist
.git
.env
*.log
🧊 Final Image Size?
- From ~700MB → <150MB using multi-stage + slim base + no dev deps
- PM2 ensures zero downtime and handles scaling across CPUs
📌 TL;DR
🙌 About Me
I’m Sachin — Principal Engineer, blog writer, and builder of clean, scalable software.
👉 Explore more of my work & projects on
🌐 sachinkasana-dev.vercel.app
🌐 https://json-formatter-dev.vercel.app/
📂 Direct Gist URL:
👉 https://gist.github.com/sachinkasana/70617b3ef629c7277a34a2f697532dd1
I regularly write about:
- Real-world architecture patterns
- Node.js & TypeScript best practices
- Dev tools, performance, and clean code
Top comments (0)