Skip to main content

Terraform S3 backend standard

One bucket, one lock table name, one backend block. Projects differ only in their state key. Removing per-account variation removes a whole class of configuration error.

Background​

The earlier pattern — a separate S3 bucket and DynamoDB table per AWS account — became unmanageable as the number of accounts and projects grew. Bucket and table names had to be discovered per account, bootstrap resources were duplicated, and backend blocks differed from project to project.

The standard replaces that with a single organisation-wide state bucket, and automation that provisions a lock table with an identical name in every account.

The backend block​

terraform {
backend "s3" {
region = "us-east-1"
key = "<account-id>/<state-key>.tfstate"
bucket = "<org>-terraform-state"
dynamodb_table = "<org>-terraform-state"
acl = "bucket-owner-full-control"
encrypt = true
}
}
ParameterValueRule
regionus-east-1Fixed. The state bucket and all lock tables live in us-east-1 regardless of workload region.
bucketorganisation-wide bucketFixed. Never create per-account or per-project state buckets.
dynamodb_tablesame name as the bucketFixed. Provisioned in each account by automation; identical in every account.
key<account-id>/<state-key>.tfstateThe only variable. Prefix with the 12-digit account ID, then a key that uniquely identifies project and environment.
aclbucket-owner-full-controlFixed. Ensures the bucket-owning account retains control of objects written from member accounts.
encrypttrueFixed. State is always encrypted at rest.

Key naming​

123456789012/payments-prod-ecs.tfstate
123456789012/payments-dev-ecs.tfstate
123456789012/platform-network-prod.tfstate
  • Start with the AWS account ID.
  • Include the project and environment.
  • Never reuse a key across environments.
  • Never reuse a key across distinct components that deploy independently.

Why the parameters are fixed​

Region. A single region for the backend means there is exactly one place to look for state and one set of access policies to audit. It also avoids the circularity of a workload in a region that the state bucket does not serve.

Bucket name. A single bucket makes access policy, versioning and lifecycle manageable. It also makes terraform init predictable: a new engineer does not need to discover a name.

Lock table name. Automation provisions terraform-state in every account. Identical naming means the same backend block works everywhere and the lock is account-local — contention in one account never blocks another.

ACL. Member accounts write state into a bucket they do not own. The bucket-owner-full-control ACL ensures the owning account can still manage every object written.

Encryption. State can contain secrets. There is no configuration in which it is acceptable to store it unencrypted.

Provisioning a new account​

  1. Create the account through the account factory.
  2. Automation creates the DynamoDB lock table in us-east-1.
  3. Add the account to the bucket policy so it can write to its prefix.
  4. Run terraform init with the backend block above and an account-prefixed key.

Anti-patterns​

  • A backend block with a literal bucket name that differs from the standard.
  • State keys without an account prefix.
  • Workspaces used to represent environments in production.
  • Manually created buckets "just for this project."
  • Local state committed to a repository.