10 Terraform Best Practices: For Secure & Fast Infrastructure.

If you’ve just started working with Terraform you might be getting that familiar feeling in the back of your mind: “Am I doing this the right way?“.

Terraform Best Practices

Today we’re going to tackle your nagging feelings head on by discussing all the important best practices for Terraform, so that you have the confidence to go full steam ahead with your project.

By the end of this article you’ll understand 10 best practices to follow when implementing Terraform. 

Terraform Best Practices (In Summary)

If you were looking for the quick checklist on best practices, here you go…

  1. Don’t commit the .tfstate file
  2. Configure a backend
  3. Back up your state files
  4. Keep your backends small
  5. Use one state per environment
  6. Setup backend state locking
  7. Execute Terraform in an automated build
  8. Manipulate state only through the commands
  9. Use variables (liberally)
  10. Use modules (only where necessary)

Let’s break some of these areas down…

What is Terraform?

Before we get to the best practices let’s discuss what Terraform is. Terraform is an open source, CLI based tool that helps with the creation, and management of cloud resources. In plain english Terraform is a tool that helps you create, update and delete your cloud infrastructure quickly and easily.

For more on Terraform and Infrastructure As Code, check out: Infrastructure As Code: An Ultimate Guide.

1. Don’t Commit The .tfState File

Terraform State File

Let’s get straight down to the brass tacks: Don’t commit the .tfstate file.

The .tfstate is a file outputted by Terraform when you run commands that alter your state. Such as when running terraform apply. The tfstate file stores information mappings between your created resource names and the real infrastructure (if you’re interested to know more about why the tfstate file is important, I’d suggest you check out the official Terraform state docs).

Ultimately Terraform cannot work without an up-to-date state file, so many engineers consider committing it to version control. But committing the state file comes with several risks… Firstly, you could be exposing secrets from your application configuration, such as  passwords, database connection strings. And secondly, you risk executing Terraform against stale or old state that you forgot to pull down from version control.

But, not to fear, there is an alternative to committing the tfstate file, and that’s by configuring a feature called a Terraform “backend”. Backends are a fairly big topic that warrants it’s own section. Let’s discuss backends now…

For more information on what the state file is and why you shouldn’t be committing it, check out: Should You Commit the Terraform .tfstate File to Git?

2. Configure a Backend

Terraform Backend Configuration

Example Terraform Backend Configuration

A Terraform backend is configuration on how (and where) to store your Terraform state in a centralised, remote location. Backend setups will depend on which cloud provider / backend type you want to configure. You can configure a backend on S3, on artifactory or in a whole host of other ways.

Backends broadly speaking have two main features: state locking and remote state storage. Locking prevents two executions happening at the same time. And remote state storage allows you to put your state in a remote, yet accessible location.

By default, Terraform uses the “local” backend type which causes the output of the .tfstate file we discussed before. But with a backend configured this state file is automatically pushed to a remote location.

Check out the backend types and follow the instructions to configure one.

If you’re using AWS, you can follow this guide to setup Github Actions The Ultimate Terraform Workflow: Setup Terraform (And Remote State) With Github Actions

3. Back Up Your State File

Terraform State Versioning

Example Terraform State Versioning

As your state file is the engine that drives your configuration it makes sense to ensure that the location where you store your state is backed up. Backed up state makes it easier to revert to a previous state if you make a mistake.

In AWS, for instance, versioning is very easy to configure as you just have to ensure that your bucket has versioning enabled. With versioning enabled rolling back to previous state is just a button click away.

So be sure to check out your cloud providers options for where you’re storing your backend and make sure versioning is enabled.

4. Keep Your Backends Small

Terraform Apply

Terraform Apply

When you start on a Terraform project you’ll likely have all your configuration in one place. However, over time with your infrastructure growing there will come a point where you want to break down your infrastructure configurations.

Keeping all your configurations together introduces risk, as you have the ability to introduce unwanted changes to other infrastructure when you apply your changes. To break down your infrastructure simply create new Terraform projects (and therefore new state).

To make your life easier, you can leverage the commands such as terraform import to move state between your Terraform configurations, and the remote_state block to pull in values from other Terraform remote states.

For more information on how to manipulate and migrate existing infrastructure, check out the article: 3 Steps To Migrate Existing Infrastructure To Terraform

5. Use One State Per Environment

Terraform State File

Environments are used to test changes before they are deployed to your live environment. Similar to the last idea of breaking down your state files it also makes sense to break down your terraform configurations and state files per environment. Again, breaking down by envioronment reduces risk when you apply changes.

6. Setup Backend State Locking

Example Terraform Backend Config (With State Locking)

Example Terraform Backend Config (With State Locking)

Terraform state comes in two parts: remote state, and state locking. State locking prevents two mutating commands, such as terraform apply operating on the same state file at the same time.

To configure remote state, follow the instructions provided for your backend configuration option. For a backend configured with AWS, updating the backend configuration is simple as referencing a new Dynamo DB table name.

7. Execute Terraform In An Automated Build

Terraform Github Actions

Terraform Github Actions

Running code in an automated build tool has many advantages, which includes having a repeatable process, and a history of changes. The concept of builds is also very useful when applied to Terraform, ensuring that it’s more visible when and what has been executed against your infrastructure for auditing, debugging and collaborating purposes.

If you want a cheap and simple way to get CI setup with Terraform, check out the article: The Ultimate Terraform Workflow: Setup Terraform (And Remote State) With Github Actions

8. Don’t Perform State Surgery (Use the CLI)

Terraform State Commands

Terraform State Commands

As the state file is a representation of your infrastructure in the real world sometimes situations come up that require you to modify your state. For instance, if you want to rename a resource block, you’ll need to re-assign your terraform state to the new resource name.

Many when they need to move or rename state are tempted to dive into the state file itself and start hacking around. But beware, there’s a much safer way! The Terraform CLI gives you commands that allow you to remove, or move (terraform state rm and terraform state mv)

In short… never touch the state file yourself. Instead move state, and remove state using the CLI. These commands should be all you ever need.

9. Use Variables, Liberally

Like in most regular programming languages, Terraform has variables. Variables allow you to store shared or repeating configuration values, which in turn keeps your code easy to manage and update. Terraform is no different.

To use variables in Terraform all you have to do is declare your variable somewhere in your configuration and later reference your variable in your resource configurations. You can then pass in values for your variables as environment variables or by passing in values from the Terraform CLI.

So be sure to go find any repeating values in your configuration, such as environment names, or prefixes and store them in variables.

10. Use Modules (Where Necessary)

Terraform Module State Move

Terraform Module State Move

Terraform modules allow you to break down infrastructure configurations into shared blothecks. You can think of modules in Terraform like regular programming functions. Modules take some inputs, do things (in our case, define infrastructure) and return outputs.

Modules help you take common infrastructure patterns and share them throughout your code, or business. But modules can sometimes be a bit of a pain to maintain, so make sure to apply them only when necessary. A good rule for when to reach for modules is when you’ve seen a pattern at least 3 times.

For more information on Terraform Modules, be sure to check out this article: Terraform Modules: A Guide To Maintainable Infrastructure As Code

Go Forth, Implement Great Terraform.

And that’s a wrap on our 10 Terraform best practices for today!

Hopefully you learned some new terraform best practices and have now got some ideas for things that you can configure or change through to make your current Terraform setup faster, more secure and easier to maintain.

Speak soon Cloud Native Friend!

What best practices have you implemented to your own Terraform?

Lou Bichard