Best Way to structure your Terraform projects — Part 1!

Dharani Sowndharya
AWS Tip
Published in
4 min readOct 28, 2023

--

Answers to the most asked questions to a DevOps Specialist!

Photo by Kaleidico on Unsplash

Often times, one of the common questions that get asked by folks who are starting their IAC automation using Terraform is:

What are the best practices to be followed in structuring my Terraform project?

The answer often is “It depends..”

There is no single solution which will work for all kinds of use-cases and there are pros and cons for each approach. The opinions provided in the blog are subjective and I wanted to write this article as an open discussion to not only share some of the approaches that I followed but also to invite people to suggest and discuss any other options and to get more feedback on the pros and cons listed. The solutions will have references to AWS services. Choose equivalent services in other cloud providers as needed.

I’ve divided this blog into multiple parts to provide as a collection of best practices that will evolve over time.

When should I use modules and when should I opt to use resources directly?

Modules are reusable functions in Terraform that you can call multiple times to keep your code DRY

If your automation is simple, you can skip writing modules and add the resources in your tf files directly. But, if its complex and if you foresee calling specific resources repeatedly, write modules and call them from your main.tf

Pros:

Reduces code duplication

Cons:

Makes testing complicated

Reduces visibility on the flow of the code

Can I club resources together like module “networking”(Contains vpc, subnet etc) module “compute” (Contains ec2, lambda) ?

Go for resource specific modules.

Reason: Clubbing them together will often create monoliths that will become difficult to find and handle. For example, networking as a module can have vpc, subnet, route table, route table associations, internet gateway, nat gateway, transit gateway, vpc peering, vpn etc

If there are no clearly defined definitions for each logically grouped modules, there is a chance that someone else from the team accidentally creating duplicates or move a resource block to a different folder structure. For example, a new team member might think that Lambda belongs to a serverless module instead of a compute module and might move it there.

Pros:

Makes debugging easier

Easier to navigate and find the respective module

Cons:

For a simple project, having too many modules could become tedious to create and test

Maintenance gets complicated

Can I store the terraform code in the same repository as our application code?

Store the modules/terraform code in a separate repository. Having the code close to the app repository might look simple in the short run, but with each new microservice, maintenance becomes complicated. And if you need to introduce any change, you need to change a lot of repositories and the team needs to make sure that it is reflected in all of them correctly. It also provides a single point of control in approving and merging PRs to the DevOps team

Can I store the modules in the same repo as the main terraform repos or should I create a separate repo for modules?

Create two repos. One where all the terraform modules are stored and one repository containing the main terraform file which refers the modules repo. Refer to this module repo using the source variable and point to a specific branch or tag.

# select a specific tag
module "vpc" {
source = "git::https://example.com/vpc.git?ref=v1.2.0"
}

Using this approach, we can control the version of the dependant resource which is referencing the module and this will allow ease in testing and providing updates.

You can also use some form of PR request based approval to change anything in the modules repo. Since modules are common functions that will impact all the dependant resources, stringent measures could be taken specifically to avoid manual errors.

Cons:

Reduced visibility and control

Can we use the public modules available in terraform registry or do we need to write our own modules?

If you are not looking for a quick automation, please consider writing your own modules and maintaining them. You can copy parts of the remote modules and use it as per your use case to not reinvent the wheel again.

Pros:

  1. The public module will have a lot of configurable use cases that might not apply to our setup
  2. Debugging becomes very difficult as the code in public modules tend to be complex
  3. Moving to a newer version or tag needs extensive testing and good understanding of the code

Cons:

  1. It is time consuming
  2. You might not follow the industry standard best practices if you choose to write your own code
  3. Your own code will not be battle tested and there are chances of getting errors

What are the best practices of storing the terraform state?

  1. Store the terraform state in an object storage like S3
  2. Use Dynamo DB to lock the terraform state to prevent concurrent writes to state file
  3. Split the terraform state logically based on accounts, environments and stacks as well and store them in respective folders separately in S3
    – This will reduce the blast radius in case of any issues in your setup
  4. Use consistent naming to find the state files and their location

Please refer to Part 2 of this blog here.

--

--