CI/CD & Environments
This guide covers the deployment pipeline, environment types, and automation workflows for TimeBack Home.
Environments
TimeBack Home supports three environment types:
| Type | Name Pattern | Purpose | Lifecycle |
|---|---|---|---|
| Production | production |
Live user-facing environment | Manual deploy via workflow_dispatch |
| Integration | integration |
Staging / continuous integration | Auto-deploys on merge to master |
| Ephemeral | pr0001–pr9999 |
Per-PR preview environments | Auto-created on PR, destroyed on close |
CloudFormation stack names follow tb-home-app-{env}-stack (for example tb-home-app-integration-stack, tb-home-app-pr0012-stack).
Environment Differences
| Aspect | Production | Integration | Ephemeral |
|---|---|---|---|
| Log retention | 1 year | 2 weeks | 2 weeks |
| Lambda minification | Yes | No | No |
| Source maps | No | Yes | Yes |
| Self-signup | No | No | Yes |
| SSO sign-in | Not shipped | Not shipped | Not shipped |
| Assets bucket | Yes | Yes | No |
| Data seeding | — | — | Copied from integration |
| Docs publishing | Yes (canonical) | Yes (staging preview) | No |
Analytics proxy (/events/mp*, /events/st*) |
Mixpanel when the Mixpanel origin-request-policy ref is in synth; Sentry /events/st* when that environment's secret includes SENTRY_DSN at synth |
Same as production column | No (skipped: CloudFront Function quota) |
CI/CD Workflows
Deployment Workflows
| Workflow | Trigger | Environment |
|---|---|---|
deploy-integration.yml |
Push to master, workflow_dispatch |
integration |
deploy-production.yml |
Manual (workflow_dispatch) |
production |
deploy-ephemeral.yml |
PR opened/updated | ephemeral (per-PR) |
destroy-ephemeral.yml |
PR closed | Destroys ephemeral stack |
Verification Workflow (verify.yml)
Runs on every PR (ready for review / synchronize):
- Lint —
pnpm check --affected(lint + type-check + format across affected workspaces) - Unit Tests —
pnpm testacross workspaces (with coverage) - DB Integration Tests — backend repository tests against a Postgres service container
-
Deployability — runs the domain/cert/quota preflight (
preflightCustomDomain) thencdk synthfor the PR's ephemeral stack, viadeployability.orchestrator.ts. This catches CDK synthesis errors and deploy-invariant violations on the PR, so a green pipeline means "safe to deploy," not just "compiles and passes unit tests." It reuses the deploy orchestrator's fail-fast opening but does not seed reservations, run hooks, or deploy; a synth error that only manifests once shared policy refs are present is not caught here. - JIRA Ticket Check — Validates PR title/branch naming
Deployment Pipeline (base-deploy.yml)
The reusable deployment workflow runs these jobs:
frontend-build ──┐
├──► frontend-upload
infrastructure ──┘
│
└──► publish-docs (production + integration only)
- frontend-build — Installs deps, builds Vite app, uploads artifact
- infrastructure — Runs the deploy orchestration script (see Deploy Orchestration below)
- frontend-upload — Downloads build artifact, syncs to S3, invalidates CloudFront
- publish-docs — Publishes Scalar documentation (production and integration only)
Docs Publishing: Documentation is published for production and integration only. Each environment has its own Scalar project and custom domain: production at
home-app-docs.dev.timeback.com, integration athome-app-docs-int.dev.timeback.com. The API reference automatically points to the environment's own API, so reviewers can test endpoints directly from the docs. Ephemeral (PR) environments do not publish docs.
Deploy Orchestration
The PR-time Deployability check (in verify.yml) reuses this orchestration's fail-fast opening — the preflight plus cdk synth — without the seed, deploy, and hook steps, so synth failures surface on the PR instead of waiting for a deploy cycle.
The infrastructure job runs pnpm run deploy:<type> from apps/infra/, which invokes deploy.orchestrator.ts. This orchestrator runs the full deploy lifecycle in a single process:
- Seed reservations — provisions shared account-level resources (CloudFront origin request policies) and writes SSM reservations to protect them from concurrent cleanup
- Synth — runs
cdk synthto produce the CloudFormation template (separate step because pre-deploy reads the synthesized template) - Pre-deploy (
pre-deploy.hook.ts) — imports orphaned CloudWatch log groups so CDK can adopt them - CDK deploy — applies the CloudFormation changeset (Lambda, API Gateway, Cognito, CloudFront, S3, IAM). Non-ephemeral frontends may gain a Mixpanel
/events/mp*behavior when synth has the Mixpanel origin-request-policy ref, and a Sentry/events/st*behavior when synth readsSENTRY_DSNfrom that deploy's secret (deploy-integration.ymluses the integration secret;deploy-production.ymluses the production secret). Ephemeral PR stacks omit those behaviors by design (resolveFrontendAnalyticsProxyProps). - Post-deploy (
post-deploy.hook.ts):- Publishes environment config JSON to S3 (used by frontend for environment switching)
- Runs optional external integrations via the integration registry
- Seeds ephemeral environments on first deploy (copies Cognito users from integration only; see
seedEphemeralEnvironmentinpost-deploy.hook.ts). Postgres is not wiped or reseeded during deploy. Use Environment Reset for an explicit Postgres wipe plus Cognito reseed. - Seeds the API and E2E test users (ephemeral and integration; never production) and stores their credentials in SSM, where
pnpm script update-envand the post-deploy test jobs pick them up
Deploy permissions for the OIDC role are documented in Deploy Role Permissions.
Postgres Database Provisioning
Each environment uses a database named tb_home_app_<env> on the product-owned Aurora cluster tb-home-app-dev. The CDK stack does not create RDS. The operator script pnpm script provision-environment --action provision creates the cluster. The migration runner creates each environment database with ensureDatabaseExists on first migrate.
- Ephemeral and integration: first migrate creates
tb_home_app_<env>in-VPC. - Production: provision a production cluster in a later ticket. This bootstrap writes a production secret without
DB_*keys.
Destroy Orchestration
When a PR is closed, destroy-ephemeral.yml runs pnpm run destroy:ephemeral from apps/infra/, which invokes destroy.orchestrator.ts:
- Pre-destroy (
pre-destroy.hook.ts) — runs any pre-destroy validation - CDK destroy — tears down the CloudFormation stack
- Post-destroy (
post-destroy.hook.ts):- Deletes S3 environment configs
- Removes SSM parameters
- Cleans up orphaned CloudWatch log groups from CDK custom resources
- Runs optional integration cleanup (e.g., revokes external resources registered by integrations)
- Releases and sweeps SSM reservations for shared resources
Post-destroy is intentionally skipped when CDK destroy fails — the stack still exists, so cleaning up reservations or shared policies would be incorrect. On failure, the workflow opens a GitHub issue for manual intervention.
Note: Manual API calls can be made via the Scalar interactive docs (
/docs/viewer) or by running the auto-generated tests inpackages/api-tests/.
Automation Workflows
| Workflow | Schedule | Purpose |
|---|---|---|
auto-update-branches.yml |
Every 30 min | Keeps feature branches up-to-date with master |
auto-release-accepted.yml |
Every 15 min | Auto-releases accepted PRs |
review-pr.yml |
On PR events | Automated PR review |
Deployment Flow (End-to-End)
A typical integration deployment follows this sequence:
- Developer merges a PR into
master deploy-integration.ymltriggers automatically- It calls
base-deploy.ymlwithenvironment: integration - frontend-build compiles the React app with Vite
- infrastructure runs the deploy orchestration script (
deploy:integration), which seeds account-level resources, synthesizes, runs pre-deploy hooks, deploys the CDK stack, and runs post-deploy hooks (environment config publish, ephemeral seeding) - frontend-upload syncs the built assets to S3 and invalidates the CloudFront cache
- publish-docs generates and publishes Scalar documentation with the latest API spec
For PR environments, the same pipeline runs with environment: prNNNN (the environment type is derived automatically from the name), but the publish-docs job is skipped (docs are only published for production and integration).
Managing Environments
Syncing Local .env from a Deployed Environment
# From the integration environment
pnpm script update-env integration
# From a specific PR environment
pnpm script update-env 20
# Wait for a stack that is still deploying
pnpm script update-env integration --wait
All also accept the target via --environment (aliases: --env, --pr).
Accessing Deployed Environments
| Environment | Frontend URL | API URL | Docs URL |
|---|---|---|---|
| Production | https://home-app.dev.timeback.com |
https://api-home-app.dev.timeback.com |
https://home-app-docs.dev.timeback.com |
| Integration | https://home-app-integration.dev.timeback.com |
https://api-home-app-integration.dev.timeback.com |
https://home-app-docs-int.dev.timeback.com |
| Ephemeral | https://home-app-prNNNN.dev.timeback.com |
https://api-home-app-prNNNN.dev.timeback.com |
— |
Ephemeral environment URLs are output by the deploy step and posted as a PR comment.