Skip to content

Ongoing Deployment

Per-release workflow for QA, once Environment Spinup is complete. Pipeline details: RAG001 · Environments and CI. Current automation status for every claim below: canonical deployment plan.

  • QA: every merge to main triggers qa-deploy.yml automatically. No operator action, no manual terraform apply.
  • Prod: no automated pipeline exists yet. There is no approval gate to describe because there is nothing yet for it to gate — see the deployment plan.

Backend:

Terminal window
cd backend
uv sync --all-extras
uv run pytest
uv run ruff check .

Frontend:

Terminal window
cd frontend
npm install && npm test && npm run build

Terraform (if infra changed): from the repo root, make tf-qa-validate (or make tf-prod-validate), or cd infra/roots/$ENV && terraform fmt -check.

Push to a feature branch, open a PR against main. CI runs:

  • pytest with 90/70 coverage hard-fail (RAG006)
  • ruff linter
  • OpenAPI spec diff (RAG005)
  • terraform fmt/validate/tflint/module tests if infra/** changed
  • Infracost cost delta comment on the PR (informational, not blocking), from terraform.yml’s infracost-diff job. It prices both roots via --config-file infracost.yml, so QA and Prod appear as separate projects

Once checks pass and /dev-standards-review signs off, merge to main.

After merge, qa-deploy.yml runs automatically (Actions tab → QA Deploy):

  1. Lint, IaC security scan, test, secret scan
  2. Build & publish the Lambda deployment package (excludes ingestion-only deps; targets the Lambda runtime’s manylinux ABI, not the runner’s)
  3. Infracost diff (post-merge), baselining against the commit before the push and updating the cost comment on the merged PR. It prices infra/roots/qa only, where the pre-merge job in terraform.yml prices both roots. The plan job declares needs on it, so it runs before any apply
  4. Terraform plan → apply against infra/roots/qa
  5. Deploy Lambda code from the published package
  6. Build & deploy the frontend: npm run build against the just-applied api_endpoint, aws s3 sync dist --delete to the SPA bucket, then a CloudFront invalidation of /index.html and /
  7. Post-deploy smoke check

If the run fails, fix and push to main again.

Terminal window
API_ENDPOINT=<qa api_endpoint>
curl -sf "$API_ENDPOINT/healthz" | jq .
# Expected: {"status": "ok"}

Check the Actions log for the apply job’s Terraform outputs if you need cloudfront_domain or s3_bucket_name. Open the X-Ray console and confirm no error spikes in the service map.

qa-deploy.yml does not run migrations. If your change adds a migration file, apply it after the deploy with rag-migrate-dataapi (RDS Data API, RAG011) — see Environment Spinup, Phase 2 step 2 for the exact commands.

Lambda-only (immediate): point the alias back to the previous version:

Terminal window
FUNCTION_NAME=rag-sample-qa-api
PREV=$(($(aws lambda get-alias --function-name $FUNCTION_NAME --name qa \
--query 'FunctionVersion' --output text) - 1))
aws lambda update-alias --function-name $FUNCTION_NAME --name qa --function-version $PREV

This is generally safe: OpenAPI governance (RAG005) is a drift check — CI fails if the committed spec doesn’t match the generated one — which surfaces unreviewed API-surface changes, though it doesn’t itself enforce backwards compatibility. Confirm the specific change is compatible before relying on this rollback.

Full rollback (infrastructure + Lambda): revert the commit, push to main via PR. This re-triggers the pipeline.

Unexpected Terraform replacements (especially Aurora): stop immediately, review the plan output in the Actions log, confirm it’s intentional before letting apply proceed (or cancel the run).

Lambda zip too large: Lambda’s limit is 250 MB unzipped. Remove unused dependencies from pyproject.toml’s base dependencies (move ingestion-only deps into the ingestion extra so .[api] excludes them); consider Lambda Layers for anything unavoidably heavy.

Terraform Plan fails with an account-ID or state-lock error: see the deployment plan’s operational notes — a stale S3-native state lock from a cancelled run needs terraform force-unlock <id>; an allowed_account_ids mismatch means the AWS_QA_ACCOUNT_ID repo variable doesn’t match the target account.

Bootstrap-level change (IAM permissions, OIDC, state/Lambda buckets) doesn’t take effect: infra/bootstrap is a separate Terraform root that no workflow ever applies automatically. Re-run make tf-bootstrap-apply manually after merging any change under infra/bootstrap/ or infra/modules/cicd/.


Reference: Environments and CI pipeline · App repo CI workflows · Deployment plan