
Most Terraform projects eventually need more than one environment. You have development, staging, and production, and you'd rather not copy the same configuration into three separate directories to keep their state apart. Workspaces are Terraform's built-in answer: one set of code, a separate state file per workspace. This post covers what workspaces are, how to use them, and how the picture changes once you run them through a remote backend like Scalr or Terraform Cloud.
Terraform workspaces let you create many instances of your infrastructure from the same Terraform code. Each workspace keeps its own state file, so you can manage different environments without duplicating the configuration. That comes in handy when you need several copies of the same setup, whether for different stages of development or for separate clients.
By default, Terraform operates in a workspace called "default." You're working within this default workspace when you run Terraform commands without specifying a workspace. However, you can create additional workspaces to manage different states of your infrastructure.
To create a new workspace, you can use the following command:
terraform workspace new <workspace_name>
This command creates a new workspace with the specified name and switches to it. You can list all available workspaces using:
terraform workspace list
To switch between workspaces, use:
terraform workspace select <workspace_name>
When you create a new workspace and execute a Terraform apply, Terraform generates a new state file specific to that workspace. The state file is crucial as it keeps track of the current state of your infrastructure and helps Terraform determine what changes need to be made to reach the desired state defined in your configuration.
In a local setup, Terraform stores these state files in a directory named terraform.tfstate.d within your working directory. Each workspace gets its own subdirectory named after the workspace, containing the state file for that particular workspace.
For example, if you have workspaces named "dev" and "prod," your directory structure might look like this:
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfstate.d
│ ├── dev
│ │ └── terraform.tfstate
│ └── prod
│ └── terraform.tfstate
└── .terraform
Local state works fine for an individual project or a small team. Once more people are involved, remote state storage tends to be the better fit. Terraform can store state in several remote backends, including Scalr, Amazon S3, Azure Blob Storage, and HashiCorp's Terraform Cloud.
When using a remote backend with workspaces, the backend typically handles the separation of state files for different workspaces. For example, if you're using an S3 backend, you might configure it like this:
In this setup, Terraform will automatically manage separate state files for each workspace within the specified S3 bucket. The actual path to the state file will include the workspace name, ensuring isolation between different environments.
Alternatively, in Scalr, you would configure it like this:
In this setup, a workspace is created in Scalr where the state is stored, run history is kept, and all items associated with that Terraform deployment are kept. In tools like Scalr and HCP Terraform, you aren't just storing the state remotely; you are doing all operations remotely so teams can have a central place to deploy Terraform.
Workspaces also let you read the active workspace name inside your Terraform code. With it, you can adjust the configuration depending on which workspace is selected.
Terraform provides a special variable terraform.workspace that contains the name of the current workspace. You can use this variable to set values or make decisions in your Terraform code.
For example, you might use it to set different resource names or tags:
In this example, the EC2 instance will be tagged with the name of the current workspace, making it easy to identify which environment it belongs to.
While the terraform.workspace variable is useful, you might want more fine-grained control over your configuration. You can achieve this by defining workspace-specific variables in your Terraform code.
For instance, you could create a map of variables for different environments:
In this setup, the instance type will be selected based on the current workspace, allowing you to easily manage different configurations for different environments.
Data sources in Terraform allow you to fetch information from existing resources or external services. When working with workspaces, you can use data sources to retrieve information specific to each environment.
For example, you might use a data source to fetch the appropriate VPC ID for each environment:
This approach allows you to maintain environment-specific resources outside of your Terraform code while still referencing them appropriately in each workspace.
The core concept of Terraform workspaces is similar between local usage and Scalr, but there are some important distinctions in how they're implemented and used:
terraform workspace new, terraform workspace select). Scalr: Managed through the web UI, API, or Terraform CLI with a remote backend configuration.The underlying idea of isolating state and configuration is the same. Scalr just builds more around it, treating a workspace as a unit of management rather than a single state file. That extra functionality isn't present in local workspaces, which is why Scalr tends to fit team environments and larger infrastructures better.
Moving from local workspaces to Scalr means trading the bare CLI workflow for one built around collaboration.
Terraform workspaces give you a way to manage several environments from one set of code. They cut down on duplication, keep each environment's state separate, and make it easier for a team to work in parallel. On a small project the CLI workspaces are usually enough. As things grow, a remote backend like Scalr or Terraform Cloud takes over the state separation and adds the access controls and run history that a team needs.
Workspaces are one piece of a larger Terraform workflow, so weigh them against how your team actually works before committing to a pattern.
