Terraform Fundamentals · Module 07 of 12

Modules, version constraints and dependency locks

Design reusable modules with explicit interfaces, separate root and child module responsibilities, and use version constraints plus the provider dependency lock file deliberately.

Learning objectives

By the end of this module, you should be able to:

  • Create and call a local child module.
  • Define clear module inputs and outputs.
  • Explain the difference between version constraints and .terraform.lock.hcl.

Modules are configuration packages

Every Terraform configuration is a module. A root module can call child modules from local paths, registries or version-control sources. Reuse should encapsulate a meaningful infrastructure capability, not merely reduce line count.

Inputs and outputs form the module contract

Variables define accepted inputs and outputs expose intentional results. Avoid forcing callers to know internal resource addresses when a stable output can serve as the interface.

Constraints and locks solve different problems

Version constraints declare acceptable versions. The dependency lock file records selected provider versions and checksums so future initialization can reproduce the same provider selection until intentionally upgraded.

Worked examples

See the idea in practice.

Local module call

module "service" {
  source = "./modules/service"
  name   = "api"
}

output "service_name" {
  value = module.service.name
}
  • The caller consumes module outputs rather than reaching into child internals.
Hands-on lab

Lab — Build and consume a child module

Goal: Create a reusable local module with an input, managed object and output.

Before you start

  • Terraform CLI only.
STEP 1

Create module

mkdir -p ~/gnu-terraform-labs/module07/modules/service && cd ~/gnu-terraform-labs/module07
cat > modules/service/main.tf <<'EOF'
variable "name" {
  type = string
}

resource "terraform_data" "this" {
  input = var.name
}

output "name" {
  value = terraform_data.this.output
}
EOF
STEP 2

Call module

cat > main.tf <<'EOF'
module "service" {
  source = "./modules/service"
  name   = "api"
}

output "service_name" {
  value = module.service.name
}
EOF
STEP 3

Initialize and apply

terraform init
terraform apply -auto-approve
STEP 4

Inspect output

terraform output -raw service_name

Verify

  • The child module initializes and applies.
  • The root output is api.

Expected outcome

  • The module exposes a small stable interface rather than leaking its internals.

If it fails

  • Run terraform init again after adding or changing module sources.
  • Do not add a provider block inside reusable child modules unless there is a specific advanced reason.
Real-world connection

Internal module registries and semantic versioning let teams reuse approved infrastructure patterns while controlling change.

Avoid these traps

Common mistakes

  • Creating giant modules that expose every provider argument.
  • Referencing child module internal resources from outside the module.
  • Deleting .terraform.lock.hcl casually in application/root configurations.
Knowledge check

Can you explain it without looking back?

Open each answer only after you have tried to answer the question yourself.

1What makes a useful module interface?

Clear inputs, intentional outputs and encapsulated implementation details.

2What does .terraform.lock.hcl record?

Selected provider versions and checksums.

3When must terraform init be rerun?

When provider/backend/module installation requirements change.

My Learning

Record your progress for this module.

Progress is saved in this browser immediately. When you sign in to My Learning, it can also sync across devices.

Not started

Attempt the core lab and knowledge checks before marking the module complete.