Skip to content

Terraform Workspaces

A Terraform workspace lets one configuration produce several separate state files switched by name, but every workspace still runs through the exact same backend, the exact same provider configuration, and the exact same .tf code.

Instead of copy-pasting a whole directory per environment, workspaces let you keep a single set of .tf files and simply switch which named state file Terraform reads from and writes to:

Terminal window
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
terraform workspace list
# default
# * dev
# staging
# prod
terraform workspace select staging
terraform apply

terraform workspace new <name> creates a workspace and switches to it immediately. terraform workspace select <name> switches the active workspace without creating a new one. terraform workspace list shows every workspace that exists, with an asterisk marking the one currently active. The active workspace is tracked locally in the .terraform directory, and every plan or apply you run only ever touches that workspace’s own state — the dev state and the prod state are two entirely separate files, even though both come from the same main.tf.

Terraform exposes the active workspace name as a built-in value, terraform.workspace, which you can interpolate directly into resource names, labels, or a lookup map to change behavior per workspace without writing separate code for each one:

locals {
machine_types = {
dev = "e2-small"
staging = "e2-medium"
prod = "e2-standard-4"
}
}
resource "google_compute_instance" "app" {
name = "app-${terraform.workspace}"
machine_type = lookup(local.machine_types, terraform.workspace, "e2-small")
zone = "us-central1-a"
boot_disk {
initialize_params {
image = "debian-cloud/debian-12"
}
}
}

Switch to the prod workspace and this same resource block names the instance app-prod and sizes it as e2-standard-4. Switch to dev and the identical code produces app-dev sized as e2-small. Nothing about the .tf file itself changes between environments — only the active workspace does.

Here is the part that is easy to miss: the backend block, the provider "google" block, and every other line of .tf code are shared, unchanged, across all workspaces.

terraform {
backend "gcs" {
bucket = "acme-terraform-state"
prefix = "app"
}
}
provider "google" {
project = "acme-shared-project"
region = "us-central1"
}

Terraform automatically namespaces each workspace’s state object inside that same bucket so dev, staging, and prod do not overwrite one another’s state file, but the bucket itself, the project in the provider "google" block, and the credentials Terraform authenticates with are identical no matter which workspace is active. There is no hard boundary here at all — nothing stops someone from being in the dev workspace, believing they are about to change a disposable test instance, when in fact every workspace shares the same acme-shared-project GCP project and the same real prod-adjacent resources it contains. A wrong terraform workspace select followed by an apply is a genuinely easy mistake to make, and workspaces alone give you no way to prevent it.

flowchart TD
  config["Single main.tf + backend + provider google.project = acme-shared-project"]
  config -->|terraform workspace select dev| devState["dev state file"]
  config -->|terraform workspace select staging| stagingState["staging state file"]
  config -->|terraform workspace select prod| prodState["prod state file"]
  devState -.->|same backend, same project| shared["acme-shared-project"]
  stagingState -.->|same backend, same project| shared
  prodState -.->|same backend, same project| shared
One Terraform configuration and backend, three workspaces, three state files, one shared GCP project
What does a Terraform workspace actually give you a separate copy of
What is terraform.workspace typically used for
What is the specific real-world risk that Terraform workspaces introduce
Which of these stays identical across every workspace in a single configuration