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:#fff

How 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:#fff

Workspace 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:#1565c0

Workspace 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 .tf directory, many state files in separate locations.
  • terraform.workspace can be used in the configuration for behavioral differences between workspaces — cleanest with the map lookup pattern local.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 apply to prevent unnecessary mistakes.

← Previous: Environment Types   Next: Directory Based →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact