WhereAbout API · Deployment Runbook

Every command to bring the backend up on a fresh Ubuntu server

Target: Ubuntu 24.04 (noble) · App dir: /home/mobile-app/backend · Runtime: Node 20 + MongoDB 8.0 + PM2 + Nginx · Public URL: https://mobilevps.tech (443) → app on 127.0.0.1:4000

Step 0 Get the code

Generate an SSH key, add the public key to GitHub, then clone the repo.

# Create an SSH key and print the public half to add at GitHub → Settings → SSH keys
ssh-keygen -t ed25519 -C "you@example.com" -f ~/.ssh/id_ed25519 -N ""
cat ~/.ssh/id_ed25519.pub

# Clone and enter the backend
git clone git@github.com:mohit49/social-mobile-app.git /home/mobile-app
cd /home/mobile-app/backend

Step 1 Install Node.js 20 + npm

Via the official NodeSource repository.

apt-get update -y
apt-get install -y ca-certificates curl gnupg
mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
  | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" \
  > /etc/apt/sources.list.d/nodesource.list
apt-get update -y
apt-get install -y nodejs

# Verify
node -v   # v20.x
npm -v

Step 2 Install & start MongoDB 8.0

curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc \
  | gpg --dearmor -o /usr/share/keyrings/mongodb-server-8.0.gpg
echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" \
  > /etc/apt/sources.list.d/mongodb-org-8.0.list
apt-get update -y
apt-get install -y mongodb-org

# Start now and enable on boot
systemctl enable --now mongod
systemctl status mongod --no-pager

Step 3 Configure environment

Config is segregated per NODE_ENV: .env.development for local, .env.production for prod. Only the *.example files are committed.

# Create the production env file from the template, then edit it
cp .env.production.example .env.production

# Generate strong JWT secrets and paste them in
openssl rand -hex 32   # -> JWT_ACCESS_SECRET
openssl rand -hex 32   # -> JWT_REFRESH_SECRET
Set APP_URL=https://mobilevps.tech, MONGODB_URI=mongodb://127.0.0.1:27017/whereabout, and tighten CORS_ORIGIN to your real client origins.

Step 4 Install dependencies & build

npm install
npm run build   # compiles TypeScript -> dist/

Step 5 Run continuously with PM2

PM2 keeps the app alive, restarts on crash, and brings it back after reboot.

# Install PM2 into the system prefix so it uses the persistent Node
npm install -g pm2 --prefix /usr

# Start from the ecosystem config (single fork instance — Socket.IO safe)
pm2 start ecosystem.config.cjs

# Persist the process list and enable boot startup
pm2 save
pm2 startup systemd -u root --hp /root
systemctl enable --now pm2-root
Cluster mode would break Socket.IO (websockets need sticky sessions). The ecosystem.config.cjs runs a single fork instance on purpose.

Step 6 Nginx reverse proxy (drop the port)

Serves the app on standard port 80 so the public URL has no :4000. The app keeps serving /api/v1, /socket.io, /uploads and the docs at root — Nginx just forwards to it.

apt-get install -y nginx

# Site config lives at /etc/nginx/sites-available/whereabout
# (proxies / and /socket.io to 127.0.0.1:4000 with websocket upgrade)
ln -sf /etc/nginx/sites-available/whereabout /etc/nginx/sites-enabled/whereabout
rm -f /etc/nginx/sites-enabled/default

nginx -t                       # validate config
systemctl enable --now nginx
systemctl reload nginx
App config: app.set('trust proxy', 1) and APP_URL=https://mobilevps.tech (no port) so generated links (emails, profile photos) are absolute and port-less.

Step 7 Verify

# Public URLs — no port
curl https://mobilevps.tech/health
# {"success":true,"data":{"status":"ok","service":"whereabout-backend"}}

# Socket.IO handshake
curl "https://mobilevps.tech/socket.io/?EIO=4&transport=polling"

# API base & docs in a browser
# https://mobilevps.tech/api/v1/...
# https://mobilevps.tech/
Reachable externally only if the firewall / security group allows inbound TCP 80. On UFW: ufw allow 'Nginx HTTP' (or ufw allow 80/tcp). Port 4000 stays internal.

Ops Day-to-day commands

pm2 status                 # list processes
pm2 logs whereabout-backend  # tail logs
pm2 restart whereabout-backend --update-env
pm2 stop whereabout-backend
pm2 delete whereabout-backend

# Deploy a new version
cd /home/mobile-app/backend
git pull
npm install
npm run build
pm2 restart whereabout-backend --update-env

npm shortcuts: npm run pm2:start, npm run pm2:restart, npm run pm2:logs, npm run pm2:stop.