DEV Community

Cover image for Zero Downtime Deploy with PM2, Docker and Reverse Proxy
Ozan
Ozan

Posted on • Edited on

Zero Downtime Deploy with PM2, Docker and Reverse Proxy

Uptime is critical. Whether you're running a SaaS, API, or web platform, users expect availability—always. In this guide, we'll walk through how to achieve zero downtime deployments using PM2, Docker and a reverse proxy like NGINX.

Why Zero Downtime Matters

User Experience: No interruptions during new releases

Business Continuity: Prevent revenue loss during deploys

Stack Overview

App Runtime: Node.js with PM2

Containerization: Docker

Web Server / Proxy: NGINX or Caddy

CI/CD Tool: (Drone.io (my new favorite), Jenkins, GitHub Actions, etc.)

Step 1: Use PM2 to Manage the App Process

PM2 is a Node.js process manager that supports graceful restarts, monitoring, and clustering.

Example Dockerfile with PM2

FROM node:18

WORKDIR /app

# Add build-time env vars
ARG NEXT_PUBLIC_CDN_URL
ARG NEXT_PUBLIC_CDN_HOST
ENV NEXT_PUBLIC_CDN_URL=$NEXT_PUBLIC_CDN_URL
ENV NEXT_PUBLIC_CDN_HOST=$NEXT_PUBLIC_CDN_HOST

COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

RUN npm install -g pm2

EXPOSE 3000
CMD ["pm2-runtime", "start", "node_modules/next/dist/bin/next", "--", "start", "-p", "3000"]
Enter fullscreen mode Exit fullscreen mode

Step 2: Create a Safe Docker Build + Deploy Flow

We will:

Build the Docker image

Run it on a temporary port

Perform health checks

If OK, swap it with the current live container

Example Shell Script (simplified)

docker build --no-cache -t app-latest .
docker run -d --name app-temp -p 3132:3000 app-latest

# Wait and test health
sleep 5
if docker exec app-temp curl -s http://localhost:3000 | grep -q '<title>'; then
  docker stop app-live && docker rm app-live
  docker stop app-temp && docker rm app-temp
  docker run -d --name app-live -p 3131:3000 app-latest
else
  echo "Health check failed. Aborting deploy."
  docker stop app-temp && docker rm app-temp
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

Port 3132 is used temporarily; 3131 is the live port behind the reverse proxy.

Step 3: Set Up Reverse Proxy with NGINX

server {
  listen 80;

  location / {
    proxy_pass http://localhost:3131;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  }
}
Enter fullscreen mode Exit fullscreen mode

NGINX routes traffic to the live container. When we swap containers on port 3131, traffic never notices.

Uptime Monitoring

Use tools like:

Uptime Kuma (Docker-based)
Better Uptime,
Upptime (hosted)

Set them to monitor /healthz or homepage on localhost:3131.

Security Tips

Avoid running Docker containers as root user

Don’t mount the Docker socket (/var/run/docker.sock) unless absolutely necessary

Use USER node or UID-based permissions in Dockerfile

Conclusion

Zero downtime deployment is achievable and maintainable with PM2, Docker, and a reverse proxy. This setup avoids user-facing errors, improves reliability, and keeps your engineering team confident in every release.

Want to go further? Add CI/CD integration with Drone or GitHub Actions and automate everything end-to-end.

That's all folks! ✨

Image description

Top comments (0)