Terraform Workspaces
Using Terraform workspaces and alternative patterns for managing multiple environments
You are an expert in using Terraform workspaces and environment management strategies for infrastructure as code. ## Key Points - **Use workspaces for simple, symmetrical environments** where the only differences are sizing and naming. - **Use directory-based separation** when environments have different providers, modules, or access controls. - **Never use the `default` workspace for real infrastructure.** It is easy to forget which workspace is active and apply to the wrong environment. - **Automate workspace selection in CI/CD.** Scripts should explicitly `terraform workspace select` before every plan or apply. - **Combine workspaces with `-var-file`** to keep environment-specific values out of the configuration logic. - **Name workspaces consistently** across all configurations: `dev`, `staging`, `prod` rather than mixing `development`, `stg`, `production`. - **Forgetting that `destroy` only affects the current workspace.** Running `terraform destroy` in the `dev` workspace does not touch `prod`. This is by design, but can be confusing. - **Not cleaning up unused workspaces.** Old workspaces with lingering state can be confusing. Delete the workspace after destroying its resources. ## Quick Example ```hcl # environments/dev.tfvars instance_type = "t3.micro" instance_count = 1 domain = "dev.example.com" enable_waf = false ``` ```hcl # environments/prod.tfvars instance_type = "t3.medium" instance_count = 3 domain = "example.com" enable_waf = true ```
skilldb get terraform-skills/Terraform WorkspacesFull skill: 265 linesWorkspaces — Terraform
You are an expert in using Terraform workspaces and environment management strategies for infrastructure as code.
Overview
Terraform workspaces allow a single configuration to manage multiple distinct instances of infrastructure, each with its own state file. The most common use case is managing separate environments (development, staging, production) from the same codebase. Workspaces are a native Terraform feature, but they are not always the right choice. Understanding when to use workspaces versus directory-based separation is critical.
Core Concepts
CLI Workspaces
Every Terraform configuration starts with a default workspace. Additional workspaces create isolated state files that share the same configuration and backend.
# List all workspaces
terraform workspace list
# Create and switch to a new workspace
terraform workspace new staging
# Switch to an existing workspace
terraform workspace select production
# Show the current workspace
terraform workspace show
# Delete a workspace (must not be the current one)
terraform workspace delete staging
Workspace-Aware Configuration
The current workspace name is available via terraform.workspace.
locals {
environment = terraform.workspace
instance_type = {
dev = "t3.micro"
staging = "t3.small"
prod = "t3.medium"
}
instance_count = {
dev = 1
staging = 2
prod = 3
}
}
resource "aws_instance" "app" {
count = local.instance_count[local.environment]
ami = data.aws_ami.ubuntu.id
instance_type = local.instance_type[local.environment]
tags = {
Name = "app-${local.environment}-${count.index}"
Environment = local.environment
}
}
Backend State Separation
With the S3 backend, workspaces create separate state files under env:/ prefix:
s3://my-terraform-state/
env:/
dev/
services/api/terraform.tfstate
staging/
services/api/terraform.tfstate
prod/
services/api/terraform.tfstate
Implementation Patterns
Variable Files per Workspace
Rather than using maps keyed by workspace name, use .tfvars files per environment.
# Directory structure
environments/
dev.tfvars
staging.tfvars
prod.tfvars
main.tf
variables.tf
outputs.tf
# environments/dev.tfvars
instance_type = "t3.micro"
instance_count = 1
domain = "dev.example.com"
enable_waf = false
# environments/prod.tfvars
instance_type = "t3.medium"
instance_count = 3
domain = "example.com"
enable_waf = true
# Apply for a specific environment
terraform workspace select prod
terraform apply -var-file=environments/prod.tfvars
Workspace-Based Naming
resource "aws_s3_bucket" "data" {
bucket = "myapp-${terraform.workspace}-data"
}
resource "aws_db_instance" "main" {
identifier = "myapp-${terraform.workspace}-db"
instance_class = var.db_instance_class
allocated_storage = var.db_storage_gb
# Protect production from accidental deletion
deletion_protection = terraform.workspace == "prod"
skip_final_snapshot = terraform.workspace != "prod"
tags = {
Environment = terraform.workspace
}
}
Directory-Based Alternative (Recommended for Large Teams)
For environments with significantly different configurations or different access controls, separate directories are often better than workspaces.
infrastructure/
modules/
vpc/
ecs/
rds/
environments/
dev/
main.tf # Calls shared modules with dev-specific values
backend.tf # Points to dev state file
terraform.tfvars
staging/
main.tf
backend.tf
terraform.tfvars
prod/
main.tf
backend.tf
terraform.tfvars
# environments/prod/backend.tf
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "prod/main/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
# environments/prod/main.tf
module "vpc" {
source = "../../modules/vpc"
cidr_block = "10.0.0.0/16"
environment = "prod"
project = "myapp"
}
module "ecs" {
source = "../../modules/ecs"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
# Prod-specific: larger instances, more capacity
instance_type = "m5.large"
desired_capacity = 6
environment = "prod"
}
Terragrunt for DRY Environment Management
Terragrunt is a popular wrapper that reduces duplication when using directory-based environments.
# terragrunt.hcl at the environment level
terraform {
source = "../../modules//ecs"
}
include "root" {
path = find_in_parent_folders()
}
inputs = {
instance_type = "m5.large"
desired_capacity = 6
environment = "prod"
}
Best Practices
- Use workspaces for simple, symmetrical environments where the only differences are sizing and naming.
- Use directory-based separation when environments have different providers, modules, or access controls.
- Never use the
defaultworkspace for real infrastructure. It is easy to forget which workspace is active and apply to the wrong environment. - Automate workspace selection in CI/CD. Scripts should explicitly
terraform workspace selectbefore every plan or apply. - Combine workspaces with
-var-fileto keep environment-specific values out of the configuration logic. - Name workspaces consistently across all configurations:
dev,staging,prodrather than mixingdevelopment,stg,production.
Core Philosophy
Workspaces solve a specific problem: running the same Terraform configuration against multiple independent instances of infrastructure, typically different environments. They are not a general-purpose environment management tool, and understanding their boundaries is more important than understanding their features. Workspaces share configuration, backend credentials, and provider access, which means they provide state isolation but not security isolation.
The decision between workspaces and directory-based separation should be driven by how similar your environments are. If dev, staging, and production differ only in sizing and naming, workspaces with per-environment .tfvars files are clean and efficient. If environments have different providers, different modules, different access controls, or significantly different architectures, directory-based separation (or Terragrunt) gives you the flexibility that workspaces cannot.
The most dangerous property of workspaces is their invisibility. There is no visual indicator in your terminal, no prompt change, and no confirmation dialog when you switch workspaces. A developer who forgets to run terraform workspace select staging before terraform apply can silently destroy production infrastructure. Every workflow that uses workspaces must include explicit workspace verification as a non-negotiable safety step.
Anti-Patterns
-
Using the
defaultworkspace for real infrastructure. Thedefaultworkspace exists as a starting point, not a destination. Using it for actual environments makes it easy to accidentally apply to the wrong place, becausedefaultis where Terraform starts and where careless operators end up. -
Workspace-keyed maps for every configuration value. Defining
local.instance_type = { dev = "t3.micro", staging = "t3.small", prod = "t3.medium" }[terraform.workspace]for every variable embeds environment logic directly in the configuration. Use per-environment.tfvarsfiles instead, which are easier to review, diff, and manage. -
Assuming workspaces provide access control. Anyone with credentials to the backend can switch to any workspace and apply. If developers should not be able to apply to production, workspaces alone do not enforce that. Use separate AWS accounts, IAM policies, or CI-only access for production.
-
Using workspaces for fundamentally different architectures. If production has a multi-AZ RDS cluster and WAF while dev has a single-AZ instance and no WAF, the conditional logic required to make one configuration serve both becomes a maintenance nightmare. Separate directories are clearer.
-
Not verifying the active workspace before applying. Running
terraform applywithout first confirming which workspace is active is the most common cause of cross-environment incidents. CI pipelines should explicitly select and verify the workspace, and local workflows should include a workspace check.
Common Pitfalls
- Applying to the wrong workspace. This is the single biggest risk. Always check
terraform workspace showbefore applying, especially in production. CI pipelines should fail if the workspace does not match the expected environment. - Assuming workspaces provide access isolation. Workspaces share the same backend credentials. If a developer can apply to
dev, they can switch toprodand apply there too. Use IAM policies on the backend or separate AWS accounts for real isolation. - Diverging environments when using workspaces. Since all workspaces share the same configuration, you cannot have a resource that exists only in production without conditional logic. This can become messy as environments diverge.
- Forgetting that
destroyonly affects the current workspace. Runningterraform destroyin thedevworkspace does not touchprod. This is by design, but can be confusing. - Not cleaning up unused workspaces. Old workspaces with lingering state can be confusing. Delete the workspace after destroying its resources.
- Using
terraform.workspacein backend configuration. The backend block does not support interpolation. You cannot dynamically set the state key based on the workspace name inside the backend block itself; the backend handles workspace separation automatically.
Install this skill directly: skilldb add terraform-skills
Related Skills
Terraform Basics
Terraform fundamentals including providers, resources, data sources, and core workflow
Terraform CI CD Pipeline
Running Terraform in CI/CD pipelines with automated plan, approval gates, and safe apply workflows
Terraform Modules
Terraform module design, composition, versioning, and reuse patterns
Terraform Provisioners
Terraform provisioners, null resources, triggers, and when to use alternatives
Terraform State Management
Remote state backends, state locking, import, migration, and state surgery techniques
Terraform Testing
Testing Terraform configurations with native tests, Terratest, plan validation, and policy-as-code