lesson ── terraform-production ── ~14 мин ── 6 шагов
The production track starts with hygiene. Three tools that no serious
team works without: terraform fmt -check (formatting as a gate),
terraform validate (syntax and gross errors), tflint (style and
provider-specific lints). All three are fast, and all three belong in
pre-commit and CI.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
cd /home/student/tf-lint
cat > main.tf <<'EOF'
resource "aws_s3_bucket" "demo"{bucket = "linuxlab-lint-demo"
tags={Owner="student"}}
output "name" {value=aws_s3_bucket.demo.bucket
}
EOF
The file has extra spaces, hugged braces, bad indentation, and
tags={...} with no spaces. A clean antipattern.
✓ Dirty HCL is in place. Now we show you how the linters see it.
terraform fmt -check
echo "exit: $?"
You should see a list of unclean files and exit 3:
main.tf
exit: 3
-check does NOT fix anything, it only reports. In pre-commit you
usually run without -check (auto-fix), in CI with -check (gate).
See tf-fmt.
Now run it without the flag, auto-fix:
terraform fmt
cat main.tf
The file is aligned. No more ={}=, the indentation is stable.
✓ fmt normalized the HCL. That is exactly its job.
Change the output so it points to an attribute that does not exist:
sed -i 's/aws_s3_bucket\.demo\.bucket/aws_s3_bucket.demo.nonexistent/' main.tf
cat main.tf
Validate without the cloud:
terraform init -backend=false -input=false
terraform validate
You should see something like:
Error: Unsupported attribute
on main.tf line N: ...
This object has no argument, nested block, or exported attribute named "nonexistent".
validate knows the provider schema and catches the typo. Without
it the problem would surface only at plan, a minute later.
Roll it back:
sed -i 's/aws_s3_bucket\.demo\.nonexistent/aws_s3_bucket.demo.bucket/' main.tf
terraform validate
It should say "Success!".
✓ validate confirmed the config. On to tflint.
Without plugins tflint checks only generic rules. For AWS specifics
you need tflint-ruleset-aws. The config, .tflint.hcl:
cat > .tflint.hcl <<'EOF'
plugin "aws" {enabled = true
version = "0.32.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
rule "terraform_naming_convention" {enabled = true
}
rule "terraform_required_version" {enabled = true
}
EOF
tflint --init
--init pulls in the plugin (this is a network request). It should
say "Installing aws plugin".
✓ tflint installed the AWS plugin. Now run the check.
Run it on the current code:
tflint --no-color
echo "exit: $?"
Most likely no errors, the HCL is simple. Add a deliberately bad
resource, an aws_instance with a type that does not exist:
cat >> main.tf <<'EOF'
resource "aws_instance" "bad" {ami = "ami-foobar"
instance_type = "t2.bogus"
}
EOF
tflint --no-color
echo "exit: $?"
You should see something like aws_instance_invalid_type: "t2.bogus" is an invalid instance type. This is specific to the AWS plugin,
which knows every type.
Remove it:
sed -i '/aws_instance "bad"/,/^}$/d' main.tf
tflint --no-color && echo "tflint clean"
✓ tflint catches provider problems that validate does not see.
OpenTofu keeps the 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 the state and do a run on a feature branch, the
differences concentrate in newer features (variables in the
backend, state encryption, OCI registry-backed modules). See
tf-opentofu-parity for the full matrix.
The canonical lint pipeline:
cat > lint.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
echo "==> fmt"
terraform fmt -check -recursive
echo "==> validate"
terraform init -backend=false -input=false -no-color > /dev/null
terraform validate -no-color
echo "==> tflint"
tflint --no-color
echo "All lints passed."
EOF
chmod +x lint.sh
./lint.sh
Any step that fails exits 1, and the rest do not run. This is the basic lint job in CI. See tf-fmt-validate-ci about wiring it into pre-commit and GitHub Actions.
✓ The pipeline gate is ready. Next, pre-commit.
Base rules without plugins:
terraform_deprecated_interpolation, the old ${...} syntax.terraform_unused_declarations, a variable/output that nothing
references.terraform_naming_convention, the snake_case convention.terraform_required_version / terraform_required_providers,
their absence = warning.The AWS plugin:
aws_instance_invalid_type, aws_db_instance_invalid_type.aws_iam_policy_invalid_principal_format.aws_s3_bucket_name (length, characters, convention).aws_resource_missing_tags, required tags (turned on through
config).Config for required tags:
rule "aws_resource_missing_tags" {enabled = true
tags = ["CostCenter", "Environment"]
}
This is a business-policy gate at the linter level.
fmt -check breaks on unclean formatting, validate on syntax and the basic schema, tflint on style and provider best practices. In CI they run in one job (lint), and exit 1 at any stage fails the whole PR.
команды
terraform fmt -check -recursivecheck that the HCL is formatted. Exit 3 = there is a difference.terraform init -backend=false && terraform validatesyntax without the cloud. -backend=false avoids stopping when S3 is missing.tflint --init && tflint --recursivelint every directory. --init pulls in the plugins.концепции