CI/CD & Environments
This guide covers the deployment pipeline, environment types, and automation workflows.
Environments
The monorepo 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 |
Environment Differences
| Aspect | Production | Integration | Ephemeral |
|---|---|---|---|
| DynamoDB removal | RETAIN | DESTROY | DESTROY |
| 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 | Yes | Yes | No |
| 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 when the Scalar project exists for that environment. Integration serves at
home-app-docs-int.dev.timeback.com. Production's intended host ishome-app-docs.dev.timeback.com; that Scalar project is not provisioned on this bootstrap ticket, so the production docs URL does not serve yet. 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, DynamoDB, Cognito, CloudFront). 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 (e.g., Slack notifications, Datadog dashboard upserts)
- Seeds ephemeral environments (copies users and DynamoDB data from integration)
- 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
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 main |
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, integration sync) - 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 |
not serving yet — intended home-app-docs.dev.timeback.com (Scalar project not provisioned on this ticket) |
| 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 | PR-specific CloudFront URL | PR-specific API Gateway URL | — |
Ephemeral environment URLs are output by the deploy step and posted as a PR comment.