Workspace #
Terraform workspaces allow a single configuration directory to manage several separate states. Instead of duplicating directories for every environment, you stay in one place and switch “state slots” with simple commands. That sounds elegant, and for certain use cases it truly is — but there are trade-offs to understand before deciding workspaces are the right solution for your multi-environment setup.
flowchart TD
A["terraform\nworkspace"] --> B["default"]
A --> C["dev"]
A --> D["staging"]
A --> E["production"]
B --> F["state: default.tfstate"]
C --> G["state: dev.tfstate"]
D --> H["state: staging.tfstate"]
E --> I["state: production.tfstate"]
style A fill:#3b82f6,stroke:#1e40af,color:#fff
style B fill:#6b7280,stroke:#4b5563,color:#fff
style C fill:#10b981,stroke:#059669,color:#fff
style D fill:#f59e0b,stroke:#d97706,color:#fff
style E fill:#ef4444,stroke:#dc2626,color:#fffHow Workspaces Work #
By default, Terraform works in a workspace named default. When you create a new workspace, Terraform creates a separate state file for that workspace — but all workspaces use the same .tf configuration.
STATE STRUCTURE WITH WORKSPACES (S3 backend):
s3://my-terraform-state/
├── terraform.tfstate ← the "default" workspace
└── env:/
├── dev/
│ └── terraform.tfstate ← the "dev" workspace
├── staging/
│ └── terraform.tfstate ← the "staging" workspace
└── production/
└── terraform.tfstate ← the "production" workspace
All workspaces use the SAME .tf configuration.
The only difference is the state file.
Basic Workspace Commands #
# List all workspaces and the active one (marked with *)
terraform workspace list
# Output:
# default
# * dev ← the currently active workspace
# Create a new workspace
terraform workspace new staging
terraform workspace new production
# Switch to another workspace
terraform workspace select production
# Show the active workspace
terraform workspace show
# Output: production
# Delete a workspace (must be empty / no resources)
terraform workspace delete dev
Using terraform.workspace in the Configuration
#
The active workspace value can be accessed in the configuration via terraform.workspace. This is what allows one configuration to behave differently in each workspace.
# Using terraform.workspace directly
resource "aws_instance" "web" {
# Different instance sizes per workspace
instance_type = terraform.workspace == "production" ? "t3.medium" : "t3.micro"
tags = {
Name = "web-${terraform.workspace}"
Environment = terraform.workspace
}
}
# A cleaner pattern: a map lookup based on the workspace
locals {
# Per-workspace configuration in one place
workspace_config = {
dev = {
instance_type = "t3.micro"
instance_count = 1
db_instance_class = "db.t3.micro"
db_multi_az = false
db_backup_retention = 0
deletion_protection = false
}
staging = {
instance_type = "t3.small"
instance_count = 2
db_instance_class = "db.t3.small"
db_multi_az = false
db_backup_retention = 7
deletion_protection = false
}
production = {
instance_type = "t3.medium"
instance_count = 3
db_instance_class = "db.r5.large"
db_multi_az = true
db_backup_retention = 30
deletion_protection = true
}
}
# Get the configuration for the active workspace
config = local.workspace_config[terraform.workspace]
}
resource "aws_instance" "web" {
count = local.config.instance_count
instance_type = local.config.instance_type
tags = {
Name = "web-${terraform.workspace}-${count.index + 1}"
Environment = terraform.workspace
}
}
resource "aws_db_instance" "main" {
instance_class = local.config.db_instance_class
multi_az = local.config.db_multi_az
backup_retention_period = local.config.db_backup_retention
deletion_protection = local.config.deletion_protection
}
flowchart LR
A["Same Code\nWorkspace A"] -->|"terraform apply"| B["State A\nResources A"]
A -->|"terraform apply"| C["State B\nResources B"]
style A fill:#3b82f6,stroke:#1e40af,color:#fff
style B fill:#10b981,stroke:#059669,color:#fff
style C fill:#f59e0b,stroke:#d97706,color:#fffWorkspace Limitations #
Workspaces sound like the perfect solution, but there are limitations to understand.
WORKSPACE LIMITATIONS:
1. ONE BACKEND FOR ALL WORKSPACES
All workspaces in the same directory use
the same backend (same S3 bucket, same region).
Can't use a different AWS account per workspace.
→ Not ideal for a multi-account strategy
2. THE CONFIGURATION MUST HANDLE ALL WORKSPACES
One .tf file must be valid for all workspaces.
If production needs resources that dev doesn't have,
you need conditional logic that can get complicated.
→ The configuration can fill up with conditionals
3. EASY TO FORGET THE ACTIVE WORKSPACE
There's no clear visual mechanism showing
which workspace is active.
terraform apply in the production workspace while you
think you're in dev → disaster.
4. CAN'T HAVE DIFFERENT PROVIDER CONFIGS
All workspaces use the same provider configuration.
Can't use a different region or assume_role
per workspace without workarounds.
5. NO ACCESS ISOLATION
Anyone with access to the directory can apply to any workspace.
There's no IAM-level control per workspace.
When Workspaces Are the Right Choice #
WORKSPACES ARE GOOD FOR:
✓ Ephemeral environments — per-PR, per-feature, per-developer
(created and removed often, no strict isolation needed)
✓ Environments that are technically very similar
(same provider, same region, same account)
✓ Small teams with a high level of trust
✓ Proof-of-concept or experiments wanting a quick setup
WORKSPACES ARE NOT GOOD FOR:
✗ Production that needs a separate AWS account
✗ Environments with very different configurations
✗ Large teams whose access needs to be controlled per-environment
✗ Cases where full isolation is a compliance requirement
Safe Workspace Workflows #
Because workspaces are easy to forget, there are habits that help prevent mistakes.
# GOOD HABIT: Always check the workspace before applying
# Show the active workspace in the shell prompt (add to .bashrc/.zshrc)
# Example for zsh with oh-my-zsh — add the workspace to the prompt
parse_tf_workspace() {
if [ -d .terraform ]; then
workspace=$(terraform workspace show 2>/dev/null)
echo " [tf:$workspace]"
fi
}
RPROMPT='$(parse_tf_workspace)'
# Or: use aliases that always show the workspace first
alias tfplan='echo "Workspace: $(terraform workspace show)" && terraform plan'
alias tfapply='echo "Workspace: $(terraform workspace show)" && read -p "Continue? [yes/no]: " confirm && [ "$confirm" = "yes" ] && terraform apply'
# Example per-PR environment workflow with workspaces:
# Create a workspace for PR #123
terraform workspace new pr-123
terraform workspace select pr-123
# Deploy the environment for this PR
terraform apply -var="environment=pr-123" -auto-approve
# After the PR is merged, remove the environment
terraform destroy -auto-approve
terraform workspace select default
terraform workspace delete pr-123
Workspace vs Directory-Based: Decision Matrix #
SELECTION CRITERIA:
Workspace Directory
Number of environments < 4 >= 4
Configuration differences Small Large
Different teams Same Different
Different IAM No Yes
CI/CD pipelines Same Different
Blast radius isolation Low High
RECOMMENDATIONS:
- Small projects, 1-2 developers → Workspaces
- Large projects, many teams → Directory-based
- Strict compliance → Directory-based
flowchart TD
A["Choose a strategy"] --> B{"How many\nenvironments?"}
B -->|"< 4"| C{"Config\ndifferences?"}
B -->|">= 4"| D["Directory-based ✅"]
C -->|"Small"| E["Workspaces ✅"]
C -->|"Large"| D
style D fill:#e8f5e9,stroke:#2e7d32
style E fill:#e3f2fd,stroke:#1565c0Workspace Automation #
# Script to manage workspaces
#!/bin/bash
WORKSPACE=$1
# Create the workspace if it doesn't exist
terraform workspace select $WORKSPACE 2>/dev/null || terraform workspace new $WORKSPACE
# Apply with workspace-specific variables
terraform apply -var-file="environments/$WORKSPACE.tfvars" -auto-approve
Workspace Naming Conventions #
# BEST PRACTICE: Consistent naming conventions
# Format: {project}-{environment}-{component}
terraform workspace new myapp-dev-networking
terraform workspace new myapp-dev-compute
terraform workspace new myapp-staging-networking
terraform workspace new myapp-production-networking
# List all workspaces
terraform workspace list
# Select a workspace
terraform workspace select myapp-production-networking
# Show the current workspace
terraform workspace show
# Delete a workspace (careful!)
terraform workspace delete myapp-old-feature
# Use the workspace name in the configuration
locals {
# Parse the workspace name
parts = split("-", terraform.workspace)
environment = element(local.parts, 1) # dev, staging, production
name_prefix = "${local.parts[0]}-${local.environment}"
}
resource "aws_instance" "web" {
tags = {
Name = "${local.name_prefix}-web"
Environment = local.environment
}
}
Workspace vs Directory #
WORKSPACE PROS:
+ Simpler setup
+ Same code, different state
+ Quick switching
WORKSPACE CONS:
- Easy to accidentally apply the wrong env
- Same code = no env-specific customization
- No code review per environment
DIRECTORY PROS:
+ Clear separation
+ Per-environment customization
+ Independent code review
+ Easy to understand
DIRECTORY CONS:
+ More files to maintain
+ Code duplication risk
+ Need shared modules
RECOMMENDATION: Use directory-based
for production, workspaces for dev/testing
Workspace CLI Commands #
# Workspace management commands
terraform workspace list # List all workspaces
terraform workspace show # Show the current workspace
terraform workspace select dev # Switch to the dev workspace
terraform workspace new staging # Create a new workspace
# Use the workspace in the configuration
resource "aws_instance" "web" {
instance_type = terraform.workspace == "production" ? "t3.large" : "t3.micro"
tags = {
Environment = terraform.workspace
}
}
# Script: switch and apply
#!/bin/bash
ENV=${1:-dev}
terraform workspace select "$ENV"
terraform apply -var-file="environments/$ENV.tfvars"
Summary #
- Workspaces separate state, not configuration — one
.tfdirectory, many state files in separate locations.terraform.workspacecan be used in the configuration for behavioral differences between workspaces — cleanest with the map lookup patternlocal.workspace_config[terraform.workspace].- Main limitations: one backend for all workspaces, no multi-account support, easy to forget the active workspace.
- Right for ephemeral environments (per-PR, per-feature) and small teams with very similar environments.
- Not right for production that needs AWS account isolation, controlled per-environment access, or very different configurations.
- Always show the active workspace in the shell prompt and add confirmation before
applyto prevent unnecessary mistakes.