Create a Terraform remote-state backend (an S3 bucket, versioned and
encrypted) via a single CloudFormation stack — no DynamoDB table required.
State locking uses Terraform's native S3 locking
(use_lockfile = true), so there is nothing else to provision or pay for.
- Terraform >= 1.11 (native S3 locking,
use_lockfile, was introduced in 1.11) - AWS credentials available via the standard SDK chain (env vars, shared config/credentials files, SSO, an instance/task role, etc.)
brew install youyo/tap/tfstoreThis installs the tfstore binary and its zsh completion script.
Prebuilt binaries for linux/arm64 and darwin/arm64 are published on
the GitHub releases page —
amd64 is not built. tfstore ships as an unsigned binary, so if you download
it manually (not via Homebrew) rather than build it yourself, macOS
Gatekeeper quarantines it on first run. Clear the quarantine attribute once
before running it:
xattr -dr com.apple.quarantine ./tfstoreRun tfstore <name> with the required backend name:
$ tfstore myproject
Creating stack...
bucket: tfstate-myproject-123456789012-ap-northeast-1
region: ap-northeast-1
key: terraform.tfstate
Terraform initialize command is
terraform init \
-backend-config 'bucket=tfstate-myproject-123456789012-ap-northeast-1' \
-backend-config 'key=terraform.tfstate' \
-backend-config 'region=ap-northeast-1' \
-backend-config 'encrypt=true' \
-backend-config 'use_lockfile=true'
$ terraform init \
-backend-config 'bucket=tfstate-myproject-123456789012-ap-northeast-1' \
-backend-config 'key=terraform.tfstate' \
-backend-config 'region=ap-northeast-1' \
-backend-config 'encrypt=true' \
-backend-config 'use_lockfile=true'| Flag | Short | Default | Description |
|---|---|---|---|
--stack-name |
tfstore-<name> |
CloudFormation stack name | |
--bucket-name |
tfstate-<name>-<account-id>-<region> |
S3 bucket name | |
--region |
-r |
resolved from AWS configuration | AWS region |
--key |
-k |
terraform.tfstate |
Terraform state object key |
$ tfstore myproject --stack-name custom-stack-name --region us-east-1 --key envs/prod/terraform.tfstateIf a stack with the given name already exists, tfstore exits with an
error — it is a create-only tool and does not update or migrate an existing
stack.
The automatically generated bucket name is deterministic. Because its
CloudFormation DeletionPolicy is Retain, deleting a stack leaves the
bucket behind; recreating the same name in the same account and region can
therefore collide. Use --bucket-name to choose a different name.
Terraform's native S3 locking writes a companion <key>.tflock object next
to the state object, so the identity running terraform init/plan/apply
needs access to both objects plus s3:ListBucket on the bucket:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TerraformStateObject",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::<BucketName>/<key>",
"arn:aws:s3:::<BucketName>/<key>.tflock"
]
},
{
"Sid": "TerraformStateBucket",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::<BucketName>"
}
]
}Replace <BucketName> and <key> with the values tfstore printed above.