Menu

Virtual Geek

Tales from real IT system administrators world and non-production environment

Terraform workspace with example

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

Terraform workspaces azurerm dev infrastructure location name tfvars var file subscription id configuration create infrastructure delete force replacement destroy prod environment.png

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

Terraform workspaces list apply --auto-approve create deploy plan destroy workspaces configuration dev prod infrastructure environment Azure cloud azurerm resource manager.png

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

terraform apply workspace different environment prod dev show list hashicorp cloud --auto-approve --var-file tfvars destory create apply plan azure azurerm resource manager.png

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

terraform azurerm provider terraform workspace list select dev prod infrastructure configuration without destroy resource group object block hashicorp hcl extreme examples.png

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>

terraform workspace delete dev destroy create plan configuration installation setup administration azure cloud azurerm hashicorp cloud worksapce terraform tfvars auto.png

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. 

terraform workspace tfstate dev and prod workspaces tfvars main.tf lock.hcl registrry.terraform.io hashicorp azurerm provider windows environment configuration tree view of folder.png

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

Go Back

Comment

Blog Search

Page Views

12085912

Follow me on Blogarama