Skip to main content
Technology & EngineeringTerraform265 lines

Terraform Workspaces

Using Terraform workspaces and alternative patterns for managing multiple environments

Quick Summary32 lines
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 lines
Paste into your CLAUDE.md or agent config

Workspaces — 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 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.

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 default workspace for real infrastructure. The default workspace exists as a starting point, not a destination. Using it for actual environments makes it easy to accidentally apply to the wrong place, because default is 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 .tfvars files 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 apply without 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 show before 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 to prod and 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 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.
  • Using terraform.workspace in 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

Get CLI access →