Beyond a Single main.tf
A single flat Terraform configuration works fine for a demo, but real infrastructure needs to be reused across dev, staging, and production without copy-pasting resource blocks. Modules and workspaces solve this in different, complementary ways.
Structuring a Reusable Module
modules/
web-service/
main.tf
variables.tf
outputs.tf
environments/
dev/
main.tf
staging/
main.tf
production/
main.tf
// modules/web-service/main.tf
resource "aws_ecs_service" "this" {
name = var.service_name
cluster = var.cluster_id
task_definition = aws_ecs_task_definition.this.arn
desired_count = var.desired_count
}
resource "aws_ecs_task_definition" "this" {
family = var.service_name
container_definitions = jsonencode([{
name = var.service_name
image = var.image
memory = var.memory
}])
}
Consuming the Module Per Environment
// environments/production/main.tf
module "web_service" {
source = "../../modules/web-service"
service_name = "web-prod"
cluster_id = data.aws_ecs_cluster.main.id
image = "myregistry/web:1.4.0"
desired_count = 5
memory = 1024
}
// environments/dev/main.tf
module "web_service" {
source = "../../modules/web-service"
service_name = "web-dev"
cluster_id = data.aws_ecs_cluster.main.id
image = "myregistry/web:latest"
desired_count = 1
memory = 256
}
Workspaces: A Lighter-Weight Alternative
For infrastructure that’s structurally identical across environments and only differs in variable values, workspaces avoid duplicating directory structures entirely:
terraform workspace new staging
terraform workspace new production
terraform workspace select production
terraform apply -var-file="production.tfvars"
Workspaces share the same configuration but maintain separate state files — useful for near-identical environments, less suited when environments genuinely diverge structurally (e.g., production has a multi-AZ database and dev doesn’t).
Modules vs Workspaces: When to Use Which
| Situation | Approach |
|---|---|
| Environments have structural differences | Separate directories + shared modules |
| Environments are nearly identical, differ only in scale/values | Workspaces |
| Reusing the same resource pattern across many services | Modules |
Remote State Per Environment
// environments/production/backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
}
}
Conclusion
Modules make infrastructure patterns reusable; workspaces or separate environment directories keep environments isolated. Most teams outgrow workspaces-only setups as production infrastructure diverges from dev — plan for separate environment directories with shared modules from the start if you expect that divergence.