lesson ── terraform-intermediate ── ~15 мин ── 5 шагов
The community has written modules for almost everything: VPC, EKS, ALB, S3 with audit configuration. Before you write your own, look at the Registry. Often a ready-made module covers 90% of what you need and saves you a week.
Here you will wire up terraform-aws-modules/s3-bucket/aws from the Registry, a
public module maintained by an active community. You will see how to
read version constraints, how to find inputs and outputs in the documentation,
and how to pin a version.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
We will wire up terraform-aws-modules/s3-bucket/aws. This is the most popular
community module for S3.
Documentation: https://registry.terraform.io/modules/terraform-aws-modules/s3-bucket/aws/latest
In real work you need to read these sections:
This is the first thing you do before using any community module. You don't write HCL, you don't debug errors, you read.
For now we do nothing, we just understand the process.
✓ Ready to wire it up.
Create main.tf:
resource "random_id" "suffix" {byte_length = 4
}
module "logs_bucket" {source = "terraform-aws-modules/s3-bucket/aws"
version = "~> 4.0"
bucket = "linuxlab-registry-logs-${random_id.suffix.hex}" versioning = {enabled = true
}
tags = {Owner = "student"
Project = "registry-lesson"
}
}
output "registry_bucket_arn" {value = module.logs_bucket.s3_bucket_arn
}
What matters:
source, three segments namespace/name/provider, no https://.version = "~> 4.0", a pessimistic constraint, see
tf-module-versioning. Takes any 4.x, but not 5.x.bucket, versioning, tags, the module inputs. Their names and format
come from its documentation, they are not invented.module.logs_bucket.s3_bucket_arn, the module output. Again, the name
is not invented, it comes from the module's Outputs section.✓ The wiring is described. Now init.
cd /home/student/tf-registry
terraform init
Internet access is needed only for the Terraform Registry, the module's own HCL will go to LocalStack endpoints, not to real AWS.
The output should contain:
Initializing modules...
Downloading registry.terraform.io/terraform-aws-modules/s3-bucket/aws ...
Check what got downloaded:
cat .terraform/modules/modules.json | jq '.Modules[] | {Key, Source, Version}'You will see the main module and its submodules (large community modules
use other modules internally). It is normal that you called one,
while .terraform/modules/ may end up with 5 directories.
If init fails with a network error: check internet with `curl -I https://registry.terraform.io`. In our sandbox this should work (links.internal: false).
✓ The module is downloaded. Now plan and apply.
terraform apply -auto-approve
The module's plugins see the AWS endpoints configured in provider.tf (we
inherited them from aws-localstack-provider.tf). Inside the module there is no
special setup, just aws_s3_bucket. So it works
both on LocalStack and on real AWS.
After apply, look at the state:
terraform state list | grep module
The addresses inside the module may look like this:
module.logs_bucket.aws_s3_bucket.this[0]
module.logs_bucket.aws_s3_bucket_versioning.this[0]
Note the [0], the community module uses count = local.create ? 1 : 0
internally (optional creation). This is a common pattern in large modules.
✓ The Registry module created the bucket inside its own nesting.
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
make a backup of state and run on a feature branch, the differences
cluster in newer features (variables in backend,
state encryption, OCI registry-backed modules). See
tf-opentofu-parity for the full matrix.
terraform output registry_bucket_arn
It should print the bucket ARN (arn:aws:s3:::linuxlab-registry-logs-...).
This value passed through two contracts:
output "s3_bucket_arn" { value = ... }.output "registry_bucket_arn" { value = module.logs_bucket.s3_bucket_arn }.Without an explicit output at the root level, nothing is plainly visible
outside the root. A module output is available only through the address module.X.<name>,
but terraform output shows only root-level outputs.
✓ The contract closed: module to root to CLI.
A ready-made module saves time, but it is not always the right choice:
You don't need 90% of the inputs. terraform-aws-modules/vpc/aws
has 200+ variables. If you use 5, your HCL looks
deceptively simple, but the dependency tree holds a pile of code you do not
know. Debugging gets harder.
"Magic" behavior. A good community module documents its behavior, but not always, you run into modules with non-obvious dependencies (for example, they create an IAM role automatically).
Versions and upgrade pain. A major upgrade (4.x to 5.x) usually breaks the contract. If 50 of your roots use the module, the upgrade is a sprint of work.
Your 100-line module vs a 5000-line Registry one. If your needs are simple, your own module is better. Full control, a clear dependency graph.
Rule: a community module when the ecosystem is behind you. Large AWS infra, ALB+EKS+RDS, take the community one. Small project, often simpler to write your own.
A Registry module = a source of the form <namespace>/<name>/<provider> plus
a required version. Without version, Terraform takes the max, which in production is a
time bomb. The module documentation lives on its Registry page, the Inputs and
Outputs sections, and that is its contract.
команды
terraform initdownloads the module and its submodules into .terraform/modules/terraform init -upgraderereads version constraints and downloads newer ones within their boundsterraform providersthe provider tree by module: shows which submodules got pulled inконцепции