Zero-drama deployments on DigitalOcean with Nginx, PM2 and GitHub Actions
The exact production setup I use for Node apps: process management, reverse proxy, CI/CD runners, and the failure modes worth pre-empting.

One droplet, clear responsibilities
A single DigitalOcean Droplet is enough for many production Node apps — if you set it up right. My stack: Nginx terminates TLS and reverse-proxies to Node, PM2 manages Node processes with auto-restart, and Node.js/Express serves as the application server.
Nothing exotic — and that's the point. Every failure has an obvious place to look, making debugging a breeze.
CI/CD that a teammate can read
GitHub Actions builds the app, then a self-hosted runner pulls changes and reloads via PM2. The workflow has five stages: Build (`npm ci && npm run build`), Test (unit + integration tests, fail fast), Deploy (copy build artifacts to the droplet), Reload (`pm2 reload ecosystem.config.js`), and Health-check (hit `/health` endpoint, fail loudly if down).
- `pm2 reload` instead of `restart` — zero-dropped-requests
- Health-check step fails the deploy loudly rather than silently
- Environment variables live on the server, never in the repo
- Rollback is a single command: `pm2 restart ecosystem.config.js --env rollback`
The failures you'll actually hit (and how to fix them)
Permission mismatches on the runner user: fix by using `deploy` user with `sudo` for PM2 commands, but restrict SSH keys. Exhausted file descriptors: increase `ulimit -n 65535` in PM2 config. Nginx buffering large uploads: set `client_max_body_size 50M` and proxy buffering off for upload routes. Memory leaks in Node: use `node --max-old-space-size=512` and monitor with PM2's built-in metrics.
Each of these failures cost me an evening once, and each one is now a checklist item before go-live.
Blue-green deployments on a single droplet
Want zero-downtime without two droplets? Use Nginx to switch between two ports: deploy new version to port 3001 (staging), run smoke tests, then Nginx reload proxies to port 3001. The old version stays on port 3000, ready for instant rollback.
This strategy has saved my team during two critical incidents where the new version needed to be rolled back within 2 minutes.
Monitoring and alerting
Production-ready means proactive monitoring. I track PM2 metrics (CPU, memory, event loop lag), Uptime monitoring via UptimeRobot or Healthchecks.io, Error tracking through Sentry for exception alerts, and Log aggregation with Papertrail or ELK stack for centralized logs.
Set up alerts for: CPU > 80%, memory > 90%, and HTTP 5xx spikes. Early warning is the difference between a 5-minute fix and a 2-hour outage.
Written by
Tariq Mehmood
Full Stack MERN Developer

