Terraform Workspaces for Environments

Manage multiple environments with Terraform workspaces. Learn how to create, switch, and use workspaces to isolate state and variables for dev, staging, and production.

Focus: manage multiple environments with workspaces

Sponsored

You've probably been here: you copy a main.tf into a prod/ folder, tweak a few variables, and hope you remember to run terraform apply in the right directory. Then one day you forget to switch, and your dev database gets replaced by a prod configuration. This is exactly the pain that Terraform workspaces solve. Instead of duplicating code and juggling directories, workspaces let you manage dev, staging, and production environments from a single configuration, with isolated state files and separate variable values. By the end of this lesson, you'll be able to manage multiple environments with workspaces confidently — creating, switching, and using them to keep your infrastructure safe and your sanity intact.

The problem this lesson solves

Managing multiple environments — dev, staging, prod — is a classic infrastructure headache. Here's what goes wrong without a deliberate strategy:

  • Copy-paste drift: You duplicate your configuration into dev/ and prod/ folders. Then you fix a bug in dev/, but forget to apply the same fix to prod/. Over weeks, environments drift apart, and deployments become a game of whack-a-mole.
  • State collision: If you use the same Terraform state file for two environments, terraform apply in one can destroy or modify resources in the other. This is the fast path to a production outage.
  • Variable spaghetti: You scatter dev.tfvars and prod.tfvars everywhere, and every command needs a -var-file flag. Errors creep in when you pass the wrong file.
  • Fear of switching: Because everything is tangled, you're scared to run terraform plan in your production directory. You cross your fingers and hope the diff is small — but it never is.

Workspaces directly attack this problem. They give you named, isolated state files for the same configuration, plus a clean way to vary inputs per environment. You stop duplicating code and start managing environments as first-class citizens of one codebase.

Core concept / mental model

Think of a Terraform workspace as a separate folder for state, but without copying your code.

In your current directory, you have a single configuration — main.tf, variables.tf, outputs.tf. Without workspaces, Terraform stores the state in a single file (or a single key in your remote backend). With workspaces, you get multiple state files, all named after the workspace, but sharing the same configuration.

Here's a mental picture:

  • Your configuration is a template (the code).
  • Each workspace is a filled-in copy of that template, with its own state and often its own variable values.
  • When you run terraform apply, Terraform knows which workspace you're in and reads/writes only that workspace's state.

This means you can have:

  • dev workspace — instance_count = 1, env = "dev"
  • staging workspace — instance_count = 2, env = "staging"
  • prod workspace — instance_count = 5, env = "prod"

All from the same main.tf. The key to making this work is the terraform.workspace variable — a special value that always stores the name of the current workspace. You use it in your configuration to vary resource names, tags, and other values.

Definitions:

  • Workspace: A named container that holds its own state file. Terraform automatically creates a default workspace when you start.
  • terraform.workspace: A built-in variable that resolves to the current workspace name. Great for naming resources uniquely.
  • Backend: Where your state lives (local file or remote like S3). Each workspace gets its own state key inside that backend.

Pro tip: Workspaces are not a silver bullet. For complex environments (especially in teams), you might prefer directory-based environments with separate backends (e.g., envs/dev/, envs/prod/). We'll compare them in the next section.

How it works step by step

Here's the logical flow of using workspaces:

  1. Initialize your configuration with terraform init. This sets up the backend (local or remote).
  2. Create a workspace with terraform workspace new dev.
  3. Switch to a workspace with terraform workspace select dev.
  4. Provide environment-specific values — usually via terraform.tfvars files or CLI variables. You can even use the workspace name to conditionally set values.
  5. Run terraform plan and apply as usual. Terraform writes state to the workspace-specific backend location.

Cause → effect: When you switch workspaces, Terraform changes the state file path (in a remote backend) or the state filename (in local mode). The configuration code stays identical, but the state is isolated. This prevents cross-environment contamination.

Variable handling: Unlike state, variables are not automatically isolated per workspace. You need to explicitly use terraform.workspace or pass different variable files. A common pattern is to create dev.tfvars and prod.tfvars and run:

terraform apply -var-file="prod.tfvars"

In your code, you can also use conditional expressions based on terraform.workspace:

instance_type = terraform.workspace == "prod" ? "t3.large" : "t3.micro"

This is powerful but be careful: embedding too much logic in HCL can make your code hard to read. Prefer variable files where possible.

Hands-on walkthrough

Let's put this into practice with a simple but complete example: a set of AWS EC2 instances (or you can adapt to any provider).

Step 1: Set up your configuration

Create a directory and add these files:

# main.tf
provider "aws" {
  region = "us-east-1"
}

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = terraform.workspace == "prod" ? "t3.large" : "t3.micro"

  tags = {
    Name = "web-${terraform.workspace}"
    Env  = terraform.workspace
  }
}
# variables.tf
variable "instance_count" {
  description = "Number of instances to launch"
  type        = number
}
# dev.tfvars
instance_count = 1
# prod.tfvars
instance_count = 3

Step 2: Initialize and create workspaces

terraform init
terraform workspace new dev
terraform workspace new prod
terraform workspace list

Expected output:

*
default
dev
prod

The * indicates the current workspace (default). Now select dev:

terraform workspace select dev

Step 3: Apply in the dev workspace

terraform apply -var-file="dev.tfvars" -auto-approve

You'll see output showing one instance with name web-dev.

Step 4: Switch to prod and apply

terraform workspace select prod
terraform apply -var-file="prod.tfvars" -auto-approve

This time, three instances named web-prod appear — and they are distinct from the dev ones.

Step 5: Verify state isolation

terraform state list

