If you find yourself having to choose between terraform workspaces for single codebase applied to dev, stage and prod environments vs for example another popular choise is to use module(s) + seperate folders per environment. Three cons when using workspaces are:
- Use of conditionals. Which is particularly bad for reading as well as use.
- Its easy to make mistakes because its not obvious which environment you are applying in as with folders the prompt is obvious in which folder your are. Assuming your prompt show folder names.
- State mistakes or mishaps.
This excellent read helps understanding the choices. As with eveything pros and cons with each choice.
As far as #2 this I mitigate by using a prompt manager like starship.rs which you should consider anyhow because you have this same issue ie which AWS account, which kube context etc…
For #3 its debatable what is best(or worse). I like the way workspaces lay down the state in s3 ie env:/dev/myproject1/terraform.tf. Where dev is the workspace name. I would argue the bigger mistake with state is copy and paste a config.tf from another project and forgetting to change the key. This mistake exist in all options and not workspaces related.
For #1 I have rarely had the need for conditionals when using a nested map for environment differences. See below AI explanation which explains it well.
Combining Terraform Workspaces (terraform.workspace) with a nested local map is a common pattern for managing environment-specific settings (like dev, staging, and prod) inside a single set of configuration files. [1, 2]
How the Pattern Works
- terraform.workspace returns the name of the currently active workspace (e.g., dev, staging, prod). [1, 2]
- A nested local map stores the configuration parameters for each environment.
- You dynamically look up the current workspace name in the local map to pull the exact settings required. [1]
Example Configuration (main.tf)
hcl
locals { # Nested local map containing environment-specific configurations environments = { dev = { instance_type = "t3.small" instance_count = 1 enable_monitoring = false } staging = { instance_type = "t3.medium" instance_count = 2 enable_monitoring = true } prod = { instance_type = "t3.xlarge" instance_count = 3 enable_monitoring = true } }
# Lookup the current workspace; fallback or default safely if needed # (ensure your active workspace matches the keys: dev, staging, prod) env_config = local.environments[terraform.workspace]}WARNING: Always use code with caution.
use in resources like this
resource "aws_instance" "app" { count = local.env_config.instance_count ami = "ami-0fb653ca2d3203ac1" instance_type = local.env_config.instance_type
monitoring = local.env_config.enable_monitoring
tags = { Name = "app-server-${terraform.workspace}" Environment = terraform.workspace }}Best Practices & Caveats
- Match Workspace Names: Ensure the keys in your environments map (dev, staging, prod) strictly match the names of the workspaces you create via terraform workspace new
. If a workspace name doesn’t exist as a key, Terraform will throw a lookup error. [1] - Single State File per Workspace: Workspaces maintain completely separate state files pointing to the exact same codebase. Avoid putting sensitive, multi-account credential differences into workspaces; workspaces work best when all environments share the exact same cloud credentials/account. [1, 2, 3]
- HashiCorp Recommendation: For large-scale or heavily drifted environments (where dev, staging, and prod deviate significantly in architecture), the Terraform team often recommends separate directory-based layouts instead of workspaces to keep logic readable and avoid complex conditionals. [1, 2]