In the previous blog post, I wrote about the principles and best practices of IaC design. To complete the topic of standardization, it is important to consider a standardized codebase. This helps you collaborate more effectively with your teammates in maintaining Terraform modules within your organization and supports development teams in doing so as well. In this post, I’m going to share my experiences with naming conventions for writing a standard block of OpenTofu and Terraform code. Some of these conventions are useful for creating a standard Terraform file, while others are necessary due to restrictions in providers’ APIs, such as those from AWS and Azure.
General Conventions
- Always use
_
(underscore) instead of—
(dash)
resource "aws_db_instance" "dev_db" {
...
name = "backend_db_instance"
...
}
- Only use lowercase letters and numbers
resource "aws_key_pair" "ops" {
key_name = "roozbeh_key_1"
public_key = "ssh-rsa ..."
}
HCLResource and Data Source Conventions
- Do not repeat resource type in resource name (not partially, nor completely)
resource "aws_route_table" "public" {
vpc_id = aws_vpc.example.id
...
}
HCL- Resource name should be named
this
if there is no more descriptive and general name available
resource "aws_nat_gateway" "this" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.example.id
...
}
HCL- Always use singular nouns for names
resource "aws_eip" "loadbalancer" {
instance = aws_instance.web.id
vpc = true
}
HCL- Use
-
inside arguments values and in places where value will be exposed to a human (eg, inside DNS name of RDS instance or Route 53 record)
resource "aws_route53_record" "www" {
zone_id = aws_route53_zone.primary.zone_id
name = "www.example-domain.com"
type = "A"
ttl = "300"
records = [aws_eip.lb.public_ip]
}
HCL- Include
count
an argument inside resource blocks as the first argument at the top and separated by a newline after it
resource "aws_instance" "web" {
count = "5"
...
}
HCL- Include
tags
the argument, if supported by resources as the last real argument, followed bydepends_on
andlifecycle
, if necessary. All of these should be separated by a single empty line:
resource "aws_nat_gateway" "this" {
count = "1"
...
tags = {
Name = "..."
}
depends_on = ["aws_internet_gateway.this"]
lifecycle {
create_before_destroy = true
}
}
HCL- When using conditions in
count
argument use a boolean value if it makes sense, otherwise, uselength
or other interpolation:
count = "${length(var.public_subnets) > 0 ? 1 : 0}"
HCL- To make inverted conditions don’t introduce another variable unless necessary, use
1 - boolean value
instead:
count = "${1 - var.create_public_subnets}"
HCLVariable Conventions
- Don’t reinvent the wheel in resource modules — use the same variable names, description, and default as defined in the “Argument Reference” section for the resource you are working on.
- Use
type = "list"
declaration if there isdefault = []
:
variable "availability_zone_names" {
type = list(string)
default = [
"eu-central-1a"
"eu-central-1b"
"eu-central-1c"
]
}
HCL- Use
type = "map"
declaration if there isdefault = {}
:
variable "images" {
type = "map"
default = {
eu-central-1 = "image-1234"
eu-west-1 = "image-4567"
}
}
HCL- Use the plural form in the name of variables of type
list
andmap
:
variable "users" {
type = "list"
...
}
variable "images" {
type = "map"
...
}
HCL- When defining variables order the keys:
description
,type
,default
. Always includedescription
for all variables even if you think it is obvious.
variable "key" {
description = "description"
type = "string"
default = "value"
}
HCLOutputs
A name for the outputs is important to make them consistent and understandable outside of its scope (when the user is using a module it should be obvious what type and attribute of the value are returned).
- The general recommendation for output names is that they should be descriptive of the value they contain and less free-form than you would normally want.
- Good structure for names of output looks like
{name}_{type}_{attribute}
, where:
1.{name}
is a resource or data source name without a provider prefix.{name}
foraws_subnet
issubnet
, foraws_vpc
it isvpc
.
2.{type}
is a type of resource source.
3.{attribute}
is an attribute returned by the output - If the output is returning a value with interpolation functions and multiple resources,
{name}
and{type}
there should be as generic as possible (this
is often the most generic and should be preferred). - If the returned value is a list it should have a plural name.
- Always include
description
for all outputs even if you think it is obvious.
Conclusion
This approach improves collaboration between you and your teammates, ensuring that everyone is able to work together more efficiently when maintaining and updating Terraform modules across your organization. By establishing a standardized process, it also provides better consistency and clarity, which not only streamlines the workflow within your immediate team but also enables development teams throughout the organization to follow best practices, reduce errors, and achieve better integrations during the development and deployment of infrastructure as code.