Terraform workspaces let you use the same Terraform configuration with multiple separate state instances. The most common reason is to represent different environments such as dev, staging, and prod without duplicating all of your .tf files.
A workspace does not create a new directory or a new configuration. It changes which state Terraform is currently using for the same configuration.
That means one codebase can be applied more than once while keeping each environment's state separate.
This is the basic idea:
.tf filesFor beginners, workspaces seem like a simple multi-environment solution. They can be useful, but they also have important limitations, so it is worth understanding them carefully.
When you initialize a new Terraform working directory, Terraform starts in a workspace named default.
You can see the current workspace with:
terraform workspace showOn a fresh project, the result is typically:
defaultIf you do nothing else and run terraform apply, Terraform stores the state in the default workspace.
That makes default the baseline state context for the configuration.
Workspaces can be helpful when you want to reuse the same configuration with different data or naming.
Examples:
dev and stagingBecause each workspace has its own state, Terraform can distinguish between the resources belonging to one environment and the resources belonging to another.
Terraform provides several CLI commands for managing workspaces.
terraform workspace listLists all workspaces in the current backend or local working directory.
terraform workspace listExample output:
* default
dev
stagingThe asterisk marks the currently selected workspace.
terraform workspace showShows the currently selected workspace.
terraform workspace showUseful in scripts, CI/CD, and troubleshooting.
terraform workspace newCreates a new workspace and usually switches to it.
terraform workspace new devAfter this command, Terraform has a new empty state for the dev workspace.
terraform workspace selectSwitches to an existing workspace.
terraform workspace select stagingAfter selection, future plan and apply operations use that workspace's state.
terraform workspace deleteDeletes a workspace.
terraform workspace delete devThis command removes the workspace state context, so it should be used carefully. Terraform generally expects the workspace not to contain active managed resources unless they are destroyed or migrated first.
A simple learning workflow might look like this:
terraform init
terraform workspace new dev
terraform apply
terraform workspace new staging
terraform apply
terraform workspace list
terraform workspace select dev
terraform planThe important idea is that Terraform is reusing the same configuration but with different state files behind the scenes.
terraform.workspace in ConfigurationTerraform exposes the currently selected workspace through the built-in value terraform.workspace.
This allows workspace-aware naming and logic.
Example:
resource "aws_s3_bucket" "logs" {
bucket = "myapp-${terraform.workspace}-logs"
}If the current workspace is dev, the bucket name becomes myapp-dev-logs. In prod, it becomes myapp-prod-logs.
This is one of the most common beginner uses of workspaces.
You can also use terraform.workspace in expressions.
Example:
locals {
instance_type = terraform.workspace == "prod" ? "t3.medium" : "t3.micro"
}Or:
resource "aws_instance" "web" {
count = terraform.workspace == "prod" ? 2 : 1
ami = data.aws_ami.ubuntu.id
instance_type = local.instance_type
}This allows a single codebase to scale environment size based on the active workspace.
For small projects, it is nice to avoid maintaining separate copies of nearly identical code. Workspace-aware expressions help the same configuration adapt to each environment.
If too much conditional logic accumulates, a single configuration may become crowded with environment-specific branches. At that point, workspaces may stop being the simplest solution.
A practical pattern is to keep one variable file per workspace.
Example naming convention:
dev.tfvars
staging.tfvars
prod.tfvarsIn automation, you can pair the selected workspace with a matching variable file.
The concept is often described as using ${terraform.workspace}.tfvars as the naming pattern, even though the file selection itself is done by the CLI command or automation workflow.
Example:
terraform workspace select dev
terraform plan -var-file=dev.tfvarsIn CI/CD, scripts often compute the file name from the workspace so the right inputs are loaded for the selected environment.
This is a clean way to separate values such as:
One of the most important facts about workspaces is this:
Each workspace has its own state file or state namespace.
That means Terraform keeps separate records of managed resources for:
defaultdevstagingprodand any other workspaces you create.
This separation is what prevents Terraform from confusing dev resources with prod resources when using the same code.
Terraform decisions are based on state. If multiple environments shared exactly the same state accidentally, Terraform would not be able to model them independently.
Workspaces solve that by splitting state while reusing code.
The exact storage behavior depends on the backend. With local state, workspace state is stored locally in different paths. With remote backends, the backend typically stores workspace state in separate logical locations.
The details vary, but the high-level idea remains the same: each workspace has its own isolated state context.
This is where many beginner misunderstandings happen.
Terraform workspaces are useful, but they are not a full replacement for separate state design, separate backends, or separate repositories in every situation.
Workspaces assume a mostly shared configuration. If environments differ dramatically, the configuration can become filled with conditionals and exceptions.
Workspaces usually separate state within the same backend configuration. Some organizations want stronger isolation, separate credentials, or separate backend stores per environment.
If an operator selects the wrong workspace and runs apply, changes may hit the wrong environment. That risk becomes more serious in production.
Large systems often prefer dedicated state, separate pipelines, separate directories, or separate repositories for stronger boundaries between environments.
This is a common architecture decision.
There is no universal answer. The right choice depends on team size, risk tolerance, environment differences, and governance requirements.
Workspaces are a good fit when:
Examples:
Workspaces are usually a weaker fit when:
In those cases, separate root modules, directories, or repositories often give clearer operational boundaries.
Here is a small example using terraform.workspace for naming and sizing.
locals {
instance_type = terraform.workspace == "prod" ? "t3.medium" : "t3.micro"
name_prefix = "myapp-${terraform.workspace}"
}
resource "aws_s3_bucket" "logs" {
bucket = "${local.name_prefix}-logs"
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = local.instance_type
tags = {
Name = "${local.name_prefix}-web"
}
}The same configuration behaves differently based on the current workspace while still staying relatively simple.
If every workspace needs many exceptions, the design may be strained.
Use terraform.workspace in naming to avoid collisions across environments.
CI/CD should explicitly select the workspace and use the correct variable file rather than relying on manual operator memory.
If prod deserves stronger isolation, do not assume workspaces alone are enough.
Teams should know whether workspaces are the chosen isolation model or just a convenience for non-critical environments.
They do not. The configuration stays the same; only the state context changes.
Sometimes separate root modules or repos are the better architectural choice.
If names do not vary by workspace, you may create collisions in shared accounts.
They separate state, but they do not automatically provide all the organizational isolation some environments need.
Terraform workspaces are a practical way to reuse one configuration with multiple state instances. They are especially helpful for small, similar environments and learning scenarios where you want dev, staging, and test states without copying directories.
The key ideas to remember are:
default workspaceterraform.workspace lets configuration react to the selected workspaceUse workspaces when they simplify your setup. Avoid them when stronger isolation or significantly different environments demand a more explicit architecture.
<MCQExercise exercise={{ id: 'tf-workspaces-q1', title: 'Question 1: Core Concept', description: 'What do Terraform workspaces primarily provide?', options: [ { id: 'a', text: 'Multiple separate state contexts for the same Terraform configuration' }, { id: 'b', text: 'Automatic provider upgrades during terraform apply' }, { id: 'c', text: 'Built-in encryption for all state files and variables' }, { id: 'd', text: 'A way to publish modules to the Terraform Registry' }, ], correctId: 'a', explanation: 'Workspaces let the same configuration use different state instances, which is why they are often used for multiple similar environments.', wrongExplanations: { b: 'Provider upgrades are handled separately from workspaces.', c: 'Workspaces separate state, but they do not automatically encrypt everything.', d: 'Module publishing is unrelated to workspace management.', }, }} />
<MCQExercise exercise={{ id: 'tf-workspaces-q2', title: 'Question 2: Built-in Value', description: 'What does terraform.workspace represent inside Terraform configuration?', options: [ { id: 'a', text: 'The path to the current module directory' }, { id: 'b', text: 'The currently selected workspace name' }, { id: 'c', text: 'The name of the active provider alias' }, { id: 'd', text: 'The remote backend hostname' }, ], correctId: 'b', explanation: 'terraform.workspace is the built-in value that exposes the name of the currently selected workspace to your configuration.', wrongExplanations: { a: 'Module paths are available through path-related values, not terraform.workspace.', c: 'Provider aliases are configured separately and are not exposed through terraform.workspace.', d: 'Backend host details are not what terraform.workspace represents.', }, }} />
<MCQExercise exercise={{ id: 'tf-workspaces-q3', title: 'Question 3: Limitations', description: 'Why are workspaces not always a replacement for separate directories or repositories?', options: [ { id: 'a', text: 'Because Terraform forbids workspaces in AWS projects' }, { id: 'b', text: 'Because workspaces only work with local state files' }, { id: 'c', text: 'Because some environments need stronger isolation or significantly different configurations' }, { id: 'd', text: 'Because workspaces cannot be used with variables' }, ], correctId: 'c', explanation: 'Workspaces are helpful for similar environments, but they may not provide enough isolation or architectural clarity for very different or high-risk environments.', wrongExplanations: { a: 'Terraform does not forbid workspaces in AWS projects.', b: 'Workspaces can also be used with remote backends; they are not limited to local state.', d: 'Variables work perfectly well with workspaces.', }, }} />
Generally no, despite it being the obvious first idea. Workspaces share one configuration and one backend, so environments cannot differ structurally, the same credentials reach all of them, and it is uncomfortably easy to run an apply against production because you forgot to check terraform workspace show. Separate directories or separate repositories with separate backends give real isolation, allow environments to differ, and let production credentials be scoped to production alone.
terraform workspace show prints it, and terraform workspace list marks the current one with an asterisk. Because nothing else in the output reminds you, showing it in your shell prompt is worth setting up if you use workspaces regularly. Within configuration, terraform.workspace returns the name, commonly used to derive resource names or select a map of per-workspace values.
terraform.workspace is available anywhere as a string, so name = "app-${terraform.workspace}" gives per-workspace resource names. A common pattern is a map of settings keyed by workspace with a lookup and a default, which lets a sandbox use small instances while a shared workspace uses larger ones. Keep this light. Heavy branching on workspace name is a sign the environments should be separate configurations.
In the same backend, under a separate path. With the S3 backend, non-default workspaces are stored under a env:/<workspace>/ prefix alongside your configured key, while default uses the key directly. They are isolated from each other but share the bucket and its access controls, which is precisely why workspaces do not give production the credential separation it should have.
Destroy its resources first with terraform destroy while it is selected, then switch away and run terraform workspace delete <name>. Terraform refuses to delete a workspace whose state is non-empty unless you force it, and forcing leaves orphaned infrastructure nobody is tracking. For ephemeral preview environments, wire the destroy-then-delete sequence into the pipeline that tears the environment down.
Configure the Terraform AWS provider, authenticate without long-lived keys using assume role and OIDC, manage regions and accounts, and tag everything.
Build a custom AWS VPC with Terraform: public and private subnets, an internet gateway, route tables and a NAT gateway, explained step by step.
Launch an EC2 web server with Terraform: add a security group, find the latest Ubuntu AMI, create a key pair, write user data and attach an Elastic IP.