Terraform Workflow

Workspaces & Environments

● Intermediate ⏱ 20 min read terraform

Most infrastructure exists in multiple environments: at minimum a staging area and production. Terraform workspaces give you a way to manage several environments from one configuration by giving each its own isolated state file, while sharing the same backend.

What Are Workspaces?

A workspace is a named slot for a state file within the same backend. Every Terraform configuration starts in the default workspace. When you create a new workspace, Terraform creates a separate state file for it — the configuration code stays the same, but the infrastructure tracked in state is completely independent.

ℹ️
Default workspace always exists

You cannot delete the default workspace. Most teams treat default as production or avoid it entirely in favour of explicitly named workspaces.

Workspaces solve a specific problem: you want to spin up an identical copy of infrastructure (e.g., a staging environment) without duplicating your .tf files. The tradeoff is that all environments share the same configuration code, which means any change applies to all of them (after separate applies).

Workspace Commands

All workspace operations go through terraform workspace:

CommandWhat it does
terraform workspace listList all workspaces; current one is marked with *
terraform workspace showPrint the name of the current workspace
terraform workspace new <name>Create a workspace and switch to it
terraform workspace select <name>Switch to an existing workspace
terraform workspace delete <name>Delete a workspace (must be empty — no managed resources)
# Start in default — create staging and production workspaces
terraform workspace new staging
# Created and switched to workspace "staging"!

terraform workspace new production
# Created and switched to workspace "production"!

terraform workspace list
#   default
#   staging
# * production

terraform workspace select staging
# Switched to workspace "staging".

After switching workspaces, the next plan or apply reads and writes the state for that workspace only. Operations on one workspace have no effect on any other.

State Isolation

On a local backend, Terraform stores workspace state files under terraform.tfstate.d/<workspace-name>/terraform.tfstate. On an S3 backend, the key is prefixed:

# S3 key for default workspace
env:/default/prod/networking/terraform.tfstate

# S3 key for staging workspace
env:/staging/prod/networking/terraform.tfstate

# S3 key for production workspace
env:/production/prod/networking/terraform.tfstate

The prefix is configurable via the workspace_key_prefix argument on the S3 backend (default: env:).

🧭
One DynamoDB table serves all workspaces

State locking uses the full S3 key as the DynamoDB LockID. You do not need separate tables per workspace — the same table handles locks for every workspace in the configuration.

Environment-Specific Config

The current workspace name is available in configuration as terraform.workspace. Use it to vary resource names, sizes, or counts per environment:

locals {
  env = terraform.workspace   # "staging" or "production"

  instance_type = {
    staging    = "t3.small"
    production = "m5.large"
  }

  min_capacity = {
    staging    = 1
    production = 3
  }
}

resource "aws_instance" "app" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = local.instance_type[local.env]

  tags = {
    Name        = "app-${local.env}"
    Environment = local.env
  }
}

resource "aws_autoscaling_group" "app" {
  min_size         = local.min_capacity[local.env]
  max_size         = local.min_capacity[local.env] * 4
  # ...
}

This pattern lets staging use cheaper resources without separate configuration files. The map lookup fails loudly if an unexpected workspace name is used — which is a feature, not a bug.

Variable Files per Workspace

Rather than embedding environment differences in locals, many teams use per-workspace .tfvars files and pass the right one at apply time:

# staging.tfvars
instance_type = "t3.small"
min_capacity  = 1
domain        = "staging.example.com"

# production.tfvars
instance_type = "m5.large"
min_capacity  = 3
domain        = "example.com"
# Apply with the matching var file
terraform workspace select staging
terraform apply -var-file="staging.tfvars"

terraform workspace select production
terraform apply -var-file="production.tfvars"

In CI/CD pipelines, derive the var file name from the workspace automatically:

#!/bin/bash
WORKSPACE=$(terraform workspace show)
terraform apply -var-file="${WORKSPACE}.tfvars" -auto-approve
⚠️
Don't commit *.tfvars files with secrets

Variable files often contain environment-specific values that are sensitive (API keys, database passwords). Add them to .gitignore and supply them through CI/CD secret management instead.

Workspaces & Remote Backends

All major backends support workspaces natively. The S3, GCS, Azure Blob, and Consul backends prefix the state key. HCP Terraform treats each workspace as a first-class object with its own settings, variables, and run history.

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "networking/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-locks"

    # workspace_key_prefix defaults to "env:" — override if needed
    workspace_key_prefix = "workspace"
  }
}

With this config and two workspaces, state files land at:

When Not to Use Workspaces

Workspaces are convenient but have real limitations. The Terraform documentation itself recommends against using workspaces for strong environment isolation:

⚠️
Workspaces share everything except state

All workspaces use the same provider credentials, the same backend access, and the same configuration code. If your staging and production environments need different IAM roles, different cloud accounts, or meaningfully different resource topologies — use separate root configurations instead of workspaces.

Use workspaces when…Use separate configs when…
Environments are structurally identical (just different sizes)Environments need different resource types or topologies
Same cloud account is acceptable for all envsEnvironments live in different accounts or subscriptions
You want quick ephemeral environments (feature branches)Production needs strict access controls separate from staging
Small team, simple infrastructureCompliance requirements mandate environment separation

A common real-world pattern: use workspaces for short-lived feature-branch environments, and use separate root configurations (with shared modules) for the permanent staging and production environments.

Key Takeaways