How I organize my Terraform files

Having one folder per provider and handling outputs

I’m not sure this is the best way to structure a terraform project but it really works for me. I often use terraform with more than one provider (i.e AWS, Cloudflare, Gitlab, etc..) so I like to have one per folder. To do so, I use terraform modules.

This is what my terraform folder structure looks like.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ tree ./terraform
├── aws
│   ├── aws.tf
│   ├── cloudwatch.tf
│   ├── outputs.tf
│   ├── policies
│   │   └── assume_role_policy.json
│   ├── role.tf
│   ├── security-groups.tf
│   ├── server.tf
│   └── sns.tf
├── cloudflare
│   ├── cloudflare.tf
│   ├── outputs.tf
│   └── site.tf
├── config.tf
└── terraform.tf

3 directories, 13 files

And this is what’s inside the terraform.tf :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
terraform {
backend "s3" {
bucket = "mastertv-terraform-states"
key = "mtv-store/terraform.tfstate"
region = "us-east-1"
}
}

module "cloudflare" {
source = "./cloudflare"
config = var.cloudflare
# This line over here makes the aws outputs available on cloudflare module.
outputs = {
server_public_ip = module.aws.server_public_ip
}
}

module "aws" {
source = "./aws"
config = var.aws
outputs = { }
}
# This makes the output variable from outside the module.
output "server_public_ip" {
value = module.aws.server_public_ip
}

In order to make terraform outputs available we need to reference them as shown in terraform.tf.

server_public_ip will be the name of the output while module.aws.server_public_ip makes reference to the output inside any of the files within the ./aws module. In this case all of the module’s outputs are contained in a single ./aws/outputs.tf file and they all look something like this.

1
2
3
output "server_public_ip" {
value = aws_instance.mtv-store-ec2.public_ip
}

Notice that the name of the output server_public_ip is the one we are referencing with module.aws.server_public_ip.

Then you can access the output with $> terraform outputs server_public_ip at the root directory.

In order to share the output among modules I make use of Input variables. I call a module and then pass the output variable as input in the module block as shown, again, in the terraform.tf within the cloudflare module block. Nevertheless, this is not enough to be able to access these Input variables, we need to declare them in the child module first. Not to have many files, I do it where I declare my provider, this is the example of cloudflare.tf. Don’t worry it sounds harder than what it is

1
2
3
4
5
6
7
8
9
10
11
provider "cloudflare" {
version = "~> 2.0"
email = var.config.email
api_key = var.config.api_key
api_token = var.config.api_token

}

#Input variable of the module, it's declared here but not defined
variable "config" {}
variable "outputs" {}

Conclusion

Terraform is a great tool but it can lead to some misunderstanding if it’s not well organized. This way of organizing files, let me have full control of the infrastructure just from one config.tf file and share outputs easily among modules. A downside to this solution is that some IDEs will not autocomplete neither outputs nor variables.
If you got up to here, thank you very much for reading if you have any questions/opinions or you’ve found any mistake on the text please feel free to write me, my contact details can be found in the /about section.

Bibliography and technology used in this project


Author: Lucas Contreras

Leave a comment: Join the Reddit discussion