Deployment Guide
Branch Modelβ
| Branch | Environment | URL | CI Deploy | Protected |
|---|---|---|---|---|
master | production | civicpulseindia.com | β on push | Yes β PR + CI required |
develop | test | test.civicpulseindia.com | β on push | Yes β PR + CI required |
feature/* | local | localhost:5174 | β | No |
hotfix/* | production | both (sequential PRs) | β | No |
Workflow:
- Develop features on
feature/your-featureβ PR intodevelop developauto-deploys totest.civicpulseindia.comβ verify there- When ready to release: PR
developβmasterβ auto-deploys to production - For emergency fixes: branch
hotfix/descriptionfrommaster, PR into bothmaster+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:
| Service | Cost |
|---|---|
| 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β
- DigitalOcean dashboard β Create β Droplets
- Image: Ubuntu 22.04 LTS
- Size: 2 GB RAM / 1 vCPU (
$12/moβ Basic plan, Bangalore regionBLR1) - Authentication: SSH Key β add your public key
- Create Droplet β note the public IPv4 address (e.g.
159.65.155.22)
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:
| Type | Host | Value |
|---|---|---|
A | test | Your 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 no longer accepts passwords for git operations. Use a PAT as the password:
- github.com/settings/tokens β Generate new token (classic)
- Note:
civicpulse-deploy, Expiration: 30 days, Scope: β repo - 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 cloneinto/var/www/civicpulsenpm ci+ Vite build βfrontend/dist/npm ci+ Docusaurus build βdocs/build/- PM2 start +
pm2 startupfor 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 statusshowsonlineon 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β
| Problem | Fix |
|---|---|
git clone prompts for password | Use a GitHub PAT β see Phase 3 above |
| Build fails with OOM | Check 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 reboot | Run pm2 startup and follow the command it prints, then pm2 save |
| Backend can't reach DynamoDB | Check 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 reachable | Check UFW: sudo ufw status β should allow Nginx Full |