Skip to main content

Deployment Guide

Branch Model​

BranchEnvironmentURLCI DeployProtected
masterproductioncivicpulseindia.comβœ… on pushYes β€” PR + CI required
developtesttest.civicpulseindia.comβœ… on pushYes β€” PR + CI required
feature/*locallocalhost:5174❌No
hotfix/*productionboth (sequential PRs)❌No

Workflow:

  1. Develop features on feature/your-feature β†’ PR into develop
  2. develop auto-deploys to test.civicpulseindia.com β€” verify there
  3. When ready to release: PR develop β†’ master β†’ auto-deploys to production
  4. For emergency fixes: branch hotfix/description from master, PR into both master + develop

CivicPulse is deployed as an all-in-one server on a DigitalOcean Droplet. Nginx handles all traffic β€” frontend React app, backend API, WebSockets, and Docusaurus docs β€” and proxies API calls to a PM2-managed Node.js process.

Architecture:

Browser
β”‚
β–Ό
DigitalOcean Droplet (Ubuntu 22.04, Bangalore)
β”‚
β”œβ”€β”€ nginx (port 80/443 β€” Let's Encrypt TLS)
β”‚ β”œβ”€β”€ / β†’ frontend/dist (React SPA, static files)
β”‚ β”œβ”€β”€ /admin β†’ frontend/dist/admin (Admin SPA)
β”‚ β”œβ”€β”€ /api/* β†’ PM2/Node.js (port 3001)
β”‚ β”œβ”€β”€ /socket.io β†’ PM2/Node.js (WebSocket upgrade)
β”‚ └── /docs/ β†’ docs/build (Docusaurus static)
β”‚
└── PM2 β†’ Node.js (port 3001)
β”‚
β”œβ”€β”€ DynamoDB (AWS ap-south-1) ← tables stay on AWS
└── S3 (AWS ap-south-1) ← media storage on AWS

Estimated cost:

ServiceCost
DigitalOcean 2GB/1vCPU Bangalore Droplet~$12/mo
AWS DynamoDB (on-demand)Free tier for dev volumes
AWS S3~$1/mo

Prerequisites​

  • A DigitalOcean account (or any Ubuntu 22.04 VPS)
  • Domain registered at GoDaddy (or any registrar)
  • GitHub repository with the CivicPulse code
  • AWS credentials for DynamoDB + S3 (the dev tables already exist)

Phase 1 β€” Create the Droplet​

  1. DigitalOcean dashboard β†’ Create β†’ Droplets
  2. Image: Ubuntu 22.04 LTS
  3. Size: 2 GB RAM / 1 vCPU ($12/mo β€” Basic plan, Bangalore region BLR1)
  4. Authentication: SSH Key β†’ add your public key
  5. Create Droplet β†’ note the public IPv4 address (e.g. 159.65.155.22)
1 GB is not enough

npm builds (React + Docusaurus) need ~1.4 GB RAM. The setup script adds 2 GB swap automatically, but starting at 2 GB avoids the risk entirely.


Phase 2 β€” DNS​

At your domain registrar, add an A record:

TypeHostValue
AtestYour droplet IP (e.g. 159.65.155.22)

This points test.civicpulseindia.com to the droplet. DNS propagation takes 5–30 minutes.

Verify propagation:

dig test.civicpulseindia.com +short
# Should print your droplet IP

Phase 3 β€” Clone & Run Setup​

SSH into the droplet:

ssh -i ~/.ssh/your_key root@159.65.155.22

Configure git to cache credentials (needed for private repo):

git config --global credential.helper store

Clone the repo (enter your GitHub username + PAT when prompted):

rm -rf /tmp/civicsetup
git clone https://github.com/yashmody/civicpulse.git /tmp/civicsetup
GitHub Personal Access Token

GitHub no longer accepts passwords for git operations. Use a PAT as the password:

  1. github.com/settings/tokens β†’ Generate new token (classic)
  2. Note: civicpulse-deploy, Expiration: 30 days, Scope: βœ… repo
  3. Enter the token (not your password) when git prompts for Password:

Run the setup script:

chmod +x /tmp/civicsetup/deploy/setup-ec2.sh
/tmp/civicsetup/deploy/setup-ec2.sh

The script takes 8–12 minutes and handles:

  • 2 GB swap file (build safety net)
  • Node.js 20, PM2, nginx, certbot install
  • git clone into /var/www/civicpulse
  • npm ci + Vite build β†’ frontend/dist/
  • npm ci + Docusaurus build β†’ docs/build/
  • PM2 start + pm2 startup for auto-restart on reboot
  • nginx config deployment (full-stack: SPA + API + docs)
  • Let's Encrypt TLS certificate via certbot
  • UFW firewall (SSH + Nginx Full)

Phase 4 β€” Fill in Environment Variables​

After setup completes, fill in the real values:

nano /var/www/civicpulse/backend/.env

Values you must set (replace all <REPLACE> placeholders):

# Server
NODE_ENV=production
PORT=3001

# JWT β€” generate two different 64-char hex strings:
# node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
JWT_SECRET=<REPLACE_WITH_64_CHAR_HEX>
JWT_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d
ADMIN_JWT_SECRET=<REPLACE_WITH_DIFFERENT_64_CHAR_HEX>

# AWS β€” DynamoDB + S3 are in ap-south-1
AWS_REGION=ap-south-1
AWS_ACCESS_KEY_ID=<YOUR_AWS_ACCESS_KEY>
AWS_SECRET_ACCESS_KEY=<YOUR_AWS_SECRET_KEY>

# DynamoDB table names
DYNAMO_TABLE=civicpulse-dev-issues
DYNAMODB_ADMINS_TABLE=civicpulse-dev-admins
DYNAMODB_SITE_CONFIG_TABLE=civicpulse-dev-site-config
DYNAMODB_DONORS_TABLE=civicpulse-dev-donors
DYNAMODB_PUSH_SUBS_TABLE=civicpulse-dev-push-subscriptions

# S3
S3_BUCKET=civicpulse-dev-media

# Google OAuth
GOOGLE_CLIENT_ID=<YOUR_GOOGLE_OAUTH_CLIENT_ID>

# CORS β€” must match frontend domain exactly
CORS_ORIGIN=https://test.civicpulseindia.com

# Push Notifications (leave blank to disable)
VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
VAPID_EMAIL=mailto:modyyash@gmail.com

After saving, restart the backend:

pm2 restart civicpulse-backend --update-env
pm2 status # should show "online"

Phase 5 β€” Seed Superadmin​

cd /var/www/civicpulse/backend
SUPERADMIN_EMAIL=your@email.com \
SUPERADMIN_PASSWORD=YourStrongPass123! \
SUPERADMIN_NAME="Admin" \
node scripts/seed-superadmin.js

Verification Checklist​

  • https://test.civicpulseindia.com β€” landing page loads
  • https://test.civicpulseindia.com/admin β€” admin login page loads
  • https://test.civicpulseindia.com/api/health β€” returns {"status":"ok"}
  • https://test.civicpulseindia.com/docs/ β€” Docusaurus loads correctly
  • pm2 status shows online on the droplet
  • Google sign-in works in browser

Useful Commands (on Droplet)​

# Backend status
pm2 status

# Live backend logs
pm2 logs civicpulse-backend

# Restart backend with new env
pm2 restart civicpulse-backend --update-env

# Nginx error log
sudo tail -f /var/log/nginx/error.log

# Nginx access log
sudo tail -f /var/log/nginx/access.log

# Test nginx config
sudo nginx -t

# Reload nginx (after config change, no downtime)
sudo systemctl reload nginx

# Disk usage
df -h

# RAM + swap
free -h

# Renew TLS cert (runs automatically via cron β€” test it)
sudo certbot renew --dry-run

Updating the Deployment​

To deploy a new version:

cd /var/www/civicpulse

# Pull latest code
git pull origin master

# Rebuild frontend
cd frontend && npm ci && VITE_GA_MEASUREMENT_ID=G-H9SKTTDTRD npm run build
cd ..

# Rebuild docs (if docs changed)
cd docs && npm ci && npm run build
cd ..

# Restart backend
cd backend && npm ci --omit=dev
pm2 restart civicpulse-backend --update-env

# Reload nginx (no downtime)
sudo systemctl reload nginx

Common Problems​

ProblemFix
git clone prompts for passwordUse a GitHub PAT β€” see Phase 3 above
Build fails with OOMCheck free -h β€” the swap file should give 2 GB headroom. If missing: sudo fallocate -l 2G /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
Docusaurus shows "site did not load properly"nginx location priority bug β€” ensure location ^~ /docs/ (with ^~) is in the nginx config
PM2 not running after rebootRun pm2 startup and follow the command it prints, then pm2 save
Backend can't reach DynamoDBCheck AWS_REGION=ap-south-1 and DYNAMO_TABLE in .env, then pm2 restart ... --update-env
502 Bad Gateway on /api/PM2 is down β€” run pm2 restart civicpulse-backend
Port 80 not reachableCheck UFW: sudo ufw status β€” should allow Nginx Full