Problems with Terraform Workspaces

David Gamba, @gambaeng
2024-09-03
version 0.1, 2024-09-03 #terraform

A really good intro with the main reason to avoid workspaces was given in a few lines in this blog post: Other problems with workspaces are detailed after that.

Terraform provides a feature called Workspaces which allows you to define the infrastructure for a root module once, then use the terraform workspace command to change the .tfvars file being used to populate values in the Terraform configuration. Note that this is different from a workspace in Terraform Cloud/Enterprise, so the following advice does not apply to those.

On the surface, this would seem to be an elegant solution to deploying multiple environments and making sure they are exactly the same. After all, if you deploy your dev environment, stage environment, and prod environment with exactly the same code, just different variables, you can be sure that they should be as similar as possible.

Unfortunately, this is also the problem with workspaces. If you force your development and production environments to use the same exact code, how do you test new versions of your infrastructure? Let’s say you want to change your application to be backed by a different type of datastore, or add in some new AWS resource that was just released. How do you do that in your development environment without doing it in production at the same time? While there are ways to do it with ternary operators, dynamic resources, or other tricks, those can introduce unwanted complexity or cause resources to be renamed, setting you up for potentially wiping out production resources if you’re not careful.

Workspaces and moved blocks

If you have a monolith state file you might want to break it into smaller pieces to make use of workspaces.

You might want to try something like:

moved {
  from = "module.${terraform.workspace}"
  to   = module.this
}

But you will run into an error like:

$ terraform workspace new dgamba1
╷
│ Error: Invalid expression
│
│   on main.tf line 23, in moved:
│   23:   from = "module.${terraform.workspace}"
│
│ A single static variable reference is required: only attribute access and indexing with constant keys. No calculations, function calls, template expressions, etc are allowed here.

You can always try to hardcode the moved from key and ensure that change is not committed into VCS.