G
GuideDevOps
Lesson 12 of 14

Terraform Workspaces

Part of the Terraform tutorial series.

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
#   production

The 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 list

State 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: staging

Deleting 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 production

Using 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 created

Conditional 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 variables

Case 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-feature

Case 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 workspaces

When 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 ws2

2. 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.dev

5. 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.$ENVIRONMENT

Workspace 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.state

Migration 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 workspaces

Migrating 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_FILE

Workspace Gotchas

1. Default Workspace Can't Be Deleted

terraform workspace delete default
# Error: Cannot delete the currently selected workspace
 
# Solution: Workspaces are permanent once created

2. 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)