lesson ── terraform-intermediate ── ~18 мин ── 6 шагов
You need the same service in dev, stage, and prod. How do you lay out the code so you are not duplicating HCL? Two approaches:
terraform workspace.
Simple, but risky (it is easy to apply to the wrong env).envs/dev/, envs/prod/. Each env has its own
root with its own backend, and the shared code lives in a module.In this lesson you build both and see the tradeoffs.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
cd /home/student/tf-envs/workspaces-style
cat > main.tf <<'EOF'
resource "random_id" "suffix" {byte_length = 4
}
locals {env = terraform.workspace
}
resource "aws_s3_bucket" "demo" { bucket = "linuxlab-${local.env}-${random_id.suffix.hex}" tags = {Environment = local.env
}
}
output "current_workspace" {value = terraform.workspace
}
EOF
terraform init
Create two workspaces:
terraform workspace new dev
terraform workspace new prod
terraform workspace list
The asterisk marks the active one.
Workspaces are separate states within one backend. With the local
backend the state lives in terraform.tfstate.d/<workspace>/. With S3,
under different keys.
✓ Two workspaces created. Each has its own state.
Switch to dev and apply:
terraform workspace select dev
terraform workspace show
terraform apply -auto-approve
terraform output current_workspace
This should create a bucket named linuxlab-dev-<hash>.
Switch to prod:
terraform workspace select prod
terraform workspace show
terraform apply -auto-approve
terraform output current_workspace
A different bucket is created: linuxlab-prod-<hash>. These two
workspaces have separate states, so each one creates its own resource.
Check it:
aws --endpoint-url=http://localstack:4566 s3 ls
You see both buckets, linuxlab-dev-... and linuxlab-prod-....
✓ Workspaces isolated the state: two different buckets from one set of HCL.
The main problem with workspaces is that it is easy to lose track of which one is active.
The scenario: you are working in dev, you switch to prod to look at
something, then you forget to switch back. You run terraform apply,
and you have applied your dev changes to the prod state.
A guard is a precondition on an external data source that checks the
workspace:
check "workspace_match" { assert {condition = terraform.workspace != "default"
error_message = "Apply on the 'default' workspace is not allowed: choose dev/stage/prod."
}
}
This is part of a safety belt. On a large team, workspaces are not used for prod, precisely to rule out this mistake. For feature-branch experiments in a local dev, they are fine.
Go back to dev:
terraform workspace select dev
✓ You see the danger. In production, use directory style.
mkdir -p /home/student/tf-envs/dirs-style/modules/app
cat > /home/student/tf-envs/dirs-style/modules/app/main.tf <<'EOF'
resource "random_id" "suffix" {byte_length = 4
}
resource "aws_s3_bucket" "demo" { bucket = "linuxlab-${var.env}-${random_id.suffix.hex}" tags = {Environment = var.env
}
}
output "bucket_arn" {value = aws_s3_bucket.demo.arn
}
EOF
cat > /home/student/tf-envs/dirs-style/modules/app/variables.tf <<'EOF'
variable "env" {type = string
validation {condition = contains(["dev", "stage", "prod"], var.env)
error_message = "env must be dev, stage, or prod."
}
}
EOF
This is a reusable module, app. It takes env and creates a bucket
with the right name.
Now the env roots:
✓ The app module is described. Now the roots per environment.
cat > /home/student/tf-envs/dirs-style/envs/dev/main.tf <<'EOF'
module "app" {source = "../../modules/app"
env = "dev"
}
output "bucket_arn" {value = module.app.bucket_arn
}
EOF
cat > /home/student/tf-envs/dirs-style/envs/prod/main.tf <<'EOF'
module "app" {source = "../../modules/app"
env = "prod"
}
output "bucket_arn" {value = module.app.bucket_arn
}
EOF
Apply dev:
cd /home/student/tf-envs/dirs-style/envs/dev
terraform init
terraform apply -auto-approve
terraform output bucket_arn
Apply prod:
cd /home/student/tf-envs/dirs-style/envs/prod
terraform init
terraform apply -auto-approve
terraform output bucket_arn
Each env has its own .terraform/ and its own state. You cannot
accidentally apply the dev config to prod, for that you would have to
physically move into the other directory.
✓ Two env roots, each with its own state. The isolation is solid.
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. On a first switch, though, back
up the state and do a run on a feature branch, the differences
cluster in the newer features (variables in backend,
state encryption, OCI registry-backed modules). See
tf-opentofu-parity for the full matrix.
Move into one of the env roots:
cd /home/student/tf-envs/dirs-style/envs/dev
terraform state list
You see:
module.app.aws_s3_bucket.demo
module.app.random_id.suffix
A comparison:
| Aspect | Workspaces | Directory-per-env |
|---|---|---|
| HCL is duplicated | No | Minimal (thin root, everything in the module) |
| Backend is duplicated | No, single | Yes (each env its own backend) |
| Accidental apply to the wrong env | Easy | Hard. You need a cd |
| Divergence between envs | Impossible, one set of HCL | Possible (good or bad?) |
| Complexity to start | Low | Medium |
| Production-scale | Not recommended | Standard |
In real work: workspaces for short experiments, dirs-per-env for anything serious. Many companies use Terragrunt to manage dirs-style (see the advanced track).
See tf-workspace for workspace details and tf-init-backends for backend per env.
✓ The intermediate track is done. Next: production.
Despite everything said above, workspaces are still useful:
The antipattern: workspaces for separating customers ("tenant per workspace"). At a hundred tenants the state operations (refresh, plan) on a single backend get slow, and the risk of mixing them up grows. For multi-tenant, use directory-style or multi-account.
Workspaces are lightweight, one set of HCL, the switch lives in state. They fit feature-branch experiments or envs that stay close to each other. A directory layout is heavier to start but safer and easier to scale. Production usually uses directory-per-env with a shared module.
команды
terraform workspace new devcreate a workspaceterraform workspace select prodswitch the active oneterraform workspace showwhich one is active right now: check this before applycd envs/prod && terraform applydirectory style: env = directoryконцепции