Deployment Pipeline

Image-based promotion through three environments on Fly.io. The same immutable Docker image moves from development to staging to production. See CI-CD Workflows for all workflow details.

Environments

EnvironmentFly AppTriggerDatabase
Developmentrenewa-appPush to mainrenewa-db (Fly.io managed)
Stagingrenewa-app-stagingManual promotionrenewa-db-staging
Productionrenewa-app-productionManual promotionrenewa-db-production
PR Previewrenewa-app-pr-{N}PR opened/updatedEphemeral per-PR database

All clusters are in the Frankfurt (fra) region.

Deployment Flow

  Push to main
       │
       ▼
  main-deploy.yml
  ├── Build Docker image
  ├── Push to Fly.io registry
  ├── Sync secrets (GitHub → Fly.io)
  └── Deploy to development
       │
       ▼ (manual trigger)
  promote-image.yml
  ├── Pull image from source environment
  ├── Tag for target environment
  └── Deploy to staging
       │
       ▼ (manual trigger)
  promote-image.yml
  └── Deploy to production

Promote command:

gh workflow run promote-image.yml \
  -f source_environment=development \
  -f target_environment=staging

Key Workflows

WorkflowFilePurpose
Deploy to devmain-deploy.ymlBuild + deploy on push to main
Promote imagepromote-image.ymlMove image between environments
PR previewpr-preview.ymlEphemeral app per PR
PR cleanuppr-cleanup.ymlDestroy preview on PR close
Security scansecurity-scan.ymlTrivy, Semgrep, Gitleaks, npm audit
Quality checksquality-checks.ymlLint, typecheck, tests
E2E testse2e-tests.ymlPlaywright end-to-end tests
Migration testsmigration-tests.ymlValidate Atlas migrations

See CI-CD Workflows for the full list of 20 workflows.

PR Preview Deployments

Each PR gets an isolated Fly.io app with its own database. See PR Preview Deployments for details.

  • App name: renewa-app-pr-{number}
  • Database: Created on first deploy, recreated only when schema/migration files change
  • Data: Persists across non-schema commits within the same PR
  • Cleanup: Automatic on PR close via pr-cleanup.yml

The ci-gate job in pr-preview.yml is the single required status check for merging. It validates that all upstream jobs passed or were skipped.

Fly.io Configuration

FileEnvironment
fly.development.tomlDevelopment
fly.staging.tomlStaging
fly.production.tomlProduction
fly.pr-preview.tomlPR previews

Docker Build

Multi-stage Dockerfile at renewa-one/Dockerfile:

  1. deps — Install Bun dependencies
  2. frontend-build — Vite production build
  3. backend-build — Bundle backend
  4. production — nginx + supervisord running nginx and backend

See Docker Setup for local development compose stack.

Secret Management

GitHub Environment Secrets are synced to Fly.io on every deploy using --stage flag.

Required secrets: DATABASE_URL, DATABASE_URL_MIGRATION, JWT_SECRET, FLY_API_TOKEN, ENCRYPTION_MASTER_KEY, TIGRIS_*, UPSTASH_REDIS_*, SENTRY_DSN_BACKEND, BIRD_API_KEY, HUBSPOT_CLIENT_SECRET.

Migration Strategy

ContextHow Migrations Run
Local / TestOn container startup
Cloud (dev/staging/prod)Via Fly.io release_command using DATABASE_URL_MIGRATION

See Database Migrations for the Atlas migration workflow.