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):

  1. Lint — pnpm check --affected (lint + type-check + format across affected workspaces)
  2. Unit Tests — pnpm test across workspaces (with coverage)
  3. DB Integration Tests — backend repository tests against a Postgres service container
  4. Deployability — runs the domain/cert/quota preflight (preflightCustomDomain) then cdk synth for the PR's ephemeral stack, via deployability.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.
  5. 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)
  1. frontend-build — Installs deps, builds Vite app, uploads artifact
  2. infrastructure — Runs the deploy orchestration script (see Deploy Orchestration below)
  3. frontend-upload — Downloads build artifact, syncs to S3, invalidates CloudFront
  4. 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 at home-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:

  1. Seed reservations — provisions shared account-level resources (CloudFront origin request policies) and writes SSM reservations to protect them from concurrent cleanup
  2. Synth — runs cdk synth to produce the CloudFormation template (separate step because pre-deploy reads the synthesized template)
  3. Pre-deploy (pre-deploy.hook.ts) — imports orphaned CloudWatch log groups so CDK can adopt them
  4. 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 reads SENTRY_DSN from that deploy's secret (deploy-integration.yml uses the integration secret; deploy-production.yml uses the production secret). Ephemeral PR stacks omit those behaviors by design (resolveFrontendAnalyticsProxyProps).
  5. 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 seedEphemeralEnvironment in post-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-env and 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:

  1. Pre-destroy (pre-destroy.hook.ts) — runs any pre-destroy validation
  2. CDK destroy — tears down the CloudFormation stack
  3. 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 in packages/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:

  1. Developer merges a PR into master
  2. deploy-integration.yml triggers automatically
  3. It calls base-deploy.yml with environment: integration
  4. frontend-build compiles the React app with Vite
  5. 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)
  6. frontend-upload syncs the built assets to S3 and invalidates the CloudFront cache
  7. 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.