Introduction to Terraform Workspaces
Terraform workspaces enable you to manage multiple states from the same configuration directory. Instead of maintaining separate directory structures for dev, staging, and production environments, you can use a single set of .tf files and switch between workspaces to manage different infrastructure instances.
What Are Workspaces?
A workspace is a separate state file for the same configuration:
Configuration: main.tf (single copy)
State Files:
├─ default
├─ dev
├─ staging
└─ production
Each workspace maintains its own .tfstate file, allowing you to deploy the same infrastructure to multiple environments from a single configuration.
Workspace Basics
Viewing Workspaces
# List all workspaces
terraform workspace list
# Output example:
# default
# * dev
# staging
# productionThe asterisk (*) indicates the currently selected workspace.
Creating Workspaces
# Create a new workspace
terraform workspace new dev
terraform workspace new staging
terraform workspace new production
# Verify creation
terraform workspace listState files are created automatically when you apply in a new workspace.
Selecting a Workspace
# Switch to a different workspace
terraform workspace select staging
# Verify current workspace
terraform workspace show
# Output: stagingDeleting Workspaces
# Delete a workspace (must not be current)
terraform workspace delete staging
# Error if you try to delete current workspace
terraform workspace delete production
# Error: Cannot delete currently selected workspace
# Solution: Switch first
terraform workspace select dev
terraform workspace delete productionUsing Workspaces in Configuration
Accessing Current Workspace Name
The terraform.workspace variable contains the current workspace name:
variable "environment" {
type = string
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-server-${terraform.workspace}"
Environment = terraform.workspace
}
}
output "instance_name" {
value = "Instance deployed to ${terraform.workspace} environment"
}Apply to different workspaces:
# Deploy to dev
terraform workspace select dev
terraform apply
# Deploy to production with same code
terraform workspace select production
terraform apply
# Different state files, different instances createdConditional Configuration by Workspace
variable "instance_type" {
type = string
# Different defaults per workspace
default = terraform.workspace == "production" ? "t3.large" : "t3.micro"
}
variable "replica_count" {
type = number
default = terraform.workspace == "production" ? 3 : 1
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = var.instance_type
tags = {
Name = "web-${terraform.workspace}"
Tier = terraform.workspace
}
}
resource "aws_autoscaling_group" "web" {
name = "asg-${terraform.workspace}"
min_size = terraform.workspace == "production" ? 3 : 1
max_size = terraform.workspace == "production" ? 10 : 2
desired_capacity = terraform.workspace == "production" ? 3 : 1
}Map-Based Configuration
For more complex environment differences:
locals {
workspace_config = {
dev = {
instance_type = "t3.micro"
instance_count = 1
monitoring = false
}
staging = {
instance_type = "t3.small"
instance_count = 2
monitoring = true
}
production = {
instance_type = "t3.large"
instance_count = 3
monitoring = true
}
}
current = local.workspace_config[terraform.workspace]
}
resource "aws_instance" "web" {
count = local.current.instance_count
ami = "ami-0c55b159cbfafe1f0"
instance_type = local.current.instance_type
tags = {
Name = "web-${terraform.workspace}-${count.index + 1}"
}
}
resource "aws_cloudwatch_metric_alarm" "cpu" {
count = local.current.monitoring ? 1 : 0
alarm_name = "high-cpu-${terraform.workspace}"
metric_name = "CPUUtilization"
threshold = 80
}Workspace Naming Conventions
Recommended Patterns
Simple Environment Names:
dev
staging
production
Versioned Environments:
production-1.0
production-1.1
staging-test-feature-x
Multi-Region:
prod-us-east-1
prod-eu-west-1
dev-us-east-1
Application-Specific:
app1-dev
app1-prod
app2-dev
app2-prod
Workspace Use Cases
Case 1: Environment Isolation
Single configuration, multiple environments:
# Initialize once
terraform init
# Deploy to dev
terraform workspace select dev
terraform apply -var-file=dev.tfvars
# Deploy to production
terraform workspace select production
terraform apply -var-file=prod.tfvars
# Both use same main.tf, different state files, different variablesCase 2: Feature Testing
Test infrastructure changes before production:
# Create temp workspace for testing
terraform workspace new test-new-feature
# Apply experimental changes
terraform apply -var-file=test.tfvars
# Test the infrastructure
# If successful, merge to main config
# If failed, delete without affecting production
terraform workspace delete test-new-featureCase 3: Parallel Deployments
Deploy multiple instances for load testing:
terraform workspace new load-test-1
terraform workspace new load-test-2
terraform workspace new load-test-3
# Each is independent, each has own state
terraform apply # Creates infrastructure in all workspacesWhen NOT to Use Workspaces
Limitations of Workspaces
1. Team Collaboration
- All workspaces stored in same backend directory
- No permission isolation between workspaces
- Developer in workspace A can destroy workspace B's infrastructure
2. Separate Tfvars
- Hard to maintain different variable files per workspace
- Risk of applying wrong variables to wrong environment
3. Backend Limitations
- All workspaces share same backend
- Network backend backends (S3, Azure) store all states together
- No namespace isolation
4. Drift Detection
- Hard to implement workspace-specific drift detection
- All workspaces subject to same refresh
When to Use Alternative Approaches
For Production Safety:
# Better: Use separate directories
├── dev/
│ ├── main.tf
│ └── terraform.tfvars
├── staging/
│ ├── main.tf
│ └── terraform.tfvars
└── production/
├── main.tf
└── terraform.tfvars
# Or use Terragrunt
├── terragrunt.hcl (root config)
└── live/
├── dev/
│ ├── vpc/
│ └── app/
├── staging/
└── production/For Multi-Team Environments:
Use separate Terraform projects with separate backends
- Team A manages: app-infrastructure
- Team B manages: database-infrastructure
- No shared state = no dependencies on teammate changes
Workspace Best Practices
1. Use Meaningful Names
# Good
terraform workspace new dev
terraform workspace new production
# Confusing
terraform workspace new ws1
terraform workspace new ws22. Document Workspace Purpose
# main.tf - Document workspace strategy
/*
Workspace Strategy:
- default: Legacy (being deprecated)
- dev: Development environment for active feature work
- staging: Pre-production testing (mirrors production config)
- production: Live customer-facing infrastructure
Always apply staging changes before production.
*/3. Prevent Accidental Production Changes
variable "allow_production_destroy" {
type = bool
default = false
validation {
condition = terraform.workspace != "production" || !var.allow_production_destroy
error_message = "Cannot destroy production without explicit override"
}
}4. Use tfvars Files
# Structure
├── main.tf
├── variables.tf
├── terraform.tfvars.dev
├── terraform.tfvars.staging
└── terraform.tfvars.production
# Usage
terraform workspace select dev
terraform apply -var-file=terraform.tfvars.dev5. Automate Workspace Selection
#!/bin/bash
# bin/deploy.sh
ENVIRONMENT=$1
if [ -z "$ENVIRONMENT" ]; then
echo "Usage: $0 {dev|staging|production}"
exit 1
fi
terraform workspace select $ENVIRONMENT || \
terraform workspace new $ENVIRONMENT
terraform apply -var-file=terraform.tfvars.$ENVIRONMENTWorkspace State Files
Local Backend
When using local state, workspaces create a directory structure:
terraform.tfstate.d/
├── dev/
│ └── terraform.tfstate
├── staging/
│ └── terraform.tfstate
└── production/
└── terraform.tfstate
Remote Backend (S3 Example)
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "infrastructure/terraform.tfstate"
region = "us-east-1"
}
}S3 backend with workspaces:
s3://company-terraform-state/
└── infrastructure/
├── env:/dev/terraform.tfstate
├── env:/staging/terraform.tfstate
└── env:/production/terraform.tfstate
Viewing Workspace State
# List all resources in current workspace
terraform state list
# Show specific resource
terraform state show aws_instance.web
# Inspect state file directly
terraform state pull | jq .
# Compare workspaces
terraform state show module.example -out prod.state
terraform workspace select dev
terraform state show module.example -out dev.state
diff prod.state dev.stateMigration Examples
Migrating from Multiple Directories to Workspaces
# Before: Separate directories
├── dev/
│ └── main.tf
├── staging/
│ └── main.tf
└── production/
└── main.tf
# After: Single directory with workspaces
├── main.tf (configuration)
├── terraform.tfvars.dev
├── terraform.tfvars.staging
└── terraform.tfvars.production
# Migration steps
terraform init
terraform workspace new dev
terraform state pull > dev.tfstate # Import previous state
terraform state push dev.tfstate
# Repeat for other workspacesMigrating from Default to Named Workspaces
# Current state is in 'default' workspace
terraform state list
# Create new workspace and import
terraform workspace new production
terraform state list # Should be empty
# Copy state (may need manual steps depending on backend)
terraform state push DEFAULT_STATE_FILEWorkspace Gotchas
1. Default Workspace Can't Be Deleted
terraform workspace delete default
# Error: Cannot delete the currently selected workspace
# Solution: Workspaces are permanent once created2. Workspace-Specific Secrets Are Vulnerable
All workspaces share same Terraform code, so:
# Bad: Secret in code, affects all workspaces
variable "db_password" {
default = "secret123" # Visible in git!
}
# Better: Use environment variables or vaults
variable "db_password" {
type = string
sensitive = true
# Pass via: export TF_VAR_db_password=...
}3. Resource Naming Must Be Unique per Workspace
# Good: Includes workspace name
resource "aws_db_instance" "main" {
identifier = "db-${terraform.workspace}-primary"
}
# Bad: Not unique per workspace (will conflict)
resource "aws_db_instance" "main" {
identifier = "db-primary" # Same name in all workspaces!
}Summary
Workspaces are useful for: ✅ Simple multi-environment management ✅ Quick testing without separate directories ✅ Feature branch infrastructure ✅ Development team separation
Consider alternatives for: ❌ Large teams (use separate Terraform projects) ❌ Strict environment isolation (use separate directories) ❌ Production safety (use directory structure + code review) ❌ Complex multi-cloud deployments (use Terragrunt)