Elevator Pitch
When teams manage multiple environments in Terraform, they often duplicate entire configurations, creating a maintenance nightmare. Terraform workspaces combined with modules provide an elegant solution—one codebase serves all environments through workspace-aware configurations. Read how MyCoCo transformed their infrastructure management using workspaces to achieve true DRY (Don't Repeat Yourself) principles.
TL;DR
The Problem: Copy-pasted Terraform configurations across dev/staging/production led to environment drift, deployment failures, and 20+ hours weekly maintenance overhead.
The Solution: Terraform workspaces for environment separation combined with modules for code reuse and workspace-specific variable files.
The Impact: MyCoCo reduced configuration management time by 70%, eliminated environment drift, and deployed new environments in hours instead of days.
Key Implementation: Use
terraform.workspace
with modular design patterns and
structured terraform.workspace.tfvars
files for
environment-specific configurations.
Bottom Line: If you're maintaining separate Terraform directories per environment, workspaces + modules will transform your infrastructure management.
From copy-paste chaos to modular design: The transformation of MyCoCo's infrastructure management
The Challenge: MyCoCo's Environment Sprawl
"Why do we have three different versions of our RDS configuration?" Jordan asked during his first week as MyCoCo's platform engineer. The answer revealed a deeper problem.
Sam, the senior DevOps engineer, explained their evolution: "We started with one environment, then copied everything for staging, then copied again for production. Now we maintain three separate Terraform directories."
The structure told the story:
infrastructure/
├── dev/
│ ├── main.tf # 500 lines
│ ├── rds.tf # 200 lines
│ └── variables.tf # 150 lines
├── staging/
│ ├── main.tf # 497 lines (slightly different)
│ ├── rds.tf # 198 lines (minor variations)
│ └── variables.tf # 148 lines (different values)
└── production/
├── main.tf # 502 lines (more differences)
├── rds.tf # 205 lines (critical variations)
└── variables.tf # 152 lines (production values)
Alex, VP of Engineering, quantified the impact: "Every infrastructure change requires three pull requests, three code reviews, and three separate terraform apply commands. We're burning 20 hours per week just keeping these in sync."
The breaking point came when a critical security patch needed to be applied across all environments. What should have been a 30-minute task turned into a two-day project of carefully updating each environment, testing separately, and hoping nothing was missed.
The Solution: Workspaces + Modules Architecture
MyCoCo's transformation centered on two key Terraform features: workspaces for environment isolation and modules for code reuse.
Before: Duplicate Directories
# production/rds.tf - Copied and modified for each environment
resource "random_password" "db_password" {
length = 16
special = true
}
# Note: Consider using password_wo instead of password in Terraform 1.11+
# for better security (write-only attribute that doesn't show in plans)
resource "aws_db_instance" "main" {
identifier = "mycoco-prod"
engine = "postgres"
engine_version = "15.3"
instance_class = "db.t3.large" # Hard-coded per environment
allocated_storage = 100
storage_encrypted = true
username = "dbadmin"
password = random_password.db_password.result
# 50+ more lines of mostly identical configuration...
}
After: Workspace-Aware Module
# main.tf - Single configuration for all environments
locals {
workspace_config = {
dev = {
instance_class = "db.t3.micro"
allocated_storage = 20
backup_retention = 1
}
staging = {
instance_class = "db.t3.medium"
allocated_storage = 50
backup_retention = 7
}
production = {
instance_class = "db.t3.large"
allocated_storage = 100
backup_retention = 30
}
}
env_config = local.workspace_config[terraform.workspace]
}
module "database" {
source = "./modules/rds"
environment = terraform.workspace
instance_class = local.env_config.instance_class
allocated_storage = local.env_config.allocated_storage
backup_retention = local.env_config.backup_retention
# Common configuration for all environments
engine = "postgres"
engine_version = "15.3"
# Workspace-aware naming
identifier = "mycoco-${terraform.workspace}"
}
The Module Structure
# modules/rds/variables.tf
variable "identifier" {
description = "The name of the RDS instance"
type = string
}
variable "engine" {
description = "The database engine"
type = string
}
variable "engine_version" {
description = "The engine version to use"
type = string
}
variable "instance_class" {
description = "The instance type of the RDS instance"
type = string
}
variable "allocated_storage" {
description = "The allocated storage in gibibytes"
type = number
}
variable "backup_retention" {
description = "The days to retain backups for"
type = number
default = 7
}
variable "environment" {
description = "The deployment environment"
type = string
}
# modules/rds/main.tf
resource "random_password" "db_password" {
length = 16
special = true
}
resource "aws_db_instance" "main" {
identifier = var.identifier
engine = var.engine
engine_version = var.engine_version
instance_class = var.instance_class
allocated_storage = var.allocated_storage
max_allocated_storage = var.allocated_storage * 2
storage_encrypted = true
db_name = "mycoco"
username = "dbadmin"
password = random_password.db_password.result
backup_retention_period = var.backup_retention
backup_window = "03:00-04:00"
# Production-only features
deletion_protection = var.environment == "production" ? true : false
skip_final_snapshot = var.environment != "production" ? true : false
tags = {
Environment = var.environment
ManagedBy = "terraform"
}
}
# modules/rds/outputs.tf
output "endpoint" {
description = "The connection endpoint"
value = aws_db_instance.main.endpoint
}
output "database_name" {
description = "The database name"
value = aws_db_instance.main.db_name
}
output "instance_id" {
description = "The RDS instance ID"
value = aws_db_instance.main.id
}
Environment-Specific Variables
# envs/production.tfvars
region = "ca-central-1"
enable_monitoring = true
enable_backups = true
multi_az = true
performance_insights = true
# envs/staging.tfvars
region = "ca-central-1"
enable_monitoring = true
enable_backups = true
multi_az = false
performance_insights = false
# envs/dev.tfvars
region = "ca-central-1"
enable_monitoring = false
enable_backups = false
multi_az = false
performance_insights = false
Workspace Management Workflow
# List available workspaces
$ terraform workspace list
default
dev
* staging
production
# Switch to production
$ terraform workspace select production
# Deploy with workspace-specific config
$ terraform apply -var-file="envs/$(terraform workspace show).tfvars"
Results: MyCoCo's Workspace Transformation
The impact of adopting workspaces with modules was immediate:
70% Reduction in Maintenance Time: Changes that previously required updating three separate codebases now happened in one place. Sam noted, "I make one change, test it in dev workspace, promote through staging, and apply to production—all with the same code."
Zero Environment Drift: With all environments using the same modules, configuration drift became impossible. The only differences were intentional, defined in workspace-specific variables.
Rapid Environment Provisioning: When MyCoCo
needed a disaster recovery environment, Jordan created it in two
hours:
terraform workspace new dr-east && terraform apply
.
Previously, this would have taken days of copying and modifying
configurations.
Clear Environment Isolation: Maya, the security engineer, appreciated the built-in separation: "Each workspace has its own state file. There's no risk of accidentally modifying production while working in dev."
The real validation came during their next security audit. Instead of reviewing three sets of configurations, auditors examined one modular codebase with clear environment separation through workspaces.
Key Takeaways
Start with Workspaces: Before diving into complex module structures, implement workspaces to separate your environments while using the same code.
Use terraform.workspace: Reference the current workspace in your configurations for dynamic resource naming and conditional logic.
Combine with Modules: Workspaces handle environment separation; modules handle code reuse. Together, they eliminate duplication.
Workspace-Specific Variables: Store environment-specific values in separate tfvars files, loaded based on the current workspace.
State Isolation: Each workspace maintains its own state file, providing natural environment isolation without complex backend configurations.
Documentation is Critical: Each module needs clear documentation of required and optional variables, making adoption straightforward. Leverage terraform-docs (a utility to generate documentation from Terraform modules in various output formats) to automate this process.
Consider Community Modules: For complex RDS setups, consider using established modules like terraform-aws-modules/rds which provides workspace-aware configurations out of the box.
For teams drowning in duplicate Terraform configurations, workspaces combined with modules offer a path to sanity. The investment in refactoring pays immediate dividends in reduced maintenance, eliminated drift, and happier engineers.