chore(trigger): sync Trigger.dev env vars from the app's Secrets Manager secret, gated per environment

Merged
WA
6d ago
+320-18

Summary

  • Trigger.dev deploys can now sync the target environment from the app's env-vars secret in Secrets Manager instead of relying on hand-copied variables
  • Off by default: a deploy syncs only when its environment slug (prod, staging, preview) is listed in the TRIGGER_ENV_SYNC_TARGETS repo variable, and only then does the job assume the deploy role for Secrets Manager. Nothing changes until that variable is set
  • Each slug maps to exactly one app secret in code, so a deploy can't sync one environment's config into another
  • Trigger-owned keys are never synced, so whatever value Trigger already holds stays: AWS_* (runs use workload identity, so static keys never go back in), REDIS_URL/REDIS_TLS_SERVERNAME (workers reach Redis through their own endpoint), INTERNAL_API_BASE_URL, MIGRATION_DATABASE_URL, OTEL_EXPORTER_OTLP_*, TRIGGER_*, GITHUB_TOKEN. DB_APP_NAME/SIM_DB_ROLE are code constants
  • Synced app values are stored as secrets, except NEXT_PUBLIC_*
  • The sync only upserts. Variables it doesn't return are left alone, and it never deletes anything
  • A failure reading the secret exits the deploy. syncEnvVars (4.8.3) catches callback errors and deploys without syncing, so throwing isn't enough. The CI log shows counts only, never names or values
  • With sync disabled, the deploy sends the shared constants (DB_APP_NAME, now also SIM_DB_ROLE=trigger, which every environment already holds, so the CLI's unchanged-value check skips it) plus the function-execution vars as before. The TRIGGER_DEV_ENABLED entry is dropped: syncEnvVars strips every TRIGGER_* key, so it never synced
  • Adds plan-trigger-env-sync.ts, a names-only dry run of what enabling an environment would add or change. --apply-secret-flags marks unchanged plain-text variables as secrets, re-reading before each batch so a value rotated mid-run is skipped rather than overwritten: the CLI skips unchanged values, so isSecret alone never converts an existing variable
  • Extracts fetchRuntimeSecrets from loadRuntimeSecrets so the boot path and the deploy read the secret the same way

Type of Change

  • Chore / infrastructure

Testing

  • Read the pinned @trigger.dev/build/trigger.dev 4.8.3 source and the webapp import route to confirm the semantics: upsert-only, isSecret flips only on changed values, sync runs before deployment init, callback errors are swallowed
  • Ran the dry-run script against all three environments, comparing values by hash only
  • Exercised the callback's success path, unmapped-environment exit, and missing-credentials exit
  • packages/runtime-secrets tests; biome; check-source-conventions, check-file-names, check-import-specifiers, check-monorepo-boundaries, check-comment-hygiene, check-utils-enforcement, check-explicit-any. Full type-check and audits run in CI

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing (new tests pass the test-audit authoring gate)
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)