In the prod workspace, only prod resources appear. Switch back to dev and run terraform state list — you'll see the dev resource list. The states are completely separate.

Step 6: Clean up

terraform workspace select prod
terraform destroy -auto-approve
terraform workspace select dev
terraform destroy -auto-approve
terraform workspace delete dev
terraform workspace delete prod

Always destroy resources in a workspace before deleting it, otherwise the state lingers.

Pro tip: Use terraform workspace show to display the current workspace name in scripts or CI. For example: if terraform workspace show | grep -q prod; then echo "Careful — prod!"; fi

Compare options / when to choose what

Workspaces are not the only way to handle multiple environments. Here's a comparison with the directory-based approach and multiple backends:

Approach Pros Cons Best for
Workspaces Single config, easy to switch, state isolation built-in Variable handling can be messy with many environments; state locking still per-workspace Small teams, rapid prototyping, simple environments
Directory-based (envs/dev, envs/prod) Full separation of state and code, can have different module versions, clearer variable files Duplication of code or heavy use of modules; more directories to manage Large teams, complex environments, stricter compliance
Multiple backends (separate S3 buckets) Complete isolation, per-environment permissions Requires more setup, still one config or copies Enterprise with strong security requirements

When to choose what:

  • Choose workspaces when you want to quickly create and test environments with minimal overhead — ideal for single-person projects or CI/CD pipelines that spin up temporary environments.
  • Choose directory-based when your environments have diverged significantly — e.g., prod uses a different module version or has extra resources — or when you have a large team that needs separate git reviews.

Workspaces are a great default for this lesson, but remember they are a tool, not a law.

Troubleshooting & edge cases

Issue: "Workspace already exists" when creating

Error: Workspace name already exists

You probably ran terraform workspace new dev twice. Fix: use terraform workspace list to see existing names, then terraform workspace select dev to switch.

Issue: "Error loading state: no such file or directory"

This happens when you try to apply in a workspace that hasn't been created yet. Fix: always create a workspace before applying. Use terraform workspace new <name> first.

Issue: Variables are not changing between workspaces

You set instance_type based on terraform.workspace, but the plan still shows the same value. Check: are you sure you're in the right workspace? Run terraform workspace show. Also confirm you're using terraform.workspace correctly — it's a string, so use quotes when comparing ("prod").

Edge case: Forgetting to destroy resources before deleting a workspace

If you run terraform workspace delete dev while dev still has resources, Terraform will orphan them (they remain in your cloud account but are no longer managed). This can cause unexpected costs. Fix: always terraform destroy before deleting a workspace.

Edge case: Remote backend and state locking

If you use an S3 backend, each workspace gets a distinct key (e.g., path/to/mykey/env:/dev). This is automatic, but ensure your IAM permissions allow write access to all relevant keys. Otherwise you'll get permission errors when switching workspaces.

Edge case: terraform.workspace in resource names

Resource names that include the workspace name (like aws_instance.web_dev) are fine, but if you change the workspace name, the resource will be recreated because the name changed. Plan carefully — use tags instead of resource names for identity.

What you learned & what's next

You've now learned to manage multiple environments with workspaces — a key skill for any Terraform professional. You can:

  • Explain why workspaces solve the state-isolation problem.
  • Create, list, and switch between workspaces.
  • Use terraform.workspace to vary resource attributes per environment.
  • Pass environment-specific variable files.
  • Destroy and delete workspaces cleanly.

This is a huge step toward writing production-grade Terraform. The next lesson in this track will show you how to combine workspaces with remote backends to lock state and enable collaboration in a team. You'll learn to enable state locking with DynamoDB and share state across a team — a natural follow-up to your new workspace skills.

Final pro tip: Treat workspaces as a convenience, not a magic wand. Always run terraform plan after switching workspaces to catch surprises before you apply. Your future self — and your teammates — will thank you.

Practice recap

Now practice: create a small Terraform config for a simple resource (e.g., a null_resource or a local file). Create dev and prod workspaces, use terraform.workspace to name an output, and apply in both. Then destroy both and delete the workspaces. This will solidify the workflow before moving to remote backends.

Common mistakes

  • Forgetting to run terraform workspace select before apply, so you accidentally modify the wrong environment.
  • Deleting a workspace without running terraform destroy first, leaving resources orphaned and incurring costs.
  • Hardcoding values in the configuration instead of using terraform.workspace or variable files, causing identical resource names across environments.
  • Assuming variables are automatically isolated per workspace — they aren't. You must pass the correct -var-file or use conditional logic.

Variations

  1. Directory-based environments: use separate folders like envs/dev and envs/prod, each with its own backend and state file.
  2. Remote backends with a key pattern that includes the environment name (e.g., state/dev.tfstate) to achieve isolation without workspaces.
  3. Using terraform.workspace for naming tags, but you can also use a custom variable that you set manually to keep code simpler.

Real-world use cases

  • A CI/CD pipeline that creates a temporary workspace for each feature branch, runs terraform apply, then destroys and deletes it.
  • A small team managing dev and staging with a single repo, using workspaces to quickly apply new module versions in staging first.
  • A solo developer running a personal project with separate environments for testing and production, keeping cost low and code simple.

Key takeaways

  • Workspaces isolate state for the same configuration, preventing cross-environment conflicts.
  • Create and switch workspaces with terraform workspace new and terraform workspace select.
  • Use terraform.workspace and variable files to vary resource attributes per environment.
  • Always destroy resources before deleting a workspace to avoid orphaned infrastructure.
  • Workspaces are best for simple, small-team needs; complex setups may prefer directory-based environments.
  • Run terraform plan after switching workspaces to catch unintended changes.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.