Skip to main content

Deployment Best Practices

This guide covers best practices for deploying Hyperscape to production, including maintenance mode coordination, health checks, and zero-downtime deployments.

Table of Contents

  1. Maintenance Mode
  2. Deployment Workflow
  3. Health Checks
  4. Rollback Procedures
  5. Security Checklist
  6. Monitoring

Maintenance Mode

Maintenance mode provides graceful deployment coordination for the streaming duel system, preventing data loss and market inconsistency during deployments.

When to Use Maintenance Mode

Required for:
  • Code deployments that restart the server
  • Database schema migrations
  • Configuration changes affecting duel system
  • Infrastructure maintenance (server moves, scaling)
Not required for:
  • Static asset updates (CDN only)
  • Client-only deployments (Cloudflare Pages)
  • Documentation updates

Maintenance Mode API

Authentication: All endpoints require ADMIN_CODE header:

Enter Maintenance Mode

Parameters:
  • reason (string): Reason for maintenance (logged for audit)
  • timeoutMs (number): Maximum wait time for markets to resolve (default: 300000 = 5 minutes)
Behavior:
  1. Pauses new duel cycles (current cycle completes)
  2. Locks betting markets (no new bets accepted)
  3. Waits for current market to resolve
  4. Returns when safe to deploy or timeout reached
Response:

Check Status

Response:
Safe to Deploy When:
  • safeToDeploy: true
  • currentPhase: "IDLE" (no active duel)
  • marketStatus: "resolved" (all markets settled)
  • pendingMarkets: 0

Exit Maintenance Mode

Response:
Behavior:
  • Resumes duel cycle scheduling
  • Unlocks betting markets
  • Normal operations resume

Helper Scripts

Pre-Deployment:
Post-Deployment:

CI/CD Integration

The Vast.ai deployment workflow (.github/workflows/deploy-vast.yml) automatically coordinates maintenance mode:
Workflow Steps:
  1. Enter maintenance mode (pauses new duels)
  2. Wait for active markets to resolve (up to 5 minutes)
  3. Deploy latest code via SSH
  4. Verify deployment health
  5. Exit maintenance mode (resumes operations)
Timeout Handling:
  • If markets don’t resolve within timeout, deployment proceeds anyway
  • Manual intervention may be required to resolve stuck markets
  • Check /admin/maintenance/status after deployment

Deployment Workflow

Railway (Production Server)

Automatic Deployment:
  • Push to main → deploys to prod environment
  • Push to develop → deploys to dev environment
Manual Deployment:
  1. Go to GitHub Actions → Deploy to Railway
  2. Select environment: prod or dev
  3. Click “Run workflow”
Environment Variables (set in Railway dashboard):
  • JWT_SECRET - Required (throws error if not set)
  • ADMIN_CODE - Required for security
  • DATABASE_URL - PostgreSQL connection string
  • PRIVY_APP_ID - Privy app ID
  • PRIVY_APP_SECRET - Privy app secret
  • PUBLIC_CDN_URL - Asset CDN URL (e.g., https://assets.hyperscape.club)
Post-Deployment:
  1. Check /health endpoint: https://hyperscape.gg/health
  2. Verify WebSocket: wss://hyperscape.gg/ws
  3. Monitor logs for errors
  4. Test character creation and login

Cloudflare Pages (Frontend)

Automatic Deployment:
  • Push to main → deploys to production
  • Pull requests → preview deployments
Manual Deployment:
Environment Variables (set in Cloudflare dashboard): Post-Deployment:
  1. Test asset loading (check Network tab for 404s)
  2. Verify WebSocket connection
  3. Test authentication flow
  4. Check for CORS errors in Console

Vast.ai (Streaming Duels)

Automatic Deployment:
  • Triggered on successful main branch builds
  • Includes maintenance mode coordination
  • Automatic health checks and recovery
Manual Deployment:
Post-Deployment:
  1. Check PM2 status: pm2 status
  2. Verify stream health: curl http://localhost:5555/health
  3. Check RTMP output: ffplay rtmp://your-rtmp-server/live/stream
  4. Monitor logs: pm2 logs

Health Checks

Server Health Endpoint

Response:
Health Indicators:
  • status: "healthy" - All systems operational
  • database: "connected" - PostgreSQL connection active
  • websocket: "active" - WebSocket server running
  • maintenance: false - Not in maintenance mode
  • streaming.active: true - Streaming duel system running

Vast.ai Health Checks

The Vast.ai keeper (packages/vast-keeper) automatically monitors instance health: Health Check Criteria:
  • HTTP /health endpoint responds with 200
  • Response time < 5 seconds
  • Database connection active
  • WebSocket server running
Failure Handling:
  1. Detect unhealthy instance (3 consecutive failures)
  2. Destroy failed instance
  3. Provision new instance
  4. Deploy latest code
  5. Resume operations
Configuration (.env):

Manual Health Checks

Server:
Streaming:

Rollback Procedures

Railway Rollback

Via Dashboard:
  1. Go to Railway dashboard → Deployments
  2. Find last known good deployment
  3. Click “Redeploy”
Via CLI:

Cloudflare Pages Rollback

Via Dashboard:
  1. Go to Cloudflare dashboard → Pages → hyperscape
  2. Click “Deployments” tab
  3. Find last known good deployment
  4. Click “Rollback to this deployment”
Via Git:

Vast.ai Rollback

Via SSH:
Via Keeper:

Security Checklist

Pre-Deployment

  • JWT_SECRET set and secure (32+ characters)
  • ADMIN_CODE set and not committed to git
  • PRIVY_APP_SECRET set and not exposed to client
  • Database credentials rotated (if compromised)
  • API tokens reviewed and scoped appropriately
  • Environment variables match between client and server
  • CORS configuration includes only known domains
  • Rate limiting enabled (DISABLE_RATE_LIMIT=false)

Post-Deployment

  • /health endpoint returns 200
  • Authentication flow works (Privy login)
  • Admin commands require ADMIN_CODE
  • CSRF protection active for same-origin requests
  • CORS errors not present in browser console
  • WebSocket connections establish successfully
  • Database migrations applied successfully
  • No sensitive data in client-side logs

Production Environment Variables

Required:
Recommended:
Generate Secrets:

Monitoring

Key Metrics

Server:
  • Response time (p50, p95, p99)
  • Error rate (4xx, 5xx)
  • WebSocket connections (active, total)
  • Database query time
  • Memory usage
  • CPU usage
Streaming:
  • RTMP uptime
  • FFmpeg restarts
  • CDP stall events
  • Frame rate (target: 30 FPS)
  • Bitrate (target: 2500 kbps)
Duel System:
  • Duel cycle duration
  • Market resolution time
  • Bet placement rate
  • Payout success rate

Logging

Server Logs:
Streaming Logs:

Alerts

Critical Alerts (configure ALERT_WEBHOOK_URL):
  • Server crash or restart
  • Database connection lost
  • WebSocket server down
  • Streaming pipeline failure
  • Maintenance mode timeout
Warning Alerts:
  • High error rate (> 5%)
  • Slow response time (> 1s p95)
  • Memory usage > 80%
  • FFmpeg restart (> 3 in 10 minutes)

Common Deployment Issues

JWT_SECRET Not Set

Symptom: Server throws error on startup in production/staging. Cause: JWT_SECRET is required as of February 2026 (security hardening). Solution:

CORS Errors After Deployment

Symptom: Assets fail to load with CORS errors in browser console. Cause: R2 bucket CORS not configured or domains missing. Solution:
  1. Run scripts/configure-r2-cors.sh (see docs/r2-cors-configuration.md)
  2. Verify all domains in AllowedOrigins list
  3. Wait 1-2 minutes for propagation
  4. Hard reload browser (Cmd+Shift+R / Ctrl+Shift+R)

Maintenance Mode Timeout

Symptom: Deployment proceeds but markets still active. Cause: Markets didn’t resolve within timeout (default 5 minutes). Solution:
  1. Check market status: GET /admin/maintenance/status
  2. Manually resolve stuck markets (if safe)
  3. Exit maintenance mode: POST /admin/maintenance/exit
  4. Monitor for data inconsistencies

Database Migration Failures

Symptom: Server fails to start after deployment with schema errors. Cause: Migration failed or partially applied. Solution:

WebSocket Connection Failures

Symptom: Clients can’t connect to WebSocket after deployment. Cause: WebSocket URL misconfigured or server not listening. Solution:
  1. Verify PUBLIC_WS_URL in client .env matches server domain
  2. Check server logs for WebSocket initialization errors
  3. Test WebSocket manually: wscat -c wss://hyperscape.gg/ws
  4. Verify Railway/Cloudflare WebSocket support enabled

Zero-Downtime Deployment

Strategy

  1. Blue-Green Deployment (Railway):
    • Deploy to new instance
    • Health check new instance
    • Switch traffic to new instance
    • Keep old instance for rollback
  2. Maintenance Mode Coordination (Vast.ai):
    • Enter maintenance mode
    • Wait for safe state
    • Deploy new code
    • Exit maintenance mode

Implementation

Railway (automatic):
  • Railway handles blue-green deployment automatically
  • Old instance kept for 30 seconds after new instance healthy
  • Traffic switches when new instance passes health checks
Vast.ai (manual coordination):

Database Migrations

Safe Migration Workflow

  1. Backup Database:
  2. Test Migration Locally:
  3. Apply to Production:
  4. Rollback if Needed:

Migration Best Practices

  • Always backup before migrations
  • Test locally with production data copy
  • Use transactions for multi-step migrations
  • Avoid breaking changes (add columns as nullable, deprecate instead of drop)
  • Monitor performance after migrations (check query plans)

Streaming Deployment

RTMP Configuration

Environment Variables (set in Vast.ai/Railway):
Verify Streaming:

Streaming Stability

Tuning Parameters (.env):
February 2026 Improvements:
  • Soft CDP Recovery: Restarts screencast without browser/FFmpeg teardown (no stream gap)
  • Increased Thresholds: CDP stall (2→4 intervals), FFmpeg restarts (5→8), recovery failures (2→4)
  • Best-Effort WebGPU: Tries maxTextureArrayLayers: 2048, retries with defaults if GPU rejects

Troubleshooting

Deployment Hangs

Symptom: Deployment stuck in “Building” or “Starting” state. Cause: Build timeout, dependency installation failure, or startup error. Solution:
  1. Check Railway/GitHub Actions logs for errors
  2. Verify bun install --frozen-lockfile succeeds locally
  3. Check for npm 403 errors (retry logic should handle)
  4. Increase build timeout in Railway settings (if needed)

Database Connection Errors

Symptom: Server starts but can’t connect to database. Cause: DATABASE_URL misconfigured or database not accessible. Solution:
  1. Verify DATABASE_URL format: postgresql://user:password@host:port/database
  2. Check database is running and accessible
  3. Test connection manually: psql $DATABASE_URL -c "SELECT 1"
  4. Verify firewall rules allow connections from server IP

Asset 404 Errors

Symptom: Models, textures, or audio fail to load with 404 errors. Cause: CDN URL misconfigured or assets not uploaded. Solution:
  1. Verify PUBLIC_CDN_URL in both client and server .env
  2. Check R2 bucket contains assets: aws s3 ls s3://hyperscape-assets/
  3. Upload missing assets: bun run assets:sync (see README)
  4. Verify CORS configuration (see docs/r2-cors-configuration.md)
  • R2 CORS: docs/r2-cors-configuration.md
  • Railway Setup: docs/railway-dev-prod.md
  • Native Releases: docs/native-release.md
  • Duel Stack: docs/duel-stack.md
  • Environment Variables: packages/server/.env.example, packages/client/.env.example

Deployment Checklist

Pre-Deployment

  • Code reviewed and tested locally
  • All tests passing (bun test)
  • Database backup created
  • Migration tested on copy of production data
  • Environment variables verified
  • Security checklist completed
  • Rollback plan documented

During Deployment

  • Maintenance mode entered (if applicable)
  • Safe state confirmed (safeToDeploy: true)
  • Deployment triggered
  • Health checks passing
  • Logs monitored for errors

Post-Deployment

  • /health endpoint returns 200
  • WebSocket connections working
  • Authentication flow tested
  • Asset loading verified
  • Database queries performing well
  • Streaming active (if applicable)
  • Maintenance mode exited
  • Monitoring alerts configured
  • Team notified of deployment

Emergency Contacts

Critical Issues:
  1. Check #hyperscape-alerts Slack channel
  2. Page on-call engineer via PagerDuty
  3. Rollback immediately if user-facing
Non-Critical Issues:
  1. Create GitHub issue with logs
  2. Post in #hyperscape-dev Slack channel
  3. Schedule fix for next deployment

Commit References

  • Maintenance Mode: 30b52bd (February 26, 2026)
  • CORS Configuration: 143914d (February 26, 2026)
  • Streaming Stability: 14a1e1b (February 25, 2026)
  • JWT Security: 3bc59db (February 26, 2026)