Introduction
Terraform modules are a core concept in Infrastructure as Code (IaC) that allow you to create reusable, configurable, and organized components. Instead of writing the same block of code in multiple places, you can encapsulate it into a module and call it whenever needed. This practice significantly improves code maintainability and consistency across your projects.
In this lab, you will learn the fundamentals of implementing Terraform modules. You will start by creating a standard module directory structure. Then, you will define a simple module that creates a local file. You will make this module configurable using variables and expose its results using outputs. Finally, you will call this module from your root Terraform configuration to deploy the resource.
By the end of this lab, you will have a practical understanding of how to build and use your own Terraform modules.
Create modules directory in project root
In this step, you will create the standard directory structure for a local Terraform module. It is a common convention to place all local modules within a modules directory in your project's root. Each subdirectory inside modules then represents a single, self-contained module.
First, let's create a directory for our modules and a specific subdirectory for a module we'll name localfile_creator. All operations will be performed in the ~/project directory.
Execute the following command to create the nested directory structure:
mkdir -p modules/localfile_creator
The -p flag ensures that mkdir creates the parent directory modules if it doesn't already exist.
Now, you can verify that the directories have been created correctly by using the tree command. If tree is not installed, you can use ls -R.
tree
You should see the following output, confirming your new directory structure:
.
└── modules
└── localfile_creator
2 directories, 0 files
This structure clearly separates your reusable module code from the root configuration that will use it.
Create main.tf in module for local_file resource
In this step, you will create the primary configuration file for your new module. Each module is a self-contained Terraform project, so it has its own set of .tf files. The main.tf file is typically where the core resources of the module are defined.
We will define a local_file resource, which is part of the hashicorp/local provider. This resource is excellent for learning purposes as it simply manages files on the local filesystem without needing any cloud provider credentials.
First, create the main.tf file inside your module's directory using the nano editor:
nano modules/localfile_creator/main.tf
Now, add the following HCL (HashiCorp Configuration Language) code to the file. This code defines a resource of type local_file named example.
resource "local_file" "example" {
content = "This is a file created by a Terraform module."
filename = "${path.module}/module_output.txt"
}
Let's break down this code:
resource "local_file" "example": This declares a resource of typelocal_fileand gives it a local nameexample.content: This argument sets the content that will be written into the file.filename: This argument specifies the path and name of the file to be created. We use the built-inpath.modulevariable, which always refers to the filesystem path of the module where the expression is placed. This makes the file path relative to the module itself.
Save the file and exit nano by pressing Ctrl+X, followed by Y, and then Enter.
Define module variables and outputs
In this step, you will make your module configurable and able to return information. Hardcoding values like filenames and content limits a module's reusability. We will use input variables to pass in custom values and output values to expose information about the resources the module creates.
By convention, variables are defined in a variables.tf file and outputs in an outputs.tf file.
First, create the variables.tf file for your module:
nano modules/localfile_creator/variables.tf
Add the following code to define two input variables: file_content and file_name.
variable "file_content" {
description = "The content of the file."
type = string
default = "Default content."
}
variable "file_name" {
description = "The name of the file to create."
type = string
}
Next, create the outputs.tf file to declare what information the module will return:
nano modules/localfile_creator/outputs.tf
Add the following code to output the full path of the created file:
output "filename" {
description = "The full path to the created file."
value = local_file.example.filename
}
Finally, you need to update the module's main.tf to use these new variables instead of the hardcoded values. Open the file again:
nano modules/localfile_creator/main.tf
Modify the file to look like this. We are replacing the hardcoded strings with var.file_content and var.file_name.
resource "local_file" "example" {
content = var.file_content
filename = "${path.module}/${var.file_name}"
}
Save and exit the editor. Your module is now flexible and ready to be used with different inputs.
Call module in root main.tf file
In this step, you will create a root configuration file that calls the module you just built. The root main.tf is the entry point for your Terraform execution. It's where you compose your infrastructure by calling one or more modules.
You will now work in the root of your project directory, ~/project. Create a main.tf file here:
nano main.tf
Add the following code to this file. This configuration will use the localfile_creator module.
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "2.4.0"
}
}
}
module "file_creator_instance" {
source = "./modules/localfile_creator"
file_content = "Hello from the root module!"
file_name = "my_test_file.txt"
}
output "created_file_path" {
description = "Path of the file created by the module."
value = module.file_creator_instance.filename
}
Let's analyze this root configuration:
terraform { ... }: This block defines provider requirements. Since our module uses thelocalprovider, the root module that calls it must also declare it.module "file_creator_instance" { ... }: This is the module block.file_creator_instanceis a local name for this specific instance of the module.source = "./modules/localfile_creator": This tells Terraform where to find the module's source code. In this case, it's a local path.file_content = "..."andfile_name = "...": Here, you are passing values to the input variables defined in the module'svariables.tf.output "created_file_path" { ... }: This root-level output block retrieves a value from the module. The syntax ismodule.<MODULE_INSTANCE_NAME>.<OUTPUT_NAME>.
Save the file and exit nano. Your project is now fully configured to use the module.
Run terraform apply to deploy via module
In this final step, you will use standard Terraform commands to initialize, plan, and apply your configuration. This will execute the code in your module and create the local file.
First, initialize the Terraform working directory. This command downloads the necessary provider plugins (in this case, hashicorp/local).
terraform init
You should see a success message indicating that Terraform has been initialized.
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/local versions matching "2.4.0"...
- Installing hashicorp/local v2.4.0...
- Installed hashicorp/local v2.4.0 (signed by HashiCorp)
Terraform has been successfully initialized!
...
Next, run terraform plan to see what changes Terraform will make. This is a dry run that doesn't change anything but shows you the execution plan.
terraform plan
The output will show that one resource (the local_file inside your module) will be created.
...
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ created_file_path = (known after apply)
...
Now, apply the configuration to create the file. We'll use the -auto-approve flag to skip the interactive confirmation prompt.
terraform apply -auto-approve
Terraform will execute the plan and create the file. The output will confirm the creation and display the output value you defined.
...
module.file_creator_instance.local_file.example: Creating...
module.file_creator_instance.local_file.example: Creation complete after 0s [id=f73598097552a798110a31388c54c1194b539a53]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
created_file_path = "./modules/localfile_creator/my_test_file.txt"
Finally, verify that the file was created with the correct content. List the files in the module directory and then display the content of the new file.
ls -l modules/localfile_creator/
cat modules/localfile_creator/my_test_file.txt
The output of the cat command should be:
Hello from the root module!
Congratulations, you have successfully created and used a Terraform module!
Summary
In this lab, you have successfully learned the fundamentals of creating and using Terraform modules. You have gone through the entire process, from setting up a proper directory structure to deploying a resource via a reusable module.
You have learned to:
- Create a standard directory structure for local modules.
- Define resources within a module's
main.tffile. - Use
variables.tfto make your module configurable and reusable. - Use
outputs.tfto expose data from your module to the calling configuration. - Call a local module from a root
main.tffile, passing input variables and accessing output values. - Apply the configuration to see the module in action.
By mastering modules, you can write cleaner, more organized, and more scalable Infrastructure as Code. Well done on completing this lab!



