Skip to content
← DevOps Foundations

Learning bite

Modules and environment boundaries

Reuse a configuration interface without confusing it with isolation.

  • Terraform
Documentation reviewed2026-10-01 · 3 min read
  1. Why
  2. Predict
  3. Try
  4. Inspect
  5. Explain
  6. Save evidence

Predict before you try

Does a valid configuration or an unchanged plan prove the application works?

Try it: Practice and answers →
On this page

Reuse a small piece before building a large abstraction

A Terraform module is a directory of configuration. The directory in which you run Terraform is the root module. It can call a child module, supplying inputs and receiving outputs. A module is useful when its interface explains a coherent capability; moving every resource into its own wrapper can instead make the configuration harder to follow.

Create a separate module-practice directory, leaving terraform-basics and its state untouched. Inside it create modules/service-label and environments/dev. Save modules/service-label/main.tf:

hcl
variable "environment" {
  type = string
}
variable "service_name" {
  type = string
}
resource "terraform_data" "label" {
  input = "${var.environment}-${var.service_name}"
}
output "label" {
  value = terraform_data.label.output
}

Save environments/dev/main.tf:

hcl
terraform {
  required_version = ">= 1.12, < 2.0"
}
module "service" {
  source       = "../../modules/service-label"
  environment  = "dev"
  service_name = "accounts"
}
output "service_label" {
  value = module.service.label
}

Run terraform init, terraform validate, and terraform plan from environments/dev. Expect one proposed resource at module.service.terraform_data.label. The root can read the child's published label output; it does not reach into arbitrary internal resources. No apply is necessary for this exercise.

Change the consumer, not the shared implementation

Create environments/qa, copy only the dev root's main.tf into it, and change the input to environment = "qa". Initialize and plan there. The same child module now describes a qa-accounts label, with separate local state if later applied. Do not copy .terraform, state, or saved plans between roots.

A reusable module should document supported inputs, non-secret outputs, assumptions, and upgrade behavior. Root modules normally configure providers; children declare their requirements and receive configurations. Aliases allow deliberate provider mappings, such as a second region. Changing an AWS provider name to GCP does not translate AWS resource schemas into Google Cloud resources.

Remote modules also need version control. Registry sources support a version constraint; Git sources select a ref in the source URL. Provider lock files do not lock arbitrary remote modules. Pin reviewed module versions and inspect plans when upgrading.

Separate names from actual isolation

A CLI workspace gives one configuration/backend a separate state instance. Calling a workspace production does not supply different credentials, prevent access to another account, or isolate a network. Two root directories are also not a security barrier on their own.

For a real environment, identify four things explicitly: who can run changes, which account or target they can access, where its state is stored, and which inputs distinguish it. The local dev/qa directories demonstrate independent state without claiming production isolation.

Refactoring an already managed root resource into this child changes its address. Without a reviewed migration, Terraform can interpret the old address as removed and the new one as needing creation. A moved block can preserve identity when supported. Do not perform that refactor on the earlier fixture just to make its layout match this one.

Practice and answers

Change only the QA service_name to ledger and plan both roots. QA should propose a different label; dev's source input remains accounts. If both roots change, inspect whether you edited the shared child instead. Why initialize after adding a module call? Terraform must discover and install its module dependencies before planning.

Retain the two small roots as an example of reuse. If you chose to apply either, review and run its own destroy plan before removing its state. The platform module lesson later extends this interface into target-specific infrastructure.

References: module usage↗, developing modules↗, workspaces↗, and refactoring↗.

Prove what you learned

Inspect the result

Retain the selected target, plan/diff, resource observation and unresolved application check.

Explain it in your own words

Explain why your observation supports the stated outcome, and name one thing it does not prove.

Try a small variation

Change one supported input in the disposable local exercise, predict the diff, and explain a second plan.

Your evidence should show

  • Record the context/revision or label the work as a paper or prepared fixture.
  • Compare the prediction with the actual result from “Practice and answers”.
  • Retain the selected target, plan/diff, resource observation and unresolved application check.
  • Explain the transfer challenge and retain the same verification question.
Hint

Separate configuration checks, selected state/target, proposed change and runtime behaviour.

Answer guide: compare after predicting

No. Inspect the plan and target, then verify the declared resource and application outcome separately.

Compare this with your actual observation. A paper answer is useful reasoning practice; record execution separately.

Recall Git review: inspect the exact change rather than only the exit status. Revisit the previous lesson.

Save the prediction, result and explanation ↓

Your notes and evidence

Record observations, questions, or links to your work. Keep credentials out of your notes.

Loading saved progress…

Back up or restore this path

Progress and notes stay in this browser. A backup contains only this learning path.