R1.1 Relay production environment - #15
Merged
Merged
Conversation
This was referenced Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the code half of R1.1 (Relay production environment) from the community-base unification plan. Refs #13. The issue stays open for its owner-gated live checks.
What changed
scripts/deploy_relay_sandbox.shtakes--environment sandbox|production(defaultsandbox, plus--help). The sandbox path is behaviour-identical: same runtime env bootstrap, same containers, same rendered docker/git/curl commands. The production path changes only what themain/relayhost contract requires: no PostgreSQL container and no Caddy (RDS and the shared ALB instead), web container bound to0.0.0.0:8000, no inbound-mail ingress (production is outbound only), explicit--memoryon every container (2 GiB host), log group/relay/production/host,SECRET_KEYandDATABASE_URLfetched from Secrets Manager via the ARNs in/etc/relay/infrastructure.envand never printed, and no public health curl (private host; the release gate is the local readiness check, the per-container checks and the in-hostsystem.echosmoke test).ALLOWED_HOSTS,CSRF_TRUSTED_ORIGINS,DEFAULT_FROM_EMAIL,AWS_SES_REGIONandAWS_SES_CONFIGURATION_SETare never rewritten by the deploy;RELAY_SES_MAX_SEND_RATEis not set in production, so the application default (10/s) applies..github/workflows/deploy-prod.yml:workflow_dispatchonly, never on push.testjob (ruff, makemigrations check, Django check, pytest) gates thedeployjob, which runs under theproductionGitHub environment (owner configures it with a required reviewer), only frommain, and sends the same SSM deploy command as sandbox with--environment production. Needs the repository variableRELAY_PRODUCTION_INSTANCE_ID;RELAY_PRODUCTION_DEPLOY_ROLE_ARNdefaults to therelay-production-github-deployrole from themain/relayoutput.scripts/provision_production_tenant.sh: provisions organisationdatatalksclub, audiencedtc, clientdtc-website, senderhello=DataTalks.Club <hello@datatalks.club>(final sender address to be confirmed by the owner), and one API key namedproduction-deployment. The key is generated on the host into the0600runtime env file, following the sandboxRELAY_BOOTSTRAP_API_KEYmechanism; nothing prints or embeds key material (output shows only the key name and public prefix). The owner copies the key into the DTC website deployment secrets.--helpand--dry-runwork without credentials. The production deploy invokes the script (it is idempotent), so tenant state converges on every deploy and the first deploy's smoke test passes; it can also be run standalone on the host.docs/production.md: environment table, how a deploy works, tenant provisioning, on-call checks, rollback. Explicit that production is not deployed yet. README points at it.Terraform note
No aws-infra change accompanies this PR: the plan's Terraform half is already satisfied by the merged
main/relayroot in DataTalksClub/aws-infra. Its documented deviations from the plan issue text (no Elastic IP: private host behind the shared ALB; one SES-webhook ingress queue: inbound mail is out of scope; thedatatalks.clubSES identity, DKIM, SPF and DMARC owned bymain/commonandmain/website-static) are reported to the plan owner as a plan fix indocs/production.md; nothing is applied here.One deployment obligation remains open on the owner side and is listed in the docs:
main/relayschedules/usr/local/sbin/relay-prune-databasedaily at 03:30 UTC, but the application does not ship the underlying retention command yet, so the script cannot be installed by this deploy. The Secrets Manager value containers (database-url,django-secret-key) must also be populated out of band before the first deploy.Verification
uv run pytest-> pass:uv run ruff check .-> pass:Workflow YAML parses (
yaml.safe_loadon both):bash -non both scripts -> pass.--helpand--dry-runwork without credentials; the dry run prints the plan and never touches files.Sandbox-path equivalence: the old script (924ac93), the new script with default arguments, and the new script with explicit
--environment sandboxwere each executed under command stubs (docker, git, curl, openssl, aws) inside a user+mount namespace with tmpfs/etc,/opt,/var/lib. The rendered command logs are byte-identical:The same harness rendering
--environment productionconfirms the expected differences: no postgres/caddy/inbound containers or pulls,--memoryon all six app containers,gunicorn --bind 0.0.0.0:8000,awslogs-group=/relay/production/host, twosecretsmanager get-secret-valuecalls with no secret value in any output, the tenant provisioning commands, no public curl, and theRelay production deployed: <sha>final line.Not run here, needs: owner approved and applied the Terraform root and the first production deploy
datatalks.clubSES identity verified in the AWS console.curlsystem.echoagainst the production deployment.GET /internal/ops/statusfrom the production host.The plan issue's Done-when boxes are left unchecked for these owner-gated checks.