Deployment
This guide covers the deployment process for all components of the LMS system.
Overview
The LMS consists of multiple deployable components:
- Frontend - Static site deployment
- Backend - Node.js API server
- Cloud Functions - Cloudflare Workers
- Database - PostgreSQL database
- Documentation - VitePress static site
Prerequisites
- Node.js 20+
- pnpm package manager
- PostgreSQL database
- Cloudflare account (for Workers)
- Hosting provider account (Vercel, Netlify, AWS, etc.)
Environment Variables
Frontend (.env.production)
env
VITE_API_URL=https://api.yourdomain.com
VITE_CLOUDFLARE_URL=https://functions.yourdomain.comBackend (.env.production)
env
# Database
DATABASE_HOST=your-db-host.com
DATABASE_PORT=5432
DATABASE_USER=lms_user
DATABASE_PASSWORD=secure_password
DATABASE_NAME=lms_production
DATABASE_SSL=true
# JWT
JWT_SECRET=your-production-jwt-secret
# CORS
CORS_ORIGIN_1=https://yourdomain.com
CORS_ORIGIN_2=https://www.yourdomain.com
# Storage (Firebase)
FIREBASE_PROJECT_ID=your-project-id
FIREBASE_PRIVATE_KEY=your-private-key
FIREBASE_CLIENT_EMAIL=your-client-email
# Storage (Appwrite)
APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
APPWRITE_PROJECT_ID=your-project-id
APPWRITE_API_KEY=your-api-key
# AI (OpenAI)
OPENAI_API_KEY=your-openai-api-key
# Server
PORT=3000
NODE_ENV=productionCloud Functions
bash
# Use wrangler to set secrets
wrangler secret put JWT_SECRET
wrangler secret put DATABASE_URLBuilding for Production
Build All Packages
bash
# From root directory
pnpm buildThis builds:
- Frontend →
apps/client-frontend/dist - Backend →
apps/client-backend/dist - Cloud Functions →
apps/cloud-functions/dist - Documentation →
apps/docs/.vitepress/dist
Build Individual Components
bash
# Frontend
cd apps/client-frontend
pnpm build
# Backend
cd apps/client-backend
pnpm build
# Cloud Functions
cd apps/cloud-functions
pnpm deploy
# Documentation
cd apps/docs
pnpm buildDatabase Deployment
1. Provision PostgreSQL Database
Options:
- Managed Services: AWS RDS, Azure Database, Google Cloud SQL
- Database-as-a-Service: Supabase, Neon, Railway
- Self-hosted: EC2, DigitalOcean Droplets
2. Run Migrations
bash
# Set production database URL
export DATABASE_URL="postgresql://user:pass@host:5432/lms_prod"
# Run migrations
cd apps/client-backend
pnpm typeorm migration:run3. Seed Initial Data
bash
# Run seed script (if applicable)
node scripts/seed-production.jsFrontend Deployment
Vercel
bash
# Install Vercel CLI
pnpm add -g vercel
# Deploy
cd apps/client-frontend
vercel --prodvercel.json:
json
{
"buildCommand": "pnpm build",
"outputDirectory": "dist",
"framework": "vite",
"env": {
"VITE_API_URL": "@api-url"
}
}Netlify
bash
# Install Netlify CLI
pnpm add -g netlify-cli
# Deploy
cd apps/client-frontend
netlify deploy --prodnetlify.toml:
toml
[build]
command = "pnpm build"
publish = "dist"
[build.environment]
NODE_VERSION = "20"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200AWS S3 + CloudFront
bash
# Build
pnpm build
# Upload to S3
aws s3 sync dist/ s3://your-bucket-name --delete
# Invalidate CloudFront cache
aws cloudfront create-invalidation \
--distribution-id YOUR_DISTRIBUTION_ID \
--paths "/*"Docker
dockerfile
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install
COPY . .
RUN pnpm build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]bash
# Build and run
docker build -t lms-frontend .
docker run -p 80:80 lms-frontendBackend Deployment
Heroku
bash
# Install Heroku CLI
npm install -g heroku
# Login
heroku login
# Create app
heroku create lms-api
# Set environment variables
heroku config:set DATABASE_URL=postgresql://...
heroku config:set JWT_SECRET=your-secret
# Deploy
git push heroku mainProcfile:
web: node dist/index.jsRailway
bash
# Install Railway CLI
npm install -g @railway/cli
# Login
railway login
# Deploy
railway uprailway.json:
json
{
"build": {
"builder": "NIXPACKS",
"buildCommand": "pnpm build"
},
"deploy": {
"startCommand": "node dist/index.js",
"restartPolicyType": "ON_FAILURE"
}
}AWS EC2
bash
# Connect to instance
ssh -i key.pem ubuntu@your-instance-ip
# Install dependencies
sudo apt update
sudo apt install -y nodejs npm postgresql-client
# Clone repository
git clone https://github.com/your-repo/lms.git
cd lms
# Install pnpm
npm install -g pnpm
# Install dependencies
pnpm install
# Build
pnpm build
# Setup PM2
npm install -g pm2
pm2 start apps/client-backend/dist/index.js --name lms-api
pm2 save
pm2 startupDocker
dockerfile
# Dockerfile
FROM node:20-alpine
WORKDIR /app
# Install pnpm
RUN npm install -g pnpm
# Copy package files
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY apps/client-backend/package.json ./apps/client-backend/
COPY packages/*/package.json ./packages/*/
# Install dependencies
RUN pnpm install --frozen-lockfile --prod
# Copy source
COPY . .
# Build
RUN pnpm build
# Expose port
EXPOSE 3000
# Start server
CMD ["node", "apps/client-backend/dist/index.js"]bash
# Build and run
docker build -t lms-backend .
docker run -p 3000:3000 \
-e DATABASE_URL=postgresql://... \
-e JWT_SECRET=secret \
lms-backendDocker Compose
yaml
# docker-compose.yml
version: "3.8"
services:
database:
image: postgres:14
environment:
POSTGRES_USER: lms_user
POSTGRES_PASSWORD: lms_password
POSTGRES_DB: lms_prod
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
backend:
build: ./apps/client-backend
ports:
- "3000:3000"
environment:
DATABASE_HOST: database
DATABASE_PORT: 5432
DATABASE_USER: lms_user
DATABASE_PASSWORD: lms_password
DATABASE_NAME: lms_prod
JWT_SECRET: ${JWT_SECRET}
depends_on:
- database
frontend:
build: ./apps/client-frontend
ports:
- "80:80"
environment:
VITE_API_URL: http://localhost:3000
depends_on:
- backend
volumes:
postgres_data:bash
# Deploy
docker-compose up -dCloud Functions Deployment
Cloudflare Workers
bash
# Deploy to production
cd apps/cloud-functions
wrangler deploy
# Deploy to staging
wrangler deploy --env stagingwrangler.toml:
toml
name = "lms-cloud-functions"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[env.production]
name = "lms-cloud-functions-prod"
routes = [
{ pattern = "functions.yourdomain.com/*", zone_name = "yourdomain.com" }
]
[env.staging]
name = "lms-cloud-functions-staging"
routes = [
{ pattern = "functions-staging.yourdomain.com/*", zone_name = "yourdomain.com" }
]bash
# Set production secrets
wrangler secret put JWT_SECRET --env production
wrangler secret put DATABASE_URL --env productionDocumentation Deployment
GitHub Pages
bash
# Build documentation
cd apps/docs
pnpm build
# Deploy to GitHub Pages
# (Usually automated via GitHub Actions)GitHub Actions Workflow:
yaml
name: Deploy Docs
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
with:
node-version: 20
cache: "pnpm"
- run: pnpm install
- run: pnpm --filter docs build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: apps/docs/.vitepress/distVercel/Netlify
Same as frontend deployment, pointing to apps/docs.
CI/CD Pipeline
GitHub Actions
yaml
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
with:
node-version: 20
cache: "pnpm"
- run: pnpm install
- run: pnpm lint
- run: pnpm test
- run: pnpm build
deploy-frontend:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
with:
node-version: 20
- run: pnpm install
- run: pnpm --filter client-frontend build
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
working-directory: ./apps/client-frontend
deploy-backend:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
- run: pnpm install
- run: pnpm --filter client-backend build
- name: Deploy to Railway
run: |
npm install -g @railway/cli
railway up --service backend
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
deploy-cloudflare:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
- run: pnpm install
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
workingDirectory: apps/cloud-functionsMonitoring
Application Monitoring
- Sentry - Error tracking
- DataDog - APM and logging
- New Relic - Performance monitoring
Infrastructure Monitoring
- CloudWatch (AWS)
- Azure Monitor (Azure)
- Google Cloud Monitoring (GCP)
Uptime Monitoring
- UptimeRobot
- Pingdom
- StatusCake
SSL/TLS Certificates
Let's Encrypt (Free)
bash
# Install Certbot
sudo apt install certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Auto-renewal
sudo certbot renew --dry-runCloudflare SSL
- Add domain to Cloudflare
- Enable "Full (strict)" SSL mode
- Certificates automatically managed
Performance Optimization
Frontend
- Enable gzip/brotli compression
- Implement CDN caching
- Optimize images
- Code splitting
- Lazy loading
Backend
- Database connection pooling
- Redis caching
- Rate limiting
- API response compression
CDN
- CloudFlare
- AWS CloudFront
- Fastly
Backup Strategy
Database Backups
bash
# Automated daily backup
0 2 * * * pg_dump -h host -U user -d lms_prod > backup-$(date +\%Y\%m\%d).sql
# Backup to S3
aws s3 cp backup.sql s3://backups/lms/$(date +\%Y\%m\%d)/File Storage Backups
- Enable versioning on S3/Firebase Storage
- Regular snapshots
- Offsite backups
Rollback Strategy
Frontend
bash
# Revert to previous deployment
vercel rollback
# or
netlify rollbackBackend
bash
# Using PM2
pm2 stop lms-api
git checkout previous-commit
pnpm build
pm2 restart lms-api
# Using Docker
docker pull lms-backend:previous-tag
docker-compose up -dDatabase
bash
# Revert migration
typeorm migration:revertHealth Checks
Backend Health Endpoint
typescript
app.get("/health", (req, res) => {
res.json({
status: "ok",
timestamp: new Date().toISOString(),
uptime: process.uptime(),
});
});Kubernetes Liveness Probe
yaml
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10Scaling
Horizontal Scaling
- Load balancer (Nginx, HAProxy, AWS ALB)
- Multiple backend instances
- Session management (Redis)
- Database read replicas
Vertical Scaling
- Increase server resources
- Optimize database queries
- Improve caching
Security Checklist
- [ ] HTTPS enabled
- [ ] Environment variables secured
- [ ] Database credentials rotated
- [ ] API rate limiting enabled
- [ ] CORS properly configured
- [ ] Security headers set
- [ ] Input validation implemented
- [ ] SQL injection prevention
- [ ] XSS protection
- [ ] CSRF protection
Post-Deployment
- Verify deployments - Check all services are running
- Run smoke tests - Test critical functionality
- Monitor logs - Watch for errors
- Check metrics - Verify performance
- Update documentation - Document any changes
Troubleshooting
Frontend not loading
- Check API URL configuration
- Verify CORS settings
- Check browser console for errors
Backend crashes
- Check logs:
pm2 logs lms-api - Verify environment variables
- Check database connectivity
Database connection errors
- Verify credentials
- Check firewall rules
- Ensure SSL settings match
Related Documentation
- Getting Started - Development setup
- Architecture Overview - System architecture
- Testing - Testing before deployment