The DevOps Jedi

Taking the cloud by storm one line of code at a time....

How I Structure Terraform Folders & Apply Naming Standards

2023-01-075 min readDarren Johnson

When I started using Terraform in 2018 the content online was mainly written from a developer’s perspective and was based on deploying resources into AWS. This was a great starting point, but the key messages in the blogs I read were all fairly consistent and seemed to align to the excellent blog series by Yevgeniy Brikman from 2016 which is well worth a read.

So, I read the series, took all the key messages onboard and started deploying resources into Azure. However, as our environment began to scale and more engineers came onboard, it was soon clear I needed to formalise our ways of working to ensure everyone followed a standard approach as we wouldn’t be operating the ‘you build it you run it’ model. This meant that once cloud services were provisioned and live, they needed to be handed over to operations who would also need to understand the Terraform configuration.

Configuration Folder

A lot of the early online tutorials showed terraform configurations that followed a standard module structure and placed all resources into a single main.tf file.

One drawback with this approach is that you would then need to read the whole configuration to understand what is being built and managed. Take a look at the example-blog folder.

example-blog/
├── main.tf
└── variables.tf

A simpler way is to break up the resources into logical groups of resources that need to be deployed, managed and destroyed together into separately named files. Terraform doesn’t care what the files are named and processes all .tf files in the folder as if they were a single file.

how-i-do-it/
├── backend.tf
├── datasources.tf
├── resource-group.tf
├── storage-account.tf
├── terraform.tf
├── variables.tf
└── windows-vm.tf

From looking at my standard folder structure, we can see that the following are part of the configuration:

  • backend.tf - the terraform backend state file storage
  • datasources.tf - any existing resources referenced in the configuration as data sources
  • resource-group.tf - the resource group containing the Azure resources
  • storage-account.tf - an Azure storage account
  • terraform.tf - the terraform and provider versions required, and any provider specific configuration
  • variables.tf - variables used within the configuration
  • windows-vm.tf - a windows virtual machine resource as well as any other resources that lifecycle with the vm such as network interfaces or extra disks

Another benefit of this approach is that should I want to remove a resource (or set of resources) I can simply rename the .tf file to something terraform will not process. I normally use .ig which stands for ignore.

Parent Folders

My parent folder structure for my terraform repository is also kept simple. I have a folder per cloud provider (az for Azure), with a subfolder per service I am building (blog), followed by a final subfolder for the environment I am building in (msdn).

Putting this all together it looks like this:

az/
└── blog
    └── msdn
        ├── backend.tf
        ├── resource-group.tf
        ├── storage-account.tf
        ├── terraform.tf
        ├── variables.tf
        └── windows-vm.tf

I keep the folder names as a single word to reflect tags wherever possible. If I have to specify multiple words, I separate them by a hyphen.

Naming Standards

I’ve explained how I name my configuration files already, but within the files I also ensure I standardise how I name the resources too.

Terraform resources are made up of standard components. Each resource type is implemented by a provider and has to be named as per the Terraform developer best practices which states that “Resource names must always start with their containing provider’s name followed by an underscore”.

Each resource is also given a user-defined name which must be unique for the resource type within the configuration. Resources are addressed within the configuration in the format of resource_type.resource_name as documented .

I always keep the user-defined name standard across the configuration and separate any words with an underscore in the same way as the resource names do to ensure consistency. I never repeat the resource type in the user-defined name to keep the resource address concise and the name is normally specified in the format of service_environment.

A good example would be:

resource "azurerm_resource_group" "blog_msdn" {}

Where a bad example would look like:

resource "azurerm_resource_group" "my-resource-group" {}

I use a slight variation of this if the resource links 2 defined resources together, such as assigning a role to an object. I try and make this clear by using the format of service_environment_role_object. So, this would look as follows:

resource "azurerm_role_assignment" "automation_msdn_contributor_rg" {}

Finally, when multiple resources of the same type are defined, I set the user-defined name to match the resource name, so it is easy to identify which resource code block matches which resource. An example here would be for 2 windows virtual machines:

resource "azurerm_windows_virtual_machine" "domain_controller_1" {}

resource "azurerm_windows_virtual_machine" "web_server_3" {}

Other Files Referenced By Terraform

If I have files that are referenced by Terraform but are not part of the actual terraform configuration itself, I always put them in their own sub folder so it is clear what they are for. For example, custom scripts go in a /scripts folder.

Key Takeaway: Each environment is considered as a separate service and has its own code folder and state file. This means changes in one environment will not affect another.