BlogIaC

Writing reusable Terraform modules that don't fight you

Design modules with clear inputs and outputs, sane defaults, and versioning so teams reuse them without forking.

Oct 15, 2025·10 min readIntermediate

A good Terraform module is a small contract: clear inputs, useful outputs, sane defaults, and a version. A bad one leaks its internals and forces every caller to fork it. The difference is mostly discipline at the interface.

The shape of a module

modules/bucket/
modules/bucket/
variables.tf # the inputs (the contract)
main.tf # the resources
outputs.tf # what callers can use
README.md # examples + the contract in prose

Validate the inputs

Defaults make the common case one line; validation turns a misconfiguration into a plan-time error instead of a 2am incident.

variables.tf
variable "name" {
type = string
validation {
condition = can(regex("^[a-z0-9-]+$", var.name))
error_message = "name must be lowercase alphanumeric or dashes."
}
}
variable "versioning" {
type = bool
default = true
}

Call it with a pinned version

main.tf
module "logs" {
source = "app.terraform.io/acme/bucket/aws"
version = "~> 2.1"
name = "acme-app-logs"
}
Never float a module version
An unpinned source means a module release can change your infrastructure on the next plan. Pin with ~> and bump deliberately.
Go deeper in a courseTerraformModules, state, and CI/CD for the standard IaC tool.View course

Related posts