Deploying Node.js Apps with Docker and Nginx on a VPS
This is the exact workflow I use on every project. No fancy orchestration, just Docker Compose, Nginx, and Let's Encrypt running on a plain Ubuntu VPS.
Prerequisites
- A VPS running Ubuntu 22.04 (I use Linode or DigitalOcean)
- A domain pointed at your server's IP
- Docker and Docker Compose installed
1. Containerise your app
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Build and test locally:
docker build -t myapp .
docker run -p 3000:3000 myapp
2. Docker Compose setup
version: '3.8'
services:
app:
image: myapp:latest
restart: unless-stopped
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
networks:
- web
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./certs:/etc/letsencrypt
depends_on:
- app
networks:
- web
networks:
web:
3. Nginx configuration
server {
listen 80;
server_name yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
location / {
proxy_pass http://app:3000;
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;
}
}
4. SSL with Let's Encrypt
apt install certbot
certbot certonly --standalone -d yourdomain.com
Set up auto-renewal:
crontab -e
# Add: 0 3 * * * certbot renew --quiet && docker compose restart nginx
5. Zero-downtime deploy script
#!/bin/bash
docker pull myapp:latest
docker compose up -d --no-deps app
echo "Deployed at $(date)"
That's it. Simple, reliable, and you own the whole stack.