All posts

Deploying Node.js Apps with Docker and Nginx on a VPS

Post Share

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:

Database Performance Considerations

If your Node.js app connects to PostgreSQL, Docker adds another layer to optimize. Connection pooling becomes critical when containers restart, and default Postgres settings rarely match production load.

For a deep dive into optimizing PostgreSQL in Docker, including connection pool configuration, query optimization, and memory tuning for containerized databases, see the complete guide.

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.

More on similar topics

#docker Docker and Kubernetes: Complete Production Deployment Guide 10 May 2026 #nextjs Next.js Deployment: Vercel vs VPS vs Docker in 2026 8 May 2026 #docker Docker Security Best Practices: Production Hardening 2026 8 May 2026