Terraform Workspaces

Until recently, our developers had to share a single instance of our development environment. When we only had a couple of developers on the team this wasn't an issue. Now that our team is larger we are running into issues where there is a queue for testing features in the development environment. Depending on the features, developers can be waiting as little as a few minutes and as long as a few days. In order to remove this inefficiency, we decided to improve our CI/CD process by enabling developers to deploy their code to their own development environments. In this article we will discuss our solution and its implementation using Terraform workspaces.

Replicating our application's infrastructure

Our applications are hosted in the cloud using AWS and we use Terraform to manage their infrastructure. In order to use the same infrastructure for every environment we will use workspaces to uniquely identify our resources.  Terraform workspaces enables us to use the same configuration with separate state files.

Using Terraform workspaces within our configuration

We will be deploying the same infrastructure within a single AWS account. Therefore, we will update the naming/tagging of our resources to identify which resources belong to which workspace. In our configuration we are using our application's name within our resource names. For example, our ALB target group is named "${var.app_name}-tg". We will let our "default" workspace to refer to our regular development environment and use our pre-existing naming convention. However, for all other named workspaces we will append the workspace name to our application's name.

To use this updated naming convention, we will leverage Terraform local values. Terraform local values will allow us to use  a ternary operator to determine when to append our workspace name. Our locals block will look like the following:

locals {
ws_app_name = "${var.app_name}-${terraform.workspace}"
app_name = "${terraform.workspace == "default" ? var.app_name : local.ws_app_name}"
}

In the above code snippet, we append the workspace name to our app name and store that value as "ws_app_name". Then we check to see if we are in the default workspace. If so, we use the app name as is. Otherwise, we use the newly created "ws_app_name" variable.

Now we have a local variable "app_name" that uses the correct name based on our workspace. Now we can replace our references to "var.app_name" with "local.app_name" wherever we are using it to name resources.

Deploying our new Terraform Configuration

At this point we have updated our Terraform configuration to use workspaces. Now it is time to test out our new configuration. To deploy a new development environment manually, we just need to create a new workspace and apply our changes. To deploy our new environments using a CI/CD pipeline, we will need to identify when a new workspace is needed.

For our pipeline, we use git tags to deploy to specific environments (e.g. "deploy-dev"). We will check if the username of the user who starts the build is within the tag. If so, we will create a new workspace using that user's name and deploy our new changes. In this scenario, if user "coder1" kicks off a new build using the tag "deploy-dev-coder1" our pipeline would recognize that and create a new workspace "coder1" to apply the terraform configurations.

Conclusion

With our new configuration, our developers can now test their code within their own environments. Preventing them from fighting for the right to use our development environment. This new functionality has enabled our developers to test features sooner and deploy new features faster.

If you have questions regarding this setup, contact us we would love to hear from you.

How we increased developer efficiency by using Terraform workspaces