In the Hashicorp Terraform there is a feature called Terraform workspace that permits you to achieve multiple, isolated infrastructure environments within a single Terraform configuration. A workspace is fundamentally a way to isolate your infrastructure configurations into different environments, such as: Development, Staging, Production, Testing and so on
Individually each workspace has its own:
- Variables (terraform.tfvars)
- State file (terraform.tfstate)
- Configuration (terraform.tf)
Workspaces are useful for:
- Testing changes in a development workspace before put on them to production
- Keeping sensitive data discrete among environments
- Work together with team members on diverse environments
- Handling divergent environments with similar configurations
Workspaces are kept in the Terraform state file, and Terraform uses the current workspace to govern which configuration and variables to use when running commands.
To start with this demo example, I have below simple terraform configuration script. It creates a new Resource Group on Azure cloud, It have two variables with name and location. (Just a note starting with azurerm provider version 4, I see subscription_id is mandatory now)
provider "azurerm" { features {} subscription_id = "9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } ############################################# variable "name" { type = string description = "Provide Resource Group Name" } variable "location" { type = string description = "Provide Resource Group Location" } ############################################# resource "azurerm_resource_group" "example" { name = var.name location = var.location }
To feed the information to Terraform variables I have created two .tfvars file one is for dev and another is for prod. Both the file has information of different resource group name and location.
#dev-infra.tfvars name = "devrg" location = "East US" #prod-infra.tfvars name = "prodrg" location = "West US"
When working with terraform workspace command, below are the options and subcommands you can use.
Usage: terraform [global options] workspace
new, list, show, select and delete Terraform workspaces.
Subcommands:
delete Delete a workspace
list List Workspaces
new Create a new workspace
select Select a workspace
show Show the name of the current workspace
To start first, I will initialize the terraform project using below command.
terraform init
Next I am creating a new Resource Group in Azure with below command for dev infra. For var file I am using dev-infra.tfvars. Deployment is successful.
terraform apply --auto-approve --var-file dev-infra.tfvars
Once dev infra is created if I try just plan for prod infra .tfvar information. It shows it will destroy the dev environment and apply create new prod environment.
Following I will show how to deal with this and accommodate multiple environment from same folder using workspaces.
terraform plan --var-file prod-infra.tfvars
To create workspace use below command for dev environment.
terraform workspace new dev
Once the workspace is created, list them using below command. Note there are 2 workspaces default and dev. dev is prefixed with *, meaning it is selected workspace. When I apply terraform configuration it will be created inside dev workspace.
terraform workspace list
Run apply command on terraform with dev-infra.tfvars file. New dev Resource Group state file is created under dev workspace.
terraform apply --auto-approve --var-file dev-infra.tfvars
Once dev infrastructure apply is successful under dev workspace, it is time to create a new workspace for prod using below command.
terraform workspace new prod
This command shows the current workspace in use.
terraform workspace show
This is a time to test prod infrastructure inside prod workspace. It will get deployed without touching and destroying dev infra / workspace.
terraform apply --auto-approve --var-file prod-infra.tfvars
List all the workspaces using below command, I see there are 3 workspaces now, and prod is the current and selected workspace prefixed with *.
terraform workspace list
Incase if I want to switch to different workspace, I can run below command.
terraform workspace select dev
If I list workspaces again, I see this time dev is my current and selected workspace now.
terraform workspace list
Here are the things to pay attention when you delete workspace. Make sure you are not deleting selected workspace and there should be active state.
terraform workspace delete <name>
Here is output of all the terraform command and its messages which I executed for this demo.
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform init
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/azurerm...
- Installing hashicorp/azurerm v4.0.1...
- Installed hashicorp/azurerm v4.0.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform apply --auto-approve --var-file dev-infra.tfvars
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# azurerm_resource_group.example will be created
+ resource "azurerm_resource_group" "example" {
+ id = (known after apply)
+ location = "eastus"
+ name = "devrg"
}
Plan: 1 to add, 0 to change, 0 to destroy.
azurerm_resource_group.example: Creating...
azurerm_resource_group.example: Still creating... [10s elapsed]
azurerm_resource_group.example: Creation complete after 11s [id=/subscriptions/9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/devrg]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform plan --var-file prod-infra.tfvars
azurerm_resource_group.example: Refreshing state... [id=/subscriptions/9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/devrg]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# azurerm_resource_group.example must be replaced
-/+ resource "azurerm_resource_group" "example" {
~ id = "/subscriptions/9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/devrg" -> (known after apply)
~ location = "eastus" -> "westus" # forces replacement
~ name = "devrg" -> "prodrg" # forces replacement
- tags = {} -> null
# (1 unchanged attribute hidden)
}
Plan: 1 to add, 0 to change, 1 to destroy.
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform destroy --auto-approve --var-file dev-infra.tfvars
azurerm_resource_group.example: Refreshing state... [id=/subscriptions/9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/devrg]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# azurerm_resource_group.example will be destroyed
- resource "azurerm_resource_group" "example" {
- id = "/subscriptions/9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/devrg" -> null
- location = "eastus" -> null
- name = "devrg" -> null
- tags = {} -> null
# (1 unchanged attribute hidden)
}
Plan: 0 to add, 0 to change, 1 to destroy.
azurerm_resource_group.example: Destroying... [id=/subscriptions/9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/devrg]
azurerm_resource_group.example: Still destroying... [id=/subscriptions/9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/devrg, 10s elapsed]
azurerm_resource_group.example: Still destroying... [id=/subscriptions/9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/devrg, 20s elapsed]
azurerm_resource_group.example: Still destroying... [id=/subscriptions/9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/devrg, 30s elapsed]
azurerm_resource_group.example: Still destroying... [id=/subscriptions/9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/devrg, 40s elapsed]
azurerm_resource_group.example: Still destroying... [id=/subscriptions/9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/devrg, 50s elapsed]
azurerm_resource_group.example: Still destroying... [id=/subscriptions/9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/devrg, 1m0s elapsed]
azurerm_resource_group.example: Destruction complete after 1m10s
Destroy complete! Resources: 1 destroyed.
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform workspace new dev
Created and switched to workspace "dev"!
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.
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform workspace list
default
* dev
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform apply --auto-approve --var-file dev-infra.tfvars
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# azurerm_resource_group.example will be created
+ resource "azurerm_resource_group" "example" {
+ id = (known after apply)
+ location = "eastus"
+ name = "devrg"
}
Plan: 1 to add, 0 to change, 0 to destroy.
azurerm_resource_group.example: Creating...
azurerm_resource_group.example: Still creating... [10s elapsed]
azurerm_resource_group.example: Creation complete after 12s [id=/subscriptions/9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/devrg]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform workspace new prod
Created and switched to workspace "prod"!
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.
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform workspace show
prod
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform apply --auto-approve --var-file prod-infra.tfvars
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# azurerm_resource_group.example will be created
+ resource "azurerm_resource_group" "example" {
+ id = (known after apply)
+ location = "westus"
+ name = "prodrg"
}
Plan: 1 to add, 0 to change, 0 to destroy.
azurerm_resource_group.example: Creating...
azurerm_resource_group.example: Still creating... [10s elapsed]
azurerm_resource_group.example: Creation complete after 14s [id=/subscriptions/9exxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/prodrg]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform workspace list
default
dev
* prod
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform workspace select dev
Switched to workspace "dev".
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform workspace list
default
* dev
prod
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform workspace delete dev
Workspace "dev" is your active workspace.
You cannot delete the currently active workspace. Please switch
to another workspace and try again.
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces>
PS D:\Projects\Terraform\\extreme_examples\01-Workspaces> terraform workspace delete prod
╷
│ Error: Workspace is not empty
│
│ Workspace "prod" is currently tracking the following resource instances:
│ - azurerm_resource_group.example
│
│ Deleting this workspace would cause Terraform to lose track of any associated remote objects, which would then require you to delete them manually outside of Terraform. You should destroy these objects with
│ Terraform before deleting the workspace.
│
│ If you want to delete this workspace anyway, and have Terraform forget about these managed objects, use the -force option to disable this safety check.
╵
PS D:\Projects\Terraform\\extreme_examples\01-Workspace
This is a tree view of the folder where all my terraform configuration exist, check the tfstate file location and workspace folder formation.
Useful Articles
Terraform convert single string to list or set
Terraform variable type list with for_each for loop examples
Terraform refactoring moved block example
Terraform create Azure Virtual Machines from map of objects
Terraform variable validation example
Configure Azure Storage Account Blob as Terraform backend to store tfstate file Examples of most used general purpose terraform functions
Create storage account and Service Principal using PowerShell for Terraform Azure Backend
Unlocking TF State File on Azure backend with PowerShell and Terraform Force-Unlock Command
Terraform variable precedence and priority
Terraform filter map and list object with if condition in for_each loop examples
Terraform Azure function app with private endpoint and storage account
Terraform module Azure function app with private endpoint and storage account
Terraform passing different credentials to different subscriptions with provider alias
Terraform Azure provider alias passing credentials and configuration in module resources
Terraform Azure provider passing multiple alias environment and credentials in child module