Stage 4 · Provision
Terraform at Scale
Workspace Strategy
CLI workspaces, directory-per-environment, Terragrunt, and when each model fails — choosing the right approach.
Workspace Models
A workspace strategy determines how you separate environments in Terraform. The three primary models are CLI workspaces, directory-per-environment, and Terragrunt. Each has different tradeoffs in code duplication, flexibility, and complexity.
CLI Workspaces
CLI workspaces use the same directory with different state files per environment. Terraform stores each workspace state in the same backend, separated by workspace name. This is the simplest approach but the least flexible.
$ terraform workspace list # List all workspaces
$ terraform workspace new dev # Create dev workspace
$ terraform workspace new staging # Create staging workspace
$ terraform workspace select prod # Switch to prod
$ terraform workspace show # Current workspaceEach workspace has its own state file. The same .tf files apply to all workspaces. Use terraform.workspace to differentiate behavior per workspace in the configuration.
locals {
env = terraform.workspace
config = {
dev = {
instance_type = "t3.micro"
min_count = 1
}
staging = {
instance_type = "t3.small"
min_count = 2
}
prod = {
instance_type = "m5.large"
min_count = 3
}
}
}
resource "aws_instance" "web" {
instance_type = local.config[local.env].instance_type
min_count = local.config[local.env].min_count
}The local.config map defines per-workspace values. The terraform.workspace variable determines which config is used. This keeps all environments in one file but limits per-environment customization.
Directory-per-Environment
Each environment has its own directory with its own .tf files, backend configuration, and variable values. This is the most explicit approach — you can see exactly what each environment deploys.
environments/
dev/
main.tf
variables.tf
terraform.tfvars
backend.tf
staging/
main.tf
variables.tf
terraform.tfvars
backend.tf
prod/
main.tf
variables.tf
terraform.tfvars
backend.tfEach directory is a separate root module. Backend configuration points to environment-specific state files. Variable values define per-environment settings. Modules are shared across environments.
Terragrunt Approach
Terragrunt is a thin wrapper that eliminates boilerplate in multi-environment setups. It provides DRY backend configuration, dependency management, and environment promotion. It is the most sophisticated approach.
# terragrunt.hcl (root)
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite"
}
config = {
bucket = "my-terraform-state-${get_aws_account_id()}"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
# environments/prod/vpc/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "../../modules//vpc"
}
inputs = {
cidr_block = "10.0.0.0/16"
env = "prod"
}The root terragrunt.hcl generates backend configuration automatically. The key parameter uses path_relative_to_include() to create a unique state file path per module. Child terragrunt.hcl files inherit the root configuration.
When Models Fail
| Model | Failure Mode |
|---|---|
| CLI Workspaces | Prod and staging share the same config — cannot customize per env |
| Directory-per-Env | Code duplication across directories — changes require updating multiple files |
| Terragrunt | Extra tool dependency — teams must learn Terragrunt syntax |
Choosing the Right Model
- Solo developer, simple infrastructure: CLI workspaces.
- Small team, few environments: Directory-per-environment.
- Large team, many environments: Terragrunt.
- Regulated industry: Directory-per-environment (explicit is better).
- Multiple cloud providers: Terragrunt (handles provider config).
- Need per-environment customization: Directory-per-environment or Terragrunt.
Directory-per-environment is the safest default. It is explicit, requires no extra tools, and is easy to understand. Upgrade to Terragrunt when the boilerplate becomes a burden.
CLI workspaces use the same .tf files for all environments. If prod needs different providers, different modules, or different variable validation, workspaces become a limitation. Split into directories before you need this flexibility.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.