Operations, Deployment, and Maintenance Guide for IndiaTrades
- Deployment Overview
- Render Deployment
- Alternative Deployments
- Environment Configuration
- CI/CD Pipeline
- Monitoring & Logging
- Health Checks
- Scaling Strategies
- Disaster Recovery
- Maintenance Procedures
flowchart TB
subgraph Users["👥 Users"]
Browser["Web Browser"]
end
subgraph CDN["🌐 CDN / Edge"]
Cloudflare["Cloudflare (Optional)"]
end
subgraph Frontend["🖥️ Frontend Hosting"]
Render_Static["Render Static Site"]
Vercel["Vercel (Alternative)"]
end
subgraph Backend["⚙️ Backend Hosting"]
Render_Service["Render Web Service"]
end
subgraph Database["💾 Database"]
Supabase["Supabase PostgreSQL"]
end
subgraph External["🌍 External APIs"]
Yahoo["Yahoo Finance"]
RapidAPI["RapidAPI"]
end
Browser --> Cloudflare
Cloudflare --> Render_Static
Cloudflare --> Vercel
Render_Static --> Render_Service
Vercel --> Render_Service
Render_Service --> Supabase
Render_Service --> Yahoo
Render_Service --> RapidAPI
| Component | Primary | Alternative | Region |
|---|---|---|---|
| Frontend | Render Static | Vercel/Netlify | Singapore |
| Backend | Render Web Service | Railway/Heroku | Singapore |
| Database | Supabase | - | Singapore |
| DNS | Cloudflare | - | Global |
The project includes a render.yaml for automated deployment:
services:
# Backend Web Service
- type: web
name: india-trades-backend
runtime: node
region: singapore
rootDir: backend
buildCommand: npm install
startCommand: npm start
envVars:
- key: NODE_ENV
value: production
- key: SUPABASE_URL
sync: false # Set manually in dashboard
- key: SUPABASE_ANON_KEY
sync: false
# Frontend Static Site
- type: web
name: india-trades-live
runtime: static
rootDir: frontend
buildCommand: npm install && npm run build
staticPublishPath: dist
routes:
- type: rewrite
source: /*
destination: /index.html
headers:
- path: /*
name: Cache-Control
value: no-cache-
Go to Render Dashboard
-
Click New → Web Service
-
Connect your GitHub repository
-
Configure:
- Name:
india-trades-backend - Root Directory:
backend - Runtime:
Node - Build Command:
npm install - Start Command:
npm start - Region:
Singapore
- Name:
-
Add Environment Variables:
SUPABASE_URL=https://your-project.supabase.co SUPABASE_ANON_KEY=your-anon-key NODE_ENV=production -
Click Deploy
-
Copy the URL (e.g.,
https://india-trades-backend.onrender.com)
-
Click New → Static Site
-
Connect same repository
-
Configure:
- Name:
india-trades-live - Root Directory:
frontend - Build Command:
npm install && npm run build - Publish Directory:
dist
- Name:
-
Add Environment Variables:
VITE_API_URL=https://india-trades-backend.onrender.com VITE_SUPABASE_URL=https://your-project.supabase.co VITE_SUPABASE_ANON_KEY=your-anon-key -
Add Redirect Rule (for SPA routing):
/* → /index.html (200) -
Click Deploy
# Test backend health
curl https://india-trades-backend.onrender.com/api/quotes/RELIANCE
# Test frontend
curl -I https://india-trades-live.onrender.com-
Install Vercel CLI:
npm i -g vercel
-
Deploy:
cd frontend vercel --prod -
Set environment variables in Vercel dashboard
-
Install Railway CLI:
npm i -g @railway/cli
-
Deploy:
cd backend railway login railway init railway up
# backend/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 4000
CMD ["npm", "start"]# frontend/Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]# docker-compose.yml
version: '3.8'
services:
backend:
build: ./backend
ports:
- "4000:4000"
environment:
- NODE_ENV=production
- SUPABASE_URL=${SUPABASE_URL}
- SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
interval: 30s
timeout: 10s
retries: 3
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend| Variable | Backend | Frontend | Required | Description |
|---|---|---|---|---|
NODE_ENV |
✅ | - | Yes | development or production |
PORT |
✅ | - | No | Server port (default: 4000) |
SUPABASE_URL |
✅ | ✅ | Yes | Supabase project URL |
SUPABASE_ANON_KEY |
✅ | ✅ | Yes | Supabase anon key |
VITE_API_URL |
- | ✅ | Yes | Backend API URL |
RAPIDAPI_KEY |
✅ | - | No | Optional enhanced data |
- Go to Service → Environment
- Add secret with type "Secret"
- Reference in code:
process.env.MY_SECRET
- Go to Repository → Settings → Secrets
- Add repository secrets:
RENDER_API_KEYSUPABASE_URLSUPABASE_ANON_KEY
// config.js
const config = {
development: {
apiUrl: 'http://localhost:4000',
logLevel: 'debug'
},
production: {
apiUrl: process.env.VITE_API_URL,
logLevel: 'error'
}
};
export default config[process.env.NODE_ENV || 'development'];# .github/workflows/deploy.yml
name: Deploy to Render
on:
push:
branches: [main]
jobs:
deploy-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: cd backend && npm ci
- name: Run tests
run: cd backend && npm test
- name: Deploy to Render
run: |
curl -X POST \
-H "Authorization: Bearer ${{ secrets.RENDER_API_KEY }}" \
https://api.render.com/v1/services/${{ secrets.BACKEND_SERVICE_ID }}/deploys
deploy-frontend:
runs-on: ubuntu-latest
needs: deploy-backend
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: cd frontend && npm ci
- name: Build
run: cd frontend && npm run build
env:
VITE_API_URL: ${{ secrets.API_URL }}
- name: Deploy to Render
run: |
curl -X POST \
-H "Authorization: Bearer ${{ secrets.RENDER_API_KEY }}" \
https://api.render.com/v1/services/${{ secrets.FRONTEND_SERVICE_ID }}/deploysflowchart LR
subgraph Trigger["🎯 Trigger"]
Push["Push to main"]
end
subgraph Test["🧪 Test"]
Lint["Lint Code"]
UnitTest["Unit Tests"]
end
subgraph Build["🔨 Build"]
BackendBuild["Backend Build"]
FrontendBuild["Frontend Build"]
end
subgraph Deploy["🚀 Deploy"]
BackendDeploy["Deploy Backend"]
FrontendDeploy["Deploy Frontend"]
end
subgraph Verify["✅ Verify"]
HealthCheck["Health Check"]
SmokeTest["Smoke Test"]
end
Push --> Lint
Lint --> UnitTest
UnitTest --> BackendBuild
UnitTest --> FrontendBuild
BackendBuild --> BackendDeploy
FrontendBuild --> FrontendDeploy
BackendDeploy --> HealthCheck
FrontendDeploy --> HealthCheck
HealthCheck --> SmokeTest
// utils/logger.js
const LOG_LEVELS = {
error: 0,
warn: 1,
info: 2,
debug: 3
};
const currentLevel = LOG_LEVELS[process.env.LOG_LEVEL || 'info'];
export const logger = {
error: (...args) => console.error('[ERROR]', new Date().toISOString(), ...args),
warn: (...args) => currentLevel >= 1 && console.warn('[WARN]', new Date().toISOString(), ...args),
info: (...args) => currentLevel >= 2 && console.log('[INFO]', new Date().toISOString(), ...args),
debug: (...args) => currentLevel >= 3 && console.log('[DEBUG]', new Date().toISOString(), ...args)
};// middleware/requestLogger.js
export function requestLogger(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
method: req.method,
path: req.path,
status: res.statusCode,
duration: `${duration}ms`,
ip: req.ip
}));
});
next();
}| Metric | Threshold | Action |
|---|---|---|
| Response Time | > 2000ms | Investigate bottleneck |
| Error Rate | > 5% | Alert + investigate |
| WebSocket Connections | > 1000 | Consider scaling |
| Memory Usage | > 80% | Scale or optimize |
| CPU Usage | > 70% | Scale or optimize |
Access via Render Dashboard → Service → Metrics:
- CPU usage
- Memory usage
- Request count
- Response times
- Uptime Monitoring: UptimeRobot, Pingdom
- APM: New Relic, DataDog
- Error Tracking: Sentry
// Backend
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV
});
// Add as first middleware
app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.errorHandler());// routes/health.js
import express from 'express';
import { supabase } from '../supabaseClient.js';
const router = express.Router();
router.get('/', async (req, res) => {
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
checks: {}
};
// Check Supabase connection
try {
const { error } = await supabase.from('watchlist').select('count').limit(1);
health.checks.database = error ? 'unhealthy' : 'healthy';
} catch {
health.checks.database = 'unhealthy';
health.status = 'degraded';
}
// Check memory
const memUsage = process.memoryUsage();
health.checks.memory = {
heapUsed: `${Math.round(memUsage.heapUsed / 1024 / 1024)}MB`,
heapTotal: `${Math.round(memUsage.heapTotal / 1024 / 1024)}MB`
};
const statusCode = health.status === 'healthy' ? 200 : 503;
res.status(statusCode).json(health);
});
export default router;{
"status": "healthy",
"timestamp": "2024-12-26T10:00:00.000Z",
"uptime": 3600.5,
"checks": {
"database": "healthy",
"memory": {
"heapUsed": "64MB",
"heapTotal": "128MB"
}
}
}In Render Dashboard → Service → Settings:
Health Check Path: /health
| Plan | RAM | CPU | Use Case |
|---|---|---|---|
| Free | 256MB | Shared | Development |
| Starter | 512MB | 0.5 | Small traffic |
| Standard | 2GB | 1 | Production |
| Pro | 4GB | 2 | High traffic |
flowchart TB
subgraph LB["Load Balancer"]
Nginx["Nginx / Render LB"]
end
subgraph Instances["Backend Instances"]
Node1["Instance 1"]
Node2["Instance 2"]
Node3["Instance 3"]
end
subgraph Shared["Shared State"]
Redis["Redis (Session/Cache)"]
Supabase["Supabase DB"]
end
Nginx --> Node1
Nginx --> Node2
Nginx --> Node3
Node1 --> Redis
Node2 --> Redis
Node3 --> Redis
Node1 --> Supabase
Node2 --> Supabase
Node3 --> Supabase
// For multi-instance Socket.io
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
io.adapter(createAdapter(pubClient, subClient));| Data | Frequency | Retention | Method |
|---|---|---|---|
| Database | Daily | 7 days | Supabase automatic |
| Database | Weekly | 30 days | Manual export |
| Code | Every push | Unlimited | GitHub |
- Go to Supabase Dashboard → Database → Backups
- Select backup point
- Click "Restore"
- Go to Render Dashboard → Service
- Click "Rollback" to previous deploy
- Or redeploy from known-good commit
flowchart TB
Incident["🚨 Incident Detected"]
Assess["📋 Assess Impact"]
Mitigate["🛡️ Mitigate"]
Communicate["📢 Communicate"]
Resolve["✅ Resolve"]
PostMortem["📝 Post-Mortem"]
Incident --> Assess
Assess --> Mitigate
Mitigate --> Communicate
Communicate --> Resolve
Resolve --> PostMortem
- Review and rotate API keys
- Update dependencies (
npm update) - Review security advisories (
npm audit) - Check database indexes
- Review error logs
- Update documentation
# Check outdated packages
npm outdated
# Update patch versions
npm update
# Check security vulnerabilities
npm audit
# Fix vulnerabilities
npm audit fix-- Check table sizes
SELECT
tablename,
pg_size_pretty(pg_total_relation_size(tablename::text)) as size
FROM pg_tables
WHERE schemaname = 'public';
-- Vacuum tables
VACUUM ANALYZE watchlist;
VACUUM ANALYZE transactions;
-- Reindex if needed
REINDEX TABLE watchlist;- Deploy new version to staging
- Run smoke tests
- Deploy to production with health checks
- Monitor metrics for 15 minutes
- Keep previous version ready for rollback
- All secrets in environment variables
- No hardcoded credentials
- HTTPS enforced
- CORS configured correctly
- Rate limiting enabled
- Input validation on all endpoints
- SSL certificate valid
- Headers security (CSP, X-Frame-Options)
- API returns appropriate error messages
- Logging doesn't expose sensitive data
- Monthly dependency audit
- Quarterly security review
- Annual penetration testing