Terraform (AWS)¶
AWS infrastructure for ParlaySports Backend lives in the repo-root
terraform/ directory. Operator quickstart: terraform/README.md.
Staging and main pin AWS profile parlaysports-terraform on the provider and S3
backend, so terraform commands in those directories use it automatically.
AWS CLI one-offs still need export AWS_PROFILE=parlaysports-terraform.
Environments¶
App production is APP_ENV=main, not production. Terraform env roots
match AppEnv:
| Root | Bucket prefixes (full id = {prefix}-{account}-{region}-an) |
App IAM |
|---|---|---|
terraform/environments/staging |
staging-general, staging-match-archive |
parlay-staging-app (+ instance profile) |
terraform/environments/main |
main-general, main-match-archive |
parlay-main-app (+ instance profile) |
Match-archive buckets transition current and noncurrent object versions to
S3 Glacier Instant Retrieval (GLACIER_IR) after 21 days. Reads stay
instant (no restore). General buckets keep no lifecycle transition.
The general bucket allows anonymous s3:GetObject on the brands/*
prefix only (public brand logos). All other prefixes and the match-archive
bucket remain private. After changing s3.tf, run terraform plan / apply
in each env root so the public-access block and bucket policy take effect.
App env vars¶
| Variable | Value |
|---|---|
AWS_S3_DEFAULT_BUCKET |
Terraform general_bucket_id (full account-regional name) |
AWS_S3_MATCH_ARCHIVE_BUCKET |
Terraform match_archive_bucket_id (full account-regional name) |
Do not set only the short prefix on staging/main AWS — use the apply outputs.
On staging/main after the host uses the instance profile (or an ECS task uses
the role), omit AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and
AWS_ENDPOINT_URL. S3Helper (src/lib/s3.py) then uses the default
AWS credential chain.
Local develop keeps LocalStack keys + endpoint; buckets are
develop-general and develop-match-archive from
docker/localstack/init-s3.sh. If you
still have an old LocalStack volume that only created develop, recreate the
volume or awslocal s3 mb the new names once.
Remote state¶
Staging and main use an S3 backend with native S3 lockfiles
(use_lockfile, Terraform >= 1.10) in ca-central-1. Create the state
bucket once, outside Terraform (see terraform/README.md for CLI). No
DynamoDB lock table. Then:
# terraform/backend.hcl from backend.hcl.example with your bucket name
cd terraform/environments/staging # or main
terraform init -backend-config=../../backend.hcl
terraform plan
terraform apply
Migrating existing local env state:
cd terraform/environments/staging # or main
terraform init -migrate-state -backend-config=../../backend.hcl
| Piece | Suggested value |
|---|---|
| State bucket | account-regional or unique name |
| Locking | S3 lockfile (use_lockfile = true) |
| Staging key | parlaysports-terraform/staging/terraform.tfstate |
| Main key | parlaysports-backend/main/terraform.tfstate |
terraform/backend.hcl is gitignored; see terraform/backend.hcl.example.
IAM (AWS-aligned)¶
Terraform creates a least-privilege S3 policy and role that trusts:
ec2.amazonaws.com— attach the instance profile to Scarlett (Compose today)ecs-tasks.amazonaws.com— reuse as ECS task role whenmainmoves to ECS
No app IAM users or access keys are created in Terraform. Existing human / terraform-operator credentials stay outside this stack (prefer SSO/OIDC later).
Attach instance profile (manual)¶
- Configure remote state (above), then apply:
- Note
app_instance_profile_namefrom outputs - In AWS console / CLI, set that instance profile on the Scarlett EC2 instance
- Update app
.env: set bucket names; remove static AWS keys and endpoint - Restart app containers
Commands¶
cd terraform/environments/staging # or main
terraform init -backend-config=../../backend.hcl
terraform plan
terraform apply
Requires Terraform >= 1.10 and a parlaysports-terraform AWS profile. See
terraform/README.md Prerequisites.
Layout¶
Each env root uses domain files (s3.tf for app buckets + Glacier lifecycle,
iam.tf for the iam_app_runtime module call). App S3 buckets are inlined —
not a shared s3_bucket module. The only module today is
terraform/modules/iam_app_runtime/ (role + policy + instance profile).
Extending¶
- Prefer a new domain
.tffile (or extends3.tf/iam.tf) in both env roots - Extract
terraform/modules/…only for multi-resource DRY across envs - Update this page and
terraform/README.mdwhen apply/ops steps change
Cursor rules¶
Agent conventions for this tree: .cursor/rules/terraform.mdc.