Skip to main content

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:

ContainerHealth CheckAuto-Restart
postgresTCP 5432always
redisTCP 6379always
apiGET /api/v1/healthalways
webHTTP 200always
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:

StepCommand
Pull latest codegit pull origin main
Install dependenciesnpm install
Build frontend + backendnpm run build
Run migrationsnpx prisma migrate deploy
Restart PM2pm2 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,
},
}],
};
SettingValuePurpose
max_memory_restart500MAuto-restart if memory exceeds 500MB
max_restarts10Maximum restart attempts before stopping
log_typejsonStructured JSON logging

CI/CD Pipeline

GitHub Actions handles automated checks and deployment:

CI Checks (on PR)

CheckCommandBlocks Merge
Lintnpm run lintYes
Type checknpm run typecheckYes
Buildnpm run buildYes

CD (on merge to main)

StepAction
SSH to VPSUsing GitHub Secrets for SSH key
Pull latestgit pull origin main
BuildDocker Compose build
Migrateprisma migrate deploy
Restartdocker compose restart
Health checkVerify /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

CheckHowExpected
API healthcurl /api/v1/health200 OK
Container statusdocker compose psAll "running"
API logsdocker compose logs -f apiNo errors
Queue processingAdmin dashboard or Redis CLIJobs being processed
Control Centerhttps://ops.unipulse.techDashboard loads
SSL certificatesBrowser padlockValid certificate
Database connectivityPrisma Studio or API queriesQueries 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