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.
Deployment model
Section titled “Deployment model”- QA: every merge to
maintriggersqa-deploy.ymlautomatically. No operator action, no manualterraform 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.
1. Test locally
Section titled “1. Test locally”Backend:
cd backenduv sync --all-extrasuv run pytestuv run ruff check .Frontend:
cd frontendnpm install && npm test && npm run buildTerraform (if infra changed): from the repo root, make tf-qa-validate (or make tf-prod-validate), or cd infra/roots/$ENV && terraform fmt -check.
2. Create a PR and merge
Section titled “2. Create a PR and merge”Push to a feature branch, open a PR against main. CI runs:
pytestwith 90/70 coverage hard-fail (RAG006)rufflinter- OpenAPI spec diff (RAG005)
terraform fmt/validate/tflint/module tests ifinfra/**changed- Infracost cost delta comment on the PR (informational, not blocking), from
terraform.yml’sinfracost-diffjob. 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.
3. Monitor the QA deployment
Section titled “3. Monitor the QA deployment”After merge, qa-deploy.yml runs automatically (Actions tab → QA Deploy):
- Lint, IaC security scan, test, secret scan
- Build & publish the Lambda deployment package (excludes ingestion-only deps; targets the Lambda runtime’s manylinux ABI, not the runner’s)
- Infracost diff (post-merge), baselining against the commit before the push and updating the cost comment on the merged PR. It prices
infra/roots/qaonly, where the pre-merge job interraform.ymlprices both roots. Theplanjob declaresneedson it, so it runs before any apply - Terraform plan → apply against
infra/roots/qa - Deploy Lambda code from the published package
- Build & deploy the frontend:
npm run buildagainst the just-appliedapi_endpoint,aws s3 sync dist --deleteto the SPA bucket, then a CloudFront invalidation of/index.htmland/ - Post-deploy smoke check
If the run fails, fix and push to main again.
4. Post-deployment verification
Section titled “4. Post-deployment verification”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.
5. Schema migrations, if any
Section titled “5. Schema migrations, if any”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.
Rollback
Section titled “Rollback”Lambda-only (immediate): point the alias back to the previous version:
FUNCTION_NAME=rag-sample-qa-apiPREV=$(($(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 $PREVThis 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.
Troubleshooting
Section titled “Troubleshooting”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
© 2026 Benjamin Arunski