Level Up Your Infrastructure Game: Mastering Terraform Modules for Reusable Magic¶
Tired of cooking up your infrastructure from scratch every single time? Imagine if you had an Instant Pot for your cloud – something that could whip up a perfect server, database, or network setup with just a few ingredients. That's exactly what Terraform modules are: prepackaged recipes for your digital kitchen.
Why Modules? Because Nobody Likes Starting From Zero¶
Think of modules as your pre-built LEGO sets. Instead of painstakingly assembling every tiny brick for a spaceship, you grab a set that's already got the main body and wings ready to go. In Terraform, these sets are modules, and they help you create reusable infrastructure pieces.
Benefits You'll Actually Care About¶
- Speedy Delivery: Instead of waiting hours for your infrastructure to bake, modules give you "instant" results.
- Consistent Dishes: No more "oops, I forgot the salt" moments. Modules ensure your infrastructure tastes the same every time.
- Easy Cleanup: Modules keep your code organized, like having all your spices in labeled jars instead of scattered across the counter.
Let's Cook Up a Simple Module: An EC2 Server "Recipe"¶
We'll make a module that launches a basic server on AWS, like an "Instant Server" recipe.
1. Recipe Repository¶
The recipe repository name must follow this naming convention
Terraform module to deploy an AWS EC2 instance, the module name would be
terraform-aws-ec2-instance
2. Create a Recipe File Tree¶
mkdir terraform-aws-ec2-instance
cd terraform-aws-ec2-instance
touch main.tf variables.tf outputs.tf
mkdir -p examples/development
cd examples/development
touch 00-providers.tf 01-data.tf 01-locals.tf 09-module.tf 10-outputs.tf
terraform-aws-ec2-instance
├── examples
│ └── development
│ ├── 00-providers.tf
│ ├── 01-data.tf
│ ├── 01-locals.tf
│ ├── 09-module.tf
│ └── 10-outputs.tf
├── main.tf
├── outputs.tf
├── README.md
└── variables.tf
main.tf
: main file of the module.variables.tf
: module's input variables.outputs.tf
: module's outputs.examples/development
: an example of how to use this module.README.md
: This file must be generated by terraform-docs
3. Write the "Recipe."¶
terraform-aws-ec2-instance/main.tf | |
---|---|
terraform-aws-ec2-instance/outputs.tf | |
---|---|
4. Create the Example for the "Recipe."¶
examples/development/00-providers.tf | |
---|---|
examples/development/01-locals.tf | |
---|---|
examples/development/09-module.tf | |
---|---|
examples/development/10-outputs.tf | |
---|---|
5. Test Your "Recipe."¶
Initializing the backend...
Initializing modules...
- ec2-instance in ../..
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 4.0"...
- Installing hashicorp/aws v4.67.0...
- Installed hashicorp/aws v4.67.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
data.aws_vpc.default: Reading...
data.aws_ami.debian: Reading...
data.aws_ami.debian: Read complete after 1s [id=ami-00a5b694d38896fa5]
data.aws_vpc.default: Read complete after 2s [id=vpc-054a2a678b91e3149]
data.aws_subnets.defaults: Reading...
data.aws_subnets.defaults: Read complete after 1s [id=eu-west-1]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# module.ec2-instance.aws_instance.this will be created
+ resource "aws_instance" "this" {
+ ami = "ami-00a5b694d38896fa5"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_stop = (known after apply)
+ disable_api_termination = (known after apply)
+ ebs_optimized = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ host_resource_group_arn = (known after apply)
+ iam_instance_profile = (known after apply)
+ id = (known after apply)
+ instance_initiated_shutdown_behavior = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t3.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = (known after apply)
+ monitoring = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ subnet_id = "subnet-0ed8441022b56cedc"
+ tags = {
+ "module" = "nhamchanvi/terraform-aws-ec2-instance"
+ "repository" = "https://github.com/nhamchanvi/terraform-aws-ec2-instance"
+ "secnario" = "module-development"
+ "terraform" = "true"
}
+ tags_all = {
+ "module" = "nhamchanvi/terraform-aws-ec2-instance"
+ "repository" = "https://github.com/nhamchanvi/terraform-aws-ec2-instance"
+ "secnario" = "module-development"
+ "terraform" = "true"
}
+ tenancy = (known after apply)
+ user_data = (known after apply)
+ user_data_base64 = (known after apply)
+ user_data_replace_on_change = false
+ vpc_security_group_ids = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
data.aws_vpc.default: Reading...
data.aws_ami.debian: Reading...
data.aws_ami.debian: Read complete after 2s [id=ami-00a5b694d38896fa5]
data.aws_vpc.default: Read complete after 3s [id=vpc-054a2a678b91e3149]
data.aws_subnets.defaults: Reading...
data.aws_subnets.defaults: Read complete after 1s [id=eu-west-1]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# module.ec2-instance.aws_instance.this will be created
+ resource "aws_instance" "this" {
+ ami = "ami-00a5b694d38896fa5"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_stop = (known after apply)
+ disable_api_termination = (known after apply)
+ ebs_optimized = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ host_resource_group_arn = (known after apply)
+ iam_instance_profile = (known after apply)
+ id = (known after apply)
+ instance_initiated_shutdown_behavior = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t3.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = (known after apply)
+ monitoring = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ subnet_id = "subnet-0ed8441022b56cedc"
+ tags = {
+ "module" = "nhamchanvi/terraform-aws-ec2-instance"
+ "repository" = "https://github.com/nhamchanvi/terraform-aws-ec2-instance"
+ "secnario" = "module-development"
+ "terraform" = "true"
}
+ tags_all = {
+ "module" = "nhamchanvi/terraform-aws-ec2-instance"
+ "repository" = "https://github.com/nhamchanvi/terraform-aws-ec2-instance"
+ "secnario" = "module-development"
+ "terraform" = "true"
}
+ tenancy = (known after apply)
+ user_data = (known after apply)
+ user_data_base64 = (known after apply)
+ user_data_replace_on_change = false
+ vpc_security_group_ids = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
module.ec2-instance.aws_instance.this: Creating...
module.ec2-instance.aws_instance.this: Still creating... [10s elapsed]
module.ec2-instance.aws_instance.this: Creation complete after 19s [id=i-04d957d4f303de62d]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
data.aws_vpc.default: Reading...
data.aws_ami.debian: Reading...
data.aws_ami.debian: Read complete after 1s [id=ami-00a5b694d38896fa5]
data.aws_vpc.default: Read complete after 2s [id=vpc-054a2a678b91e3149]
data.aws_subnets.defaults: Reading...
data.aws_subnets.defaults: Read complete after 0s [id=eu-west-1]
module.ec2-instance.aws_instance.this: Refreshing state... [id=i-04d957d4f303de62d]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# module.ec2-instance.aws_instance.this will be destroyed
- resource "aws_instance" "this" {
- ami = "ami-00a5b694d38896fa5" -> null
- arn = "arn:aws:ec2:eu-west-1:842445166689:instance/i-04d957d4f303de62d" -> null
- associate_public_ip_address = true -> null
- availability_zone = "eu-west-1b" -> null
- cpu_core_count = 1 -> null
- cpu_threads_per_core = 2 -> null
- disable_api_stop = false -> null
- disable_api_termination = false -> null
- ebs_optimized = false -> null
- get_password_data = false -> null
- hibernation = false -> null
- id = "i-04d957d4f303de62d" -> null
- instance_initiated_shutdown_behavior = "stop" -> null
- instance_state = "running" -> null
- instance_type = "t3.micro" -> null
- ipv6_address_count = 0 -> null
- ipv6_addresses = [] -> null
- monitoring = false -> null
- placement_partition_number = 0 -> null
- primary_network_interface_id = "eni-0479a48d34b61566d" -> null
- private_dns = "ip-172-31-3-184.eu-west-1.compute.internal" -> null
- private_ip = "172.31.3.184" -> null
- public_dns = "ec2-54-74-231-76.eu-west-1.compute.amazonaws.com" -> null
- public_ip = "54.74.231.76" -> null
- secondary_private_ips = [] -> null
- security_groups = [
- "default",
] -> null
- source_dest_check = true -> null
- subnet_id = "subnet-0ed8441022b56cedc" -> null
- tags = {
- "module" = "nhamchanvi/terraform-aws-ec2-instance"
- "repository" = "https://github.com/nhamchanvi/terraform-aws-ec2-instance"
- "secnario" = "module-development"
- "terraform" = "true"
} -> null
- tags_all = {
- "module" = "nhamchanvi/terraform-aws-ec2-instance"
- "repository" = "https://github.com/nhamchanvi/terraform-aws-ec2-instance"
- "secnario" = "module-development"
- "terraform" = "true"
} -> null
- tenancy = "default" -> null
- user_data_replace_on_change = false -> null
- vpc_security_group_ids = [
- "sg-0078b23a0d63568f7",
] -> null
- capacity_reservation_specification {
- capacity_reservation_preference = "open" -> null
}
- cpu_options {
- core_count = 1 -> null
- threads_per_core = 2 -> null
}
- credit_specification {
- cpu_credits = "unlimited" -> null
}
- enclave_options {
- enabled = false -> null
}
- maintenance_options {
- auto_recovery = "default" -> null
}
- metadata_options {
- http_endpoint = "enabled" -> null
- http_put_response_hop_limit = 1 -> null
- http_tokens = "optional" -> null
- instance_metadata_tags = "disabled" -> null
}
- private_dns_name_options {
- enable_resource_name_dns_a_record = false -> null
- enable_resource_name_dns_aaaa_record = false -> null
- hostname_type = "ip-name" -> null
}
- root_block_device {
- delete_on_termination = true -> null
- device_name = "/dev/xvda" -> null
- encrypted = false -> null
- iops = 100 -> null
- tags = {} -> null
- throughput = 0 -> null
- volume_id = "vol-0a24924083e4afd34" -> null
- volume_size = 8 -> null
- volume_type = "gp2" -> null
}
}
Plan: 0 to add, 0 to change, 1 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
module.ec2-instance.aws_instance.this: Destroying... [id=i-04d957d4f303de62d]
module.ec2-instance.aws_instance.this: Still destroying... [id=i-04d957d4f303de62d, 10s elapsed]
module.ec2-instance.aws_instance.this: Still destroying... [id=i-04d957d4f303de62d, 20s elapsed]
module.ec2-instance.aws_instance.this: Still destroying... [id=i-04d957d4f303de62d, 30s elapsed]
module.ec2-instance.aws_instance.this: Still destroying... [id=i-04d957d4f303de62d, 40s elapsed]
module.ec2-instance.aws_instance.this: Still destroying... [id=i-04d957d4f303de62d, 50s elapsed]
module.ec2-instance.aws_instance.this: Destruction complete after 54s
Destroy complete! Resources: 1 destroyed.
6. Create README.md with terraform-docs¶
==> Downloading https://formulae.brew.sh/api/formula.jws.json
==> Downloading https://ghcr.io/v2/homebrew/core/terraform-docs/manifests/0.19.0
################################################################################################################################# 100.0%
==> Fetching terraform-docs
==> Downloading https://ghcr.io/v2/homebrew/core/terraform-docs/blobs/sha256:73916d978b414105ca9a9d3d264e064b91e90cd43180a6f391372275fa19d563
################################################################################################################################# 100.0%
==> Pouring terraform-docs--0.19.0.x86_64_linux.bottle.tar.gz
==> Downloading https://formulae.brew.sh/api/cask.jws.json
==> Caveats
zsh completions have been installed to:
/home/linuxbrew/.linuxbrew/share/zsh/site-functions
==> Summary
🍺 /home/linuxbrew/.linuxbrew/Cellar/terraform-docs/0.19.0: 8 files, 23.2MB
==> Running `brew cleanup terraform-docs`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
Why This Is Like an Instant Pot¶
module "ec2_instance"
: Says, "Hey Terraform, use my instant server recipe."source = "../../"
: Tells it where to find the recipe.- We give it the ingredients (ami, instance_type, name).
- The module cooks up the server, and we get the address.
Simple Cooking Tips¶
- Make recipes that do one thing well (like "Instant Pot Chicken").
- Write notes on your recipes (comments) so you remember what's what.
- Share your recipes with your team!
Conclusion¶
In essence, Terraform modules are your infrastructure's secret weapon, transforming complex deployments into streamlined, repeatable processes. By embracing this "Instant Pot" approach to infrastructure as code, you'll not only save valuable time and resources but also ensure consistency and collaboration across your team. So, ditch the tedious manual labor and start building with the reusable magic of Terraform modules, unlocking a new level of efficiency and simplicity in your cloud deployments.