lesson ── terraform-intermediate ── ~18 мин ── 5 шагов
Until now the state lived in terraform.tfstate next to your HCL. That
works while you are alone. A team of two means two state files, and they
drift apart within the first week.
Now you will build the boilerplate setup: one root creates the S3 bucket for the state and a DynamoDB table for the lock. A second root uses them as a remote backend. This is the standard layout: bootstrap separate from the application.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
Create the files in /home/student/tf-backend/bootstrap/:
main.tf:
resource "aws_s3_bucket" "state" {bucket = "linuxlab-tfstate"
}
resource "aws_s3_bucket_versioning" "state" {bucket = aws_s3_bucket.state.id
versioning_configuration {status = "Enabled"
}
}
resource "aws_dynamodb_table" "lock" {name = "linuxlab-tflocks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {name = "LockID"
type = "S"
}
}
This is a separate root. Its state is local (by default). The bucket and the table are the only resources, and they exist to hold the state of the other roots.
S3 versioning is mandatory: it saves you when the state gets overwritten by accident. See tf-remote-backend-s3.
✓ Bootstrap is described. Init plus apply will create the backend infra.
cd /home/student/tf-backend/bootstrap
terraform init
terraform apply -auto-approve
After apply:
aws --endpoint-url=http://localstack:4566 s3 ls
aws --endpoint-url=http://localstack:4566 dynamodb list-tables
You see linuxlab-tfstate and linuxlab-tflocks. Ready to use.
✓ The backend infra is created. Now the app root will point at it.
In /home/student/tf-backend/app/ create main.tf:
terraform { backend "s3" {bucket = "linuxlab-tfstate"
key = "app/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "linuxlab-tflocks"
# For LocalStack
endpoints = {s3 = "http://localstack:4566"
dynamodb = "http://localstack:4566"
}
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
use_path_style = true
encrypt = false
}
}
resource "random_id" "suffix" {byte_length = 4
}
resource "aws_s3_bucket" "demo" { bucket = "linuxlab-app-${random_id.suffix.hex}"}
Notice:
backend "s3" goes inside the terraform { } block.key = "app/terraform.tfstate" is the path inside the bucket. The
convention is <project>/<env>/terraform.tfstate.endpoints. Needed only for LocalStack. On real AWS you drop it.encrypt = false, for LocalStack. On real AWS, set it to true,
preferably with KMS.Backend parameters cannot be interpolated through var.*. They are
static, or you use a partial backend.
✓ The app root is described with an S3 backend. Init will pick it up.
cd /home/student/tf-backend/app
terraform init
The output will show:
Initializing the backend...
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
There will be no local terraform.tfstate file. The state goes to
S3. Check it:
ls -la /home/student/tf-backend/app/
aws --endpoint-url=http://localstack:4566 s3 ls s3://linuxlab-tfstate/
In the directory, there is no terraform.tfstate. In the bucket, still
empty (no apply yet).
✓ The app sees the S3 backend. State will be written to the bucket.
OpenTofu keeps its CLI and state compatible with Terraform for the
commands in this step: migration usually goes through mv .terraform .terraform.bak; tofu init -upgrade. But on the first switch, back up
your state and run it on a feature branch first. The differences
cluster in the newer features (variables in the backend,
state encryption, OCI registry-backed modules). See
tf-opentofu-parity for the full matrix.
terraform apply -auto-approve
After apply, look at what is in the bucket:
aws --endpoint-url=http://localstack:4566 s3 ls s3://linuxlab-tfstate/app/
You will see terraform.tfstate. Download it and look:
aws --endpoint-url=http://localstack:4566 s3 cp s3://linuxlab-tfstate/app/terraform.tfstate /tmp/state.json
cat /tmp/state.json | jq '.serial, .lineage'
That is your state. If a colleague in another terminal runs
terraform init with the same backend parameters, they get the same
state.
Now check the lock through DynamoDB:
aws --endpoint-url=http://localstack:4566 dynamodb scan --table-name linuxlab-tflocks
After apply there are no records (the lock is released). If someone
tries to run in parallel with your apply, they will hit the lock and
fail with Error acquiring the state lock. See tf-common-errors.
✓ The state lives in S3. Ready for teamwork, ready for CI/CD.
All four or five backend parameters duplicated across prod/stage/dev? That is bad: edit one parameter, and you get N PRs.
The fix is a partial backend:
terraform { backend "s3" {} # empty block}
And you pass the parameters at init:
# backends/prod.hcl
bucket = "linuxlab-tfstate"
key = "app/prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "linuxlab-tflocks"
# backends/dev.hcl
bucket = "linuxlab-tfstate"
key = "app/dev/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "linuxlab-tflocks"
terraform init -backend-config=backends/prod.hcl
# or
terraform init -backend-config=backends/dev.hcl
One HCL. N environments. This is the typical multi-env layout. In detail in lesson 12.
The S3 backend keeps the state file in a bucket and the lock record in
DynamoDB. You configure it in terraform { backend "s3" { ... } }.
Backend values cannot be interpolated, so the parameters are either
static or passed through -backend-config at init.
команды
terraform init -migrate-statemove from the old backend to a new oneterraform init -backend-config=prod.hclpartial backend, parameters from a fileaws --endpoint-url=$LS s3 ls s3://BUCKET/look at the state files in the bucketконцепции