Localized slice

Terraform modules: contracts, composition, and safe evolution

How to design inputs, outputs, and provider dependencies that reuse infrastructure without turning the module into a black box.

Translation status: reviewedSource language: Español

A module is a contractual boundary

Inputs express allowed decisions, resources implement invariants, and outputs publish only what another component needs. Extracting a folder does not automatically create a useful abstraction: the contract should reduce coupling without hiding important operational choices.

Compose small modules in the root module

Terraform favors explicit composition rather than inheritance. The root module connects networking, identity, and workloads by passing outputs into inputs. Child modules should declare required providers and receive configurations from above, avoiding implicit credentials, aliases, or Regions that are hard to test.

  • Model cohesive objects instead of adding one switch for every possible resource.
  • Keep ownership, lifecycle, and state boundaries visible in the composition.

Version the contract and test the migration

Renaming resources, variable types, or outputs can break state addresses and consumers. Use constraints, a changelog, moved blocks, and tests to demonstrate compatibility. A major release should explain its migration path rather than merely announcing that breaking changes exist.

References

  1. [1]

    Terraform module development

    Official documentation for module structure, composition, and publication.

  2. [2]

    OpenTofu module development

    Equivalent OpenTofu contract for reusable modules and providers.