Workspaces Are Isolated State Environments

You’ve got one Terraform config. You want to deploy it to dev, staging, and prod without copy-pasting into three separate directories. CLI workspaces let you do exactly this — they maintain separate state files for each named workspace, all from the same config directory.

Every Terraform project starts in the default workspace. Create a new workspace, you get a fresh, empty state file. Switch workspaces, Terraform operates against a completely different state.

One thing that confuses people early on: CLI workspaces and HCP Terraform workspaces are different things that share a name. CLI workspaces are a local state-isolation mechanism. HCP Terraform workspaces are full execution environments with their own variables, teams, and run history. Don’t conflate them — the exam will try to trip you up on this.

The Workspace Commands
# List all workspaces (* = current)
terraform workspace list

# Show the current workspace
terraform workspace show

# Create a new workspace
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod

# Switch to a workspace
terraform workspace select dev

# Delete a workspace (must not be current, must have empty state)
terraform workspace delete dev

In practice:

$ terraform workspace list
* default

$ terraform workspace new dev
Created and switched to workspace "dev"!

$ terraform workspace new prod
Created and switched to workspace "prod"!

$ terraform workspace list
  default
  dev
* prod

$ terraform workspace select dev
Switched to workspace "dev".
How State Is Stored Per Workspace

With a local backend, workspaces create a directory structure:

terraform.tfstate.d/
├── dev/
│   └── terraform.tfstate
├── staging/
│   └── terraform.tfstate
└── prod/
    └── terraform.tfstate
terraform.tfstate           # The "default" workspace state

With an S3 backend, each workspace gets its own key:

s3://my-tf-state/
├── env:/dev/terraform.tfstate
├── env:/staging/terraform.tfstate
└── env:/prod/terraform.tfstate

(The key format is: env:/<workspace_name>/<configured_key>)

Using the Workspace Name in Config

Access the current workspace name with terraform.workspace:

resource "aws_instance" "web" {
  instance_type = terraform.workspace == "prod" ? "t3.xlarge" : "t3.micro"

  tags = {
    Environment = terraform.workspace
    Name        = "${terraform.workspace}-web-server"
  }
}

resource "aws_s3_bucket" "data" {
  bucket = "myapp-${terraform.workspace}-data"
}

Deploy to dev → Instance: t3.micro, Bucket: myapp-dev-data Deploy to prod → Instance: t3.xlarge, Bucket: myapp-prod-data

Simple. Effective. No config duplication.

Workspace-Specific Variable Files

Combine workspaces with .tfvars for clean environment-specific configs:

# Use a var file named after the workspace
terraform apply -var-file="${terraform workspace show}.tfvars"

Or build the environment logic directly into your config with a map:

locals {
  env_config = {
    dev = {
      instance_type  = "t3.micro"
      instance_count = 1
      multi_az       = false
    }
    prod = {
      instance_type  = "t3.xlarge"
      instance_count = 3
      multi_az       = true
    }
  }

  config = local.env_config[terraform.workspace]
}

resource "aws_instance" "app" {
  count         = local.config.instance_count
  instance_type = local.config.instance_type
}

I’ve used both approaches. The map-in-code version keeps everything in one place, which is nice until you have 15 environment parameters and the locals block becomes an unreadable wall. At that point, switch to .tfvars files.

CLI Workspaces vs. Separate Directories

Two common patterns for multi-environment infrastructure:

Pattern 1: CLI Workspaces

project/
├── main.tf
├── variables.tf
└── outputs.tf

Three workspaces: dev, staging, prod. One codebase.

Pattern 2: Separate Directories

project/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   │   └── ...
│   └── prod/
│       └── ...
└── modules/
    └── app/
CLI WorkspacesSeparate Directories
State isolationYesYes
Config isolationNo — same configYes — fully independent
Drift between envsRisky — silent divergenceExplicit — you see the diff
Blast radius of mistakeHigherLower
SimplicityHigherLower

CLI workspaces work great for ephemeral environments — short-lived dev/test instances spun up from the same config. For prod vs. staging where environments genuinely differ (different module versions, different providers, fundamentally different resource sets), separate directories with shared modules give you better isolation. You’ll be happier debugging issues at 2 AM when prod and dev have clearly separate codepaths.

When Workspaces Break Down

CLI workspaces share the same config. If prod needs fundamentally different infrastructure from dev — different modules, extra resource types, a WAF that only exists in prod — you end up littering your config with conditionals:

# This is a sign workspaces are the wrong tool
resource "aws_cloudfront_distribution" "cdn" {
  count = terraform.workspace == "prod" ? 1 : 0
  ...
}

One of these? Fine. Four? Getting ugly. Ten? You’ve turned your Terraform config into a choose-your-own-adventure book. When it gets to that point, bite the bullet and split into separate directories. The short-term pain of restructuring is nothing compared to the ongoing pain of maintaining a config full of count = terraform.workspace == "prod" ? 1 : 0 scattered everywhere.

Also: you can’t rename a workspace. Want to change staging to pre-prod? Create the new workspace, re-apply everything, destroy the old one. Plan workspace names carefully.


Key Takeaways

terraform workspace delete fails if the workspace has resources in state. You have to terraform destroy first. Terraform won’t let you orphan managed resources, which is the right safety guardrail.

Here’s the one that burns people: the -var flags, -var-file, and TF_VAR_ environment variables are NOT workspace-aware. They apply to whichever workspace is currently selected. Switch workspaces and forget to update your var file? Congratulations, you just applied dev config to prod state. Every engineer makes this mistake exactly once. After that, they add terraform workspace show to their shell prompt.

Always check which workspace you’re in before applying. Make it a habit: terraform workspace show before every apply. Some teams add the workspace name to their shell prompt. Others write a wrapper script that prints it in bold red before confirmation. Whatever works — just don’t assume you’re in the right workspace.