PR Preview Deployments
Each pull request gets an isolated Fly.io preview app with its own ephemeral database.
How It Works
- PR opened/updated (non-draft only)
pr-preview.ymldetect-changesjob decides which jobs run (app code vs docs-only vs schema changes)- Builds PR-specific Docker image (tag:
pr-<number>) - Creates Fly.io app
renewa-app-pr-{number}(APP_ENV=preview) - Provisions the ephemeral database via the
manage-pr-databasecomposite action - Fetches app secrets from Infisical via OIDC and stages them with
flyctl secrets set --stage - Deploys, runs e2e tests (after a blocking warmup), posts preview URL as PR comment
Workflow Files
| Workflow | Trigger | Purpose |
|---|---|---|
.github/workflows/pr-preview.yml | PR open/sync | Orchestrator: build + deploy + reusable CI workflows + ci-gate |
.github/workflows/pr-cleanup.yml | PR close | Destroy preview app + database + Tigris objects + Entra redirect URI |
.github/workflows/cleanup-orphaned-previews.yml | Daily 02:00 UTC | Remove stale preview apps |
The legacy config-sync step in cleanup was decommissioned and Infisical sourcing fixed in PR#1968.
Secrets
App secrets come from Infisical EU (pr-preview scope), fetched in CI via GitHub OIDC (Infisical/secrets-action, no long-lived tokens) and applied per preview app with flyctl secrets set --stage. Only CI-infra credentials (e.g. FLY_API_TOKEN) remain GitHub Actions secrets. Unlike development/staging/production, preview apps are NOT covered by Infisical’s native Fly sync.
Database Behavior
- Created on first deploy for the PR (
renewa-app-pr-{number}) - Recreated only when schema/migration files change (
detect-changesfilters onbackend/src/db/schema.ts,backend/src/db/schema/**, and migration files) - Data persists across non-schema commits (faster deploys)
manage-pr-databaseaction supportscreate,verify,reset,create-if-missing- Uses the minimal mock dataset (3 users, 3 buildings, 5 projects, essential reference data) for startup in <30s; departments/employees come from the Entra sync
E2E Warmup Gate
Fly preview machines auto-stop when idle (auto_stop_machines = true, min_machines_running = 0), so e2e re-runs hit a cold app. .github/scripts/e2e-warmup.sh (I#1947, PR#1949) blocks until health is up AND a bot login round-trips, or fails the job with an explicit infrastructure error so no Playwright specs run cold. The script itself is tested by the ci-e2e-warmup-script job in quality-checks.yml.
Fly.io Configuration
Preview apps use a dedicated config: renewa-one/fly.pr-preview.toml
Key differences from production:
- Smaller VM size, single region (Frankfurt)
auto_stop_machineswith zero minimum running- Ephemeral storage only; Fly HTTP compression disabled (nginx handles it)
Required Status Check
The ci-gate job in pr-preview.yml is the single required check for merging. It validates all upstream jobs (quality-checks, security-scanning, migration-validation, build, deploy-preview, e2e-tests, container-security-scan) passed or were correctly skipped. See CI-CD Workflows.
HubSpot OAuth
The hubspot-app-update job (and scripts/manage-hubspot-pr-preview.sh) keeps the preview app’s redirect URI registered with HubSpot, mirroring the fixed local nginx port slots (see Docker Setup).
Promotion Path
Preview deployments are independent from the promotion pipeline:
PR Preview (pr-<number>) -- ephemeral, per-PR
Development (main-<sha>) -- auto-deploy on merge
Staging -- manual promotion
Production -- manual promotion
Key Files
.github/workflows/pr-preview.yml.github/workflows/pr-cleanup.yml.github/actions/manage-pr-database/.github/scripts/e2e-warmup.shrenewa-one/fly.pr-preview.toml
See Also
- CI-CD Workflows — all workflow definitions
- Deployment Pipeline — production promotion flow
- Mock and Config Data — minimal mock dataset
- Database Migrations — schema change detection
- Git Workflow — PR creation and branch rules