Skip to content
Infrastructure as Code iac environments 4 min read

Workspaces

A Terraform CLI workspace lets a single configuration directory hold more than one independent state. Every workspace is the same code applied against its own state file, so you can stand up several parallel copies of the same infrastructure — one per developer, one per feature branch, or a quick staging clone — without duplicating any .tf files. They are cheap, built in, and require zero extra tooling, but they also have real limits that make them the wrong choice for environments that diverge in any meaningful way. This page covers how they work, the commands, and exactly where they fit.

What a workspace actually is

When you run terraform init against a configuration, Terraform creates a workspace called default. Every workspace shares the same code and the same backend, but stores its state under a separate key or path so the resources tracked in one workspace are completely isolated from another.

With a local backend, each non-default workspace gets its own file under terraform.tfstate.d/<name>/terraform.tfstate. With a remote backend such as S3, Terraform inserts the workspace name into the state path automatically — your configured key becomes env:/<workspace>/<key> for any workspace other than default. You do not manage these paths yourself; switching workspaces transparently switches the state Terraform reads and writes.

This is identical in OpenTofutofu workspace mirrors the commands below exactly.

The terraform.workspace value

Inside your configuration you can read the active workspace name through the terraform.workspace expression. This is the primary lever for making one config behave slightly differently per workspace, typically for naming and tagging:

resource "aws_s3_bucket" "uploads" {
  bucket = "devcraftly-uploads-${terraform.workspace}"

  tags = {
    Environment = terraform.workspace
    ManagedBy   = "terraform"
  }
}

A common pattern is to drive small per-workspace differences from a locals map keyed by the workspace name, falling back to a default:

locals {
  instance_type = {
    default = "t3.micro"
    staging = "t3.small"
    prod    = "t3.large"
  }

  selected_type = lookup(local.instance_type, terraform.workspace, "t3.micro")
}

resource "aws_instance" "api" {
  ami           = "ami-0c7217cdde317cfec"
  instance_type = local.selected_type

  tags = {
    Name = "api-${terraform.workspace}"
  }
}

Tip: Never let terraform.workspace silently default to prod. A lookup with a safe, low-cost fallback means an unrecognized or accidentally created workspace deploys something harmless rather than full production sizing.

Workspace commands

All workspace management happens through the terraform workspace subcommands. They operate on the backend configured by terraform init.

CommandWhat it does
terraform workspace listLists all workspaces; the active one is marked with *
terraform workspace new <name>Creates a workspace and switches to it
terraform workspace select <name>Switches to an existing workspace
terraform workspace showPrints the name of the current workspace
terraform workspace delete <name>Deletes a workspace (must be empty and not active)

A typical session:

terraform workspace new staging
terraform workspace list
terraform plan
terraform workspace select default

Output:

Created and switched to workspace "staging"!

You're now on a new, empty workspace. Workspaces isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.

  default
* staging

Switched to workspace "default".

Because a brand-new workspace starts with empty state, the first plan after creating one shows every resource as a fresh create — you are building a complete second copy, not reusing the first.

Where workspaces fit — and where they don’t

Workspaces shine when you need short-lived, near-identical clones of one configuration. They are a poor fit the moment environments need to differ structurally.

Good fits:

  • Ephemeral test or PR environments that are torn down quickly.
  • Per-developer sandboxes sharing one config and one backend.
  • Multi-region rollouts of a region-agnostic module where only a variable changes.

Poor fits:

  • Staging vs production that diverge. Different account boundaries, IAM, sizing, or feature flags become a tangle of terraform.workspace conditionals that obscure what each environment really is.
  • Strong blast-radius isolation. All workspaces live in the same backend — usually the same bucket and often the same cloud account — so a misconfigured backend or credential can touch every environment. Separate directories or backends give a far harder boundary.
  • Different variable files per environment. Workspaces do not load staging.tfvars automatically; you must remember to pass -var-file, and forgetting it applies whatever defaults exist.

Warning: It is dangerously easy to run apply against the wrong workspace. The active workspace is invisible unless you check it. Add terraform workspace show to your CI pipeline, or surface the workspace in your shell prompt, before any production apply.

Best practices

  • Use workspaces for ephemeral or identical copies; use separate directories or backends for environments that genuinely differ.
  • Drive per-workspace differences through a locals map plus lookup with a safe fallback, not scattered count/if conditionals.
  • Always confirm the active workspace with terraform workspace show before plan or apply, especially in automation.
  • Pair workspaces with explicit -var-file arguments rather than relying on the workspace name to imply configuration.
  • Keep production in its own backend (and ideally its own account) instead of a prod workspace beside dev, so a backend mistake cannot cascade.
  • Document the expected workspace names in the README so new contributors do not create stray ones.
Last updated June 14, 2026
Was this helpful?