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)