Skip to content

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 when main moves 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)

  1. Configure remote state (above), then apply:
cd terraform/environments/staging
terraform apply
  1. Note app_instance_profile_name from outputs
  2. In AWS console / CLI, set that instance profile on the Scarlett EC2 instance
  3. Update app .env: set bucket names; remove static AWS keys and endpoint
  4. 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

  1. Prefer a new domain .tf file (or extend s3.tf / iam.tf) in both env roots
  2. Extract terraform/modules/… only for multi-resource DRY across envs
  3. Update this page and terraform/README.md when apply/ops steps change

Cursor rules

Agent conventions for this tree: .cursor/rules/terraform.mdc.