Repository navigation
State and Backends
How this module expects Terraform state to be stored, and what the bootstrap/
module is for.
versions.tf declares an S3 backend with an empty body:
terraform {
backend "s3" {}
}
No bucket, key, region, or lock table is hardcoded — deliberately, since this
module is published and a literal would point every user's state at one
account. You must supply the configuration at init time, either with
-backend-config=backend.hcl or with repeated -backend-config=key=value
flags.
The four keys the backend needs, plus encrypt, are shown in
backend.hcl.example. backend.hcl itself is gitignored.
State storage is a chicken-and-egg problem: you cannot keep the state for the
state bucket in the state bucket. bootstrap/ is a small, separate root module
that solves it. It uses local state, creates exactly two things, and is meant
to be run once per account:
- An S3 bucket named
<project_name>-terraform-state, versioned, SSE-KMS encrypted, with all public access blocked andprevent_destroyset. - A DynamoDB table named
<project_name>-terraform-lock, pay-per-request, with aLockIDstring hash key.
cd bootstrap
terraform init
terraform apply
Its three outputs — state_bucket, lock_table, region — are exactly what
backend.hcl needs.
The bootstrap module's own state files are gitignored
(bootstrap/terraform.tfstate*). If you plan to run bootstrap from more than
one machine, back that file up or move it into the bucket it just created.
Three files in the repo disagree on the lock table name. bootstrap/main.tf
creates <project_name>-terraform-lock, backend.hcl.example uses
reportmate-terraform-lock, but the quick-start snippet in README.md writes
reportmate-terraform-locks — plural. Copy the README snippet verbatim and
terraform init will fail to acquire a lock. Take the value from the bootstrap
output, not the README.
DynamoDB-based locking means concurrent applies from CI and a laptop will
block rather than corrupt each other. Nothing in the module sets
use_lockfile, so the DynamoDB table is the only lock: leave dynamodb_table
out of backend.hcl and state is not locked at all.
The module has no workspace logic. To run more than one environment, use a
different key in the backend config (the example uses prod/terraform.tfstate)
and set environment to match. Bear in mind two things that are not
environment-scoped and will collide inside one account:
- ECR repository names use
project_nameonly, so two environments in the same account share the same<project>-api,<project>-frontend,<project>-maintenance, and<project>-demo-looprepositories. - The Cognito hosted-UI domain and the S3 bucket names are globally unique;
they do include the environment, so they will not collide across
environments, but they will collide with any other AWS account that picked
the same
project_nameandenvironment.
Changing project_name per environment sidesteps all of this at the cost of
uglier names.
.github/workflows/terraform.yml runs plan on pull requests and apply on
pushes to main, using OIDC (id-token: write) to assume secrets.AWS_ROLE_ARN
and reading TF_VAR_db_password, TF_VAR_api_internal_secret, and
TF_VAR_client_passphrase from repository secrets. It also posts the plan as a
PR comment and gates apply on a production GitHub environment.
The workflow is not usable as-is in this repo: both its path filter and its
TF_DIR point at infrastructure/aws, a directory that exists in the monorepo
this module was extracted from but not here. As written the paths filter never
matches, so the workflow never triggers; if you make it trigger, every step
fails on a missing working directory. Set TF_DIR to . and the paths filter
to something that matches this repo before relying on it. It also does not pass
TF_VAR_public_api_url, which is a required variable.
terraform destroy will not complete without manual steps. The RDS instance
carries both deletion_protection = true and a prevent_destroy lifecycle
block, and the bootstrap state bucket has prevent_destroy too. Removing them
requires editing the code, and the database is configured to take a final
snapshot named <prefix>-final-snapshot — a name that will collide if you tear
down and rebuild the same environment twice.
MIT licensed. — reportmate.app