- Understand what modules are and how they make Terraform configurations reusable.
- Learn how to structure and call child modules.
- Explore the purpose and configuration of backends for storing state remotely.
- Apply best practices for using modules and managing backend configurations securely.
A module is a collection of Terraform configuration files in a folder.
Modules let you group related resources together and reuse them across multiple projects or environments.
You already use one module every time you run Terraform — the root module (your main working directory).
Modules help you:
- Reuse and standardize infrastructure patterns
- Avoid repetitive code
- Simplify environment management (dev, stage, prod)
- Share architecture blueprints with teams
Example use case:
Instead of writing EC2, VPC, and security group resources multiple times, you define them once in a module and call it wherever needed.
A simple reusable module usually includes:
my-vpc-module/
├── main.tf # defines the actual resources
├── variables.tf # defines inputs
├── outputs.tf # defines what values get returned
└── README.md # explains usage
Then your root module (where you run Terraform) can call it like this:
module "vpc" {
source = "./modules/my-vpc-module"
cidr_block = "10.0.0.0/16"
env = "dev"
}Terraform can load modules from multiple sources:
| Source Type | Example | Use Case |
|---|---|---|
| Local path | source = "./modules/vpc" |
Within your project |
| Git repo | source = "git::https://github.com/TerminalsandCoffee/terraform-vpc.git" |
Shared code or team repo |
| Terraform Registry | source = "terraform-aws-modules/vpc/aws" |
Public community modules |
| S3 bucket / GCS | source = "s3::https://s3.amazonaws.com/terraform-modules/vpc.zip" |
Private hosted modules |
Modules use input variables to accept configuration values from the caller.
Inside the module (variables.tf):
variable "cidr_block" {
type = string
description = "VPC CIDR range"
}
variable "env" {
type = string
default = "dev"
}In the root module (where module is called):
module "vpc" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
env = "prod"
}Modules can export values (like resource IDs) to the root module.
Inside the module (outputs.tf):
output "vpc_id" {
description = "ID of the created VPC"
value = aws_vpc.main.id
}In the root module:
output "vpc_id" {
value = module.vpc.vpc_id
}Run:
terraform output vpc_idFolder structure:
terraform-associate-study/
├── main.tf
└── modules/
└── s3_bucket/
├── main.tf
├── variables.tf
└── outputs.tf
s3_bucket/main.tf:
resource "aws_s3_bucket" "this" {
bucket = var.bucket_name
tags = {
Environment = var.env
}
}s3_bucket/variables.tf:
variable "bucket_name" {
type = string
description = "Name of the S3 bucket"
}
variable "env" {
type = string
default = "dev"
}s3_bucket/outputs.tf:
output "bucket_arn" {
value = aws_s3_bucket.this.arn
description = "ARN of the created bucket"
}main.tf (root):
provider "aws" {
region = "us-east-1"
}
module "storage" {
source = "./modules/s3_bucket"
bucket_name = "my-terraform-study-bucket"
env = "dev"
}
output "bucket_arn" {
value = module.storage.bucket_arn
}Run:
terraform init
terraform apply -auto-approveA backend defines where and how Terraform stores its state. It determines how state is loaded, stored, and locked during operations.
Without a backend, Terraform defaults to a local backend — terraform.tfstate in your working directory.
You can configure a remote backend for shared, secure state (e.g., AWS S3, Terraform Cloud).
terraform {
backend "s3" {
bucket = "terraform-study-state"
key = "network/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "state-locking"
encrypt = true
}
}bucket: where the state file is storedkey: path within the bucketregion: AWS regiondynamodb_table: optional — adds state lockingencrypt = true: enables SSE encryption at rest
Run:
terraform initTerraform will migrate or set up your remote state automatically.
If you initially used a local state, and then add a backend block, Terraform will detect the change and prompt you:
"Do you want to copy your existing state to the new backend?"
Always say yes to migrate the existing resources safely.
You can also migrate manually with:
terraform init -migrate-stateCommon backend types and their use cases:
| Backend | Use Case | Supports Locking? |
|---|---|---|
| local | Simple, single-user local files | ❌ No |
| s3 | Team collaboration via AWS S3 + DynamoDB | ✅ Yes |
| terraform cloud / enterprise | Managed remote state with workspace automation | ✅ Yes |
| azurerm / gcs / consul | Platform-specific remote storage | ✅ Varies |
- Backends are configured in the terraform block, not the provider block.
- Backend settings can’t use variables or
count/for_each. terraform initis required after any backend change.- Backends store only state, not configuration.
- Terraform never includes backend credentials in state files.
| Practice | Reason |
|---|---|
| Store reusable logic in modules | Promotes DRY (Don’t Repeat Yourself) |
| Use remote state backends | Enable team collaboration and persistence |
| Version control your modules | Track changes safely |
| Keep backend config minimal | Avoid embedding secrets in backend blocks |
| Use consistent naming for module folders | Improves readability |
| Test modules independently | Validate logic before reuse |
Which module source type would you use to load a module from a Git repository?
A) source = "./modules/vpc"
B) source = "git::https://github.com/user/module.git"
C) source = "terraform-aws-modules/vpc/aws"
D) source = "modules/vpc"
Show Answer
Answer: **B** - Git repositories use the `git::` protocol prefix. Option A is a local path, C is the Terraform Registry format, and D is invalid.Can you use variables in the backend configuration block? A) Yes, any variable type B) Yes, but only string variables C) No, backend configuration is static and cannot use variables D) Only in certain backends
Show Answer
Answer: **C** - Backend configuration blocks cannot use variables, functions, or any expressions. They must be static values. Use `-backend-config` flag or backend config files for dynamic values.What is the purpose of the required_providers block in a module?
A) Configure provider credentials
B) Declare which providers and versions the module needs
C) Set provider region
D) Define provider aliases
Show Answer
Answer: **B** - The `required_providers` block declares provider dependencies and version constraints. It doesn't configure credentials or settings, which are done in provider blocks.- Modules group resources and make Terraform reusable, maintainable, and modular.
- Inputs pass data into a module; outputs pass data out.
- Backends control where Terraform stores state (local vs remote).
- Remote backends (S3 + DynamoDB) provide collaboration, encryption, and locking.
- Backend config is static — cannot depend on variables.
- Together, modules and backends form the foundation of scalable Terraform architecture.