Deployment Strategies (Heroku, Docker, VPS) · learncode.live

Deployment Options

PlatformBest ForEffortScalingCost
HerokuQuick deployment, startupsLowManual/AutoPay-per-dyno
DockerContainerized apps, consistencyMediumOrchestrationDepends on host
VPS (DigitalOcean, AWS EC2)Full control, custom setupHighManualFixed monthly

Pre-Deployment Checklist

// Before deploying, ensure:
// 1. Use environment variables for config
// 2. Set NODE_ENV=production
// 3. Use a process manager (PM2)
// 4. Enable logging (Winston)
// 5. Set up error handling
// 6. Use a reverse proxy (Nginx)
// 7. Enable compression
// 8. Set security headers (Helmet)
// 9. Add rate limiting
// 10. Configure CORS properly

Production-Ready Server Setup

const express = require('express');
const helmet = require('helmet');
const compression = require('compression');
const cors = require('cors');

const app = express();

// Production middleware
app.use(helmet());
app.use(compression());
app.use(cors({ origin: process.env.CLIENT_URL }));
app.use(express.json({ limit: '100kb' }));

// Trust proxy (important behind Nginx/Heroku)
app.set('trust proxy', 1);

// Routes
app.use('/api', require('./routes'));

// 404 & error handlers
app.use((req, res) => res.status(404).json({ error: 'Not found' }));
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ error: 'Internal server error' });
});

module.exports = app;

1. Deploying to Heroku

Requirements

# Install Heroku CLI
# Create a Heroku account

# Login
heroku login

# Create app
heroku create my-express-app

Procfile

web: npm start

package.json Scripts

{
  "scripts": {
    "start": "node index.js",
    "heroku-postbuild": "echo 'Build complete'"
  },
  "engines": {
    "node": "18.x"
  }
}

Deploy

# Set environment variables
heroku config:set NODE_ENV=production
heroku config:set JWT_SECRET=$(openssl rand -base64 32)
heroku config:set MONGO_URI=mongodb+srv://...

# Add MongoDB addon (or use external)
heroku addons:create mongolab:sandbox

# Deploy
git push heroku main

# Open app
heroku open

# View logs
heroku logs --tail

# Scale
heroku ps:scale web=1

Zero-Downtime Deployment

// Use multiple dynos for rolling updates
heroku ps:scale web=2
// Heroku will start new dynos before stopping old ones

2. Deploying with Docker

Dockerfile

FROM node:18-alpine

WORKDIR /app

# Install dependencies
COPY package*.json ./
RUN npm ci --only=production

# Copy source
COPY . .

# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 && \
    chown -R nodejs:nodejs /app

USER nodejs

EXPOSE 3000

CMD ["node", "index.js"]

.dockerignore

node_modules
npm-debug.log
.env
.git
.gitignore
logs
README.md

Docker Compose (Multi-Service)

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - MONGO_URI=mongodb://mongo:27017/myapp
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      - mongo
      - redis
    restart: always

  mongo:
    image: mongo:7
    volumes:
      - mongo_data:/data/db
    restart: always

  redis:
    image: redis:7-alpine
    restart: always

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - app
    restart: always

volumes:
  mongo_data:

Build & Run

# Build and start
docker-compose up -d --build

# View logs
docker-compose logs -f app

# Stop
docker-compose down

# Deploy to server
docker save myapp:latest | ssh user@server 'docker load'

Multi-Stage Build (Smaller Image)

# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

EXPOSE 3000
CMD ["node", "dist/index.js"]

3. Deploying to a VPS (DigitalOcean, AWS EC2, Linode)

Server Setup

# SSH into server
ssh root@your-server-ip

# Update system
apt update && apt upgrade -y

# Install Node.js
curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
apt install -y nodejs git nginx

# Install PM2 (process manager)
npm install -g pm2

# Clone your app
git clone https://github.com/your/repo.git /app
cd /app
npm ci --only=production

# Set environment variables
cat > /app/.env << EOF
NODE_ENV=production
PORT=3000
MONGO_URI=...
JWT_SECRET=...
EOF

# Start with PM2
pm2 start index.js --name "myapp"

# Save PM2 process list
pm2 save
pm2 startup

PM2 Configuration (ecosystem.config.js)

module.exports = {
  apps: [{
    name: 'myapp',
    script: 'index.js',
    instances: 'max',          // Use all CPU cores
    exec_mode: 'cluster',      // Cluster mode for load balancing
    env: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    error_file: './logs/err.log',
    out_file: './logs/out.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss',
    max_memory_restart: '500M',
    autorestart: true,
    watch: false,
    max_restarts: 10,
    restart_delay: 5000
  }]
};
# Start with config
pm2 start ecosystem.config.js

# Monitor
pm2 monit

# List processes
pm2 list

# Graceful reload (zero downtime)
pm2 reload ecosystem.config.js

Nginx as Reverse Proxy (on VPS)

# /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }

    # Enable gzip
    gzip on;
    gzip_types text/plain application/json application/javascript text/css;
}
# Enable site
ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
nginx -t
systemctl restart nginx

# Set up SSL with Certbot
apt install certbot python3-certbot-nginx
certbot --nginx -d yourdomain.com

Deployment Comparison

const deployments = {
  heroku: {
    setup: 'Minutes',
    scaling: 'Easy (horizontal)',
    cost: 'Pay per dyno',
    control: 'Limited',
    bestFor: 'Quick MVPs, startups'
  },
  docker: {
    setup: 'Hours',
    scaling: 'Via orchestrator',
    cost: 'Server cost + orchestration',
    control: 'Full',
    bestFor: 'Microservices, consistent environments'
  },
  vps: {
    setup: 'Half day',
    scaling: 'Manual (vertical/horizontal)',
    cost: 'Fixed monthly',
    control: 'Complete',
    bestFor: 'Full control, custom requirements'
  }
};

Key Takeaways

  • Heroku: Fastest setup, git push deployment, good for MVPs
  • Docker: Consistent environments across dev/prod, container orchestration
  • VPS: Full control, PM2 for process management, Nginx for reverse proxy
  • Always use environment variables for config
  • Use PM2 with cluster mode for multi-core CPU utilization
  • Set up a reverse proxy (Nginx) for SSL, load balancing, static file serving
  • Enable compression, Helmet, and rate limiting in production
  • Set app.set('trust proxy', 1) when behind a reverse proxy
Courses