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 main |
| 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 |
CI/CD Workflows
Deployment Workflows
| Workflow | Trigger | Environment |
|---|---|---|
deploy-integration.yml |
Push to main |
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
monorepo-starter-docs.wseng.rp.devfactory.com, integration atmonorepo-starter-docs-int.wseng.rp.devfactory.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, DynamoDB, Cognito, CloudFront)
- 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 connects to its own database on the shared dev Postgres cluster, named dtch_ws_mono_st_<env> (apps/infra/app.ts). The deploy does not create the database:
-
Ephemeral:
deploy-ephemeral.ymlhas aCreate DB Schemastep that invokes the sharedwseng-db-helperLambda (create-ephemeral-schema, idempotent) before CDK deploy;destroy-ephemeral.ymlenqueues its deletion. -
Integration: the database is provisioned once during repo bootstrap, with the same Lambda:
aws lambda invoke \ --function-name arn:aws:lambda:us-east-1:856284715153:function:wseng-db-helper \ --payload '{"type":"create-ephemeral-schema","product":"WsMonoSt","name":"dtch_ws_mono_st_integration","user":"<DB_USERNAME from the integration secret>"}' \ --cli-binary-format raw-in-base64-out response.jsonIf the integration deploy fails at the
MigrationTriggercustom resource withdatabase "..." does not exist, this step is the fix. -
Production: the
wseng-db-helpercreate path only targets the dev cluster; production database provisioning is a separate, deliberate operation (see issue #138).
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
main 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 --env integration
# From a specific PR environment
pnpm script update-env --pr 20
# Wait for a stack that is still deploying
pnpm script update-env --env integration --wait
Accessing Deployed Environments
| Environment | Frontend URL | API URL | Docs URL |
|---|---|---|---|
| Production | https://monorepo.wseng.rp.devfactory.com |
https://api-monorepo.wseng.rp.devfactory.com |
https://monorepo-starter-docs.wseng.rp.devfactory.com |
| Integration | https://monorepo-integration.wseng.rp.devfactory.com |
https://api-monorepo-integration.wseng.rp.devfactory.com |
https://monorepo-starter-docs-int.wseng.rp.devfactory.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.