VPS Deployment Guide
Prerequisites
- Ubuntu 22.04+ / Debian 12+
- Node.js 24 (use nvm)
- PostgreSQL 16+
- Redis 7+
- MinIO (optional, for file uploads)
- Nginx (reverse proxy)
- PM2 (process manager)
1. Server Setup
Install Node.js 24
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
source ~/.bashrc
nvm install 24
nvm use 24
npm install -g yarn pm2
Install PostgreSQL
sudo apt update
sudo apt install -y postgresql postgresql-contrib
sudo systemctl enable postgresql
# Create user + database
sudo -u postgres psql <<SQL
CREATE USER booking WITH PASSWORD 'YOUR_DB_PASSWORD';
CREATE DATABASE booking_prod OWNER booking;
SQL
Install Redis
sudo apt install -y redis-server
sudo systemctl enable redis-server
Install MinIO (optional)
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
sudo mv minio /usr/local/bin/
# Create data directory
sudo mkdir -p /data/minio
sudo chown $USER:$USER /data/minio
# Create systemd service
sudo tee /etc/systemd/system/minio.service > /dev/null <<EOF
[Unit]
Description=MinIO
After=network-online.target
[Service]
User=$USER
Environment="MINIO_ROOT_USER=booking_minio"
Environment="MINIO_ROOT_PASSWORD=YOUR_MINIO_PASSWORD"
ExecStart=/usr/local/bin/minio server /data/minio --console-address ":9001"
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable minio
sudo systemctl start minio
2. Clone & Install
mkdir -p /home/projects/booking
cd /home/projects/booking
git clone git@github.com:novagoo/booking-api.git
git clone git@github.com:novagoo/booking-web.git
# API
cd booking-api
nvm use
yarn install --frozen-lockfile
# Web
cd ../booking-web
nvm use
yarn install --frozen-lockfile
3. Environment Variables
booking-api/.env
PORT=3010
NODE_ENV=production
# Database (local PostgreSQL, port 5432)
DATABASE_URL=postgresql://booking:YOUR_DB_PASSWORD@localhost:5432/booking_prod?schema=public
# Redis (local, default port)
REDIS_HOST=localhost
REDIS_PORT=6379
# MinIO
MINIO_ENDPOINT=localhost
MINIO_PORT=9000
MINIO_ACCESS_KEY=booking_minio
MINIO_SECRET_KEY=YOUR_MINIO_PASSWORD
# Auth
JWT_SECRET=GENERATE_A_STRONG_SECRET_HERE
JWT_EXPIRES_IN=30m
JWT_REFRESH_EXPIRES_IN=30d
# Bull Board (admin)
BULL_BOARD_USER=admin
BULL_BOARD_PASS=YOUR_BULL_PASSWORD
# Email (Resend)
RESEND_API_KEY=re_xxxxxxxxx
EMAIL_FROM=BookingSystem <noreply@yourdomain.com>
# Frontend URL (for CORS + email links)
FRONTEND_URL=https://yourdomain.com
CORS_ORIGIN=https://yourdomain.com
booking-web/.env
NEXT_PUBLIC_API_URL=http://localhost:3010/api
NEXT_PUBLIC_APP_NAME=BookingSystem
NEXT_PUBLIC_APP_SHORT_NAME=BS
Note:
NEXT_PUBLIC_API_URLpoints to localhost because Nginx proxies external requests, and Next.js server-side rewrites connect directly to the API.
4. Build & Migrate
# API: generate Prisma client + run migrations + seed
cd /home/projects/booking/booking-api
yarn prisma generate
yarn prisma migrate deploy
yarn seed # only for first deploy, creates demo data
yarn build
# Web: build
cd /home/projects/booking/booking-web
yarn build
5. PM2 Process Manager
Create ecosystem config
cat > /home/projects/booking/ecosystem.config.cjs << 'EOF'
module.exports = {
apps: [
{
name: 'booking-api',
cwd: '/home/projects/booking/booking-api',
script: 'dist/src/main.js',
instances: 1,
env: {
NODE_ENV: 'production',
},
},
{
name: 'booking-web',
cwd: '/home/projects/booking/booking-web',
script: 'node_modules/.bin/next',
args: 'start -p 3020',
instances: 1,
env: {
NODE_ENV: 'production',
},
},
],
};
EOF
Start services
cd /home/projects/booking
pm2 start ecosystem.config.cjs
pm2 save
pm2 startup # follow instructions to enable auto-start on boot
Useful PM2 commands
pm2 status # check status
pm2 logs # view logs
pm2 logs booking-api # API logs only
pm2 restart all # restart both
pm2 reload all # zero-downtime reload
6. Nginx Reverse Proxy
sudo apt install -y nginx
Create config
sudo tee /etc/nginx/sites-available/booking > /dev/null << 'EOF'
server {
listen 80;
server_name yourdomain.com;
# API
location /api/ {
proxy_pass http://localhost:3010/api/;
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;
client_max_body_size 10M;
}
# Web (Next.js)
location / {
proxy_pass http://localhost:3020;
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;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/booking /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
SSL with Certbot (optional but recommended)
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com
7. Deploy Updates
#!/bin/bash
# deploy.sh — run from /home/projects/booking
# API
cd booking-api
git pull
yarn install --frozen-lockfile
yarn prisma generate
yarn prisma migrate deploy
yarn build
pm2 restart booking-api
# Web
cd ../booking-web
git pull
yarn install --frozen-lockfile
yarn build
pm2 restart booking-web
echo "Deploy complete!"
Make it executable: chmod +x deploy.sh
8. Monitoring
# PM2 monitoring
pm2 monit
# Check logs
pm2 logs --lines 100
# Check disk/memory
df -h
free -h
# PostgreSQL connections
sudo -u postgres psql -c "SELECT count(*) FROM pg_stat_activity WHERE datname = 'booking_prod';"
Ports Summary
| Service | Port | Access |
|---|---|---|
| API (NestJS) | 3010 | Internal only |
| Web (Next.js) | 3020 | Internal only |
| PostgreSQL | 5432 | Internal only |
| Redis | 6379 | Internal only |
| MinIO API | 9000 | Internal only |
| MinIO Console | 9001 | Internal only |
| Nginx HTTP | 80 | Public |
| Nginx HTTPS | 443 | Public |
All services are accessed internally. Only Nginx port 80/443 is exposed to the internet.