Chapter 8 of 24

AWS Providers and Remote State

Bootstrap encrypted, versioned state once, then let independent roots use it with S3 lockfiles.

Learning objectives

Provider and backend responsibilities

Authentication

Use AWS SSO and a named approved profile. Never put credentials in HCL or variable files.

Identity pin

The bootstrap passes that profile to the AWS provider and fails its plan unless STS resolves to the expected account ID.

Locks

The shared S3 backend contract is use_lockfile = true; do not add a DynamoDB lock table.

Feature floor

This backend lab requires Terraform and OpenTofu >= 1.10.

Bootstrap ordering

The bootstrap root creates the KMS key and S3 bucket with local state. It has no backend block because it cannot safely store creation state in infrastructure that does not exist. After its reviewed apply, another root copies the backend output into a local uncommitted file and initializes. State flows forward only.

Lab: create backend prerequisites

Read the bootstrap resources and backend example. The output supplies the created customer-managed key ARN as kms_key_id, so backend writes use SSE-KMS rather than the default AES256 encryption. Choose exactly one CLI per working directory; do not run Terraform and OpenTofu against the same local bootstrap state.

export COURSE_AWS_PROFILE="replace-with-approved-aws-profile"
export COURSE_AWS_ACCOUNT_ID="123456789012"
cd learn-terraform/labs/08-remote-state
terraform init -backend=false
terraform validate
aws --profile "$COURSE_AWS_PROFILE" --region ap-south-1 sts get-caller-identity --query '{Account:Account,Arn:Arn}' --output table
terraform plan \
  -var='aws_region=ap-south-1' \
  -var="aws_profile=$COURSE_AWS_PROFILE" \
  -var="expected_aws_account_id=$COURSE_AWS_ACCOUNT_ID" \
  -var='state_bucket_name=replace-with-a-unique-name'

# In a fresh copy of this lab, use OpenTofu instead:
tofu init -backend=false
tofu validate
aws --profile "$COURSE_AWS_PROFILE" --region ap-south-1 sts get-caller-identity --query '{Account:Account,Arn:Arn}' --output table
tofu plan \
  -var='aws_region=ap-south-1' \
  -var="aws_profile=$COURSE_AWS_PROFILE" \
  -var="expected_aws_account_id=$COURSE_AWS_ACCOUNT_ID" \
  -var='state_bucket_name=replace-with-a-unique-name'

The displayed STS account and ARN must match the approved profile and expected account ID immediately before every plan or apply; the lab repeats that account check during planning and fails on a mismatch. It accepts no ambient default credentials. Re-run the same STS command immediately before a reviewed apply.

Use init -migrate-state only to move existing state after a backup and reviewed ownership decision. Use init -reconfigure only to discard cached backend settings and initialize the already-selected backend again; it does not migrate state. Neither option makes switching CLIs in a shared directory safe.

Review backend setup

ControlExpected
IdentityNamed approved profile, STS account and ARN match the expected account, and region is ap-south-1.
S3 bucketUnique input name, versioning enabled, all public access blocked, and prevent_destroy enabled.
EncryptionSSE-KMS using the bootstrap customer-managed key ARN; the key also has prevent_destroy.
Lockinguse_lockfile = true in a separate S3 backend configuration.
RecoveryInspect bucket versions and the last successful plan before state rollback.

Failure drill

Do not force-unlock, delete bootstrap local state, or restore a state version on suspicion. Prove the original run stopped, inspect versions, and make a reviewed recovery plan. Use init -migrate-state only after a backup and ownership decision.

Verify

  1. Run formatting and validation without an apply.
  2. Immediately before plan, inspect STS account and ARN for the approved named profile in ap-south-1.
  3. Review the bootstrap plan for the account precondition, KMS encryption, versioning, public-access blocks, and both lifecycle guards.
  4. Confirm the bootstrap has no backend block and its output includes kms_key_id and use_lockfile = true.

Cleanup

Retain bootstrap local state securely while the backend exists. The KMS key and versioned state bucket use prevent_destroy: remove those guards only through a deliberate reviewed change after every dependent state version has been migrated or removed and recovery has been verified. Never casually destroy the bootstrap or delete local state while its resources remain managed. Remove only generated initialization directories from this repository.

Quick check

Why is a backend bootstrap separate?

A configuration cannot safely store creation state in infrastructure that does not exist yet.

What serializes S3 backend writes here?

The shared use_lockfile = true contract.

Recap and next

Use approved identities, provider constraints, KMS permissions, lifecycle guards, and state ownership deliberately. The next phase builds the AWS network foundation on these boundaries.

Next: Chapter 9 will build the three-AZ network foundation.