Deployment
UniPulse runs on a VPS (Ubuntu) using Docker Compose for the main application and PM2 for the Control Center.
Production Architecture
Deployment Steps
1. Build the Application
# On the VPS (or in CI/CD pipeline)
cd Pulse
npm install
npm run build
2. Deploy with Docker Compose
docker compose -f docker-compose.prod.yml up -d --build
This starts all production containers:
| Container | Health Check | Auto-Restart |
|---|---|---|
postgres | TCP 5432 | always |
redis | TCP 6379 | always |
api | GET /api/v1/health | always |
web | HTTP 200 | always |
certbot | - | On SSL renewal |
3. Run Database Migrations
docker compose -f docker-compose.prod.yml exec api npx prisma migrate deploy
Migration Order
Always run migrations after the containers are up but before sending traffic to the new version. Ensure the migration is backward-compatible with the previous API version during the transition.
4. Verify Deployment
# Check API health
curl https://app.unipulse.tech/api/v1/health
# Check container status
docker compose -f docker-compose.prod.yml ps
# Check logs
docker compose -f docker-compose.prod.yml logs -f api
# Check Control Center
curl https://ops.unipulse.tech
Control Center Deployment
The Control Center runs independently on PM2 (not Docker) so it survives container outages:
cd UniPulse-Control-Center
# First-time setup
./setup.sh
# Subsequent deployments
./deploy.sh
deploy.sh performs:
| Step | Command |
|---|---|
| Pull latest code | git pull origin main |
| Install dependencies | npm install |
| Build frontend + backend | npm run build |
| Run migrations | npx prisma migrate deploy |
| Restart PM2 | pm2 restart ecosystem.config.js |
PM2 Configuration
// ecosystem.config.js
module.exports = {
apps: [{
name: 'control-center',
script: './apps/server/dist/index.js',
max_memory_restart: '500M',
max_restarts: 10,
log_type: 'json',
env: {
NODE_ENV: 'production',
PORT: 3001,
},
}],
};
| Setting | Value | Purpose |
|---|---|---|
max_memory_restart | 500M | Auto-restart if memory exceeds 500MB |
max_restarts | 10 | Maximum restart attempts before stopping |
log_type | json | Structured JSON logging |
CI/CD Pipeline
GitHub Actions handles automated checks and deployment:
CI Checks (on PR)
| Check | Command | Blocks Merge |
|---|---|---|
| Lint | npm run lint | Yes |
| Type check | npm run typecheck | Yes |
| Build | npm run build | Yes |
CD (on merge to main)
| Step | Action |
|---|---|
| SSH to VPS | Using GitHub Secrets for SSH key |
| Pull latest | git pull origin main |
| Build | Docker Compose build |
| Migrate | prisma migrate deploy |
| Restart | docker compose restart |
| Health check | Verify /api/v1/health returns 200 |
Rollback Procedure
Quick Rollback (Docker)
# Stop current containers
docker compose -f docker-compose.prod.yml down
# Checkout previous version
git checkout <previous-tag>
# Rebuild and start
docker compose -f docker-compose.prod.yml up -d --build
# Re-apply migrations (if needed)
docker compose -f docker-compose.prod.yml exec api npx prisma migrate deploy
Control Center Rollback
cd UniPulse-Control-Center
git checkout <previous-tag>
npm install && npm run build
pm2 restart ecosystem.config.js
Database Rollback
Prisma Migrate does not support automatic rollback. If a migration needs to be reversed, you must write a new migration that undoes the changes. Always test migrations on staging first and back up the production database before deploying.
Monitoring Post-Deployment
| Check | How | Expected |
|---|---|---|
| API health | curl /api/v1/health | 200 OK |
| Container status | docker compose ps | All "running" |
| API logs | docker compose logs -f api | No errors |
| Queue processing | Admin dashboard or Redis CLI | Jobs being processed |
| Control Center | https://ops.unipulse.tech | Dashboard loads |
| SSL certificates | Browser padlock | Valid certificate |
| Database connectivity | Prisma Studio or API queries | Queries succeed |
SSL Certificate Management
# Initial certificate generation
docker compose -f docker-compose.prod.yml run --rm certbot certonly \
--webroot -w /var/www/certbot \
-d app.unipulse.tech
# Auto-renewal (add to crontab)
0 0 1 * * docker compose -f docker-compose.prod.yml run --rm certbot renew
Cross-Reference
- Docker -- container configuration
- Nginx -- reverse proxy setup
- Environment Variables -- production env config
- Control Center Setup -- Control Center deployment details
- Migrations -- database migration workflow