Skip to main content
Understanding Terraform modules starts with a simple truth: a module is not a special language construct — it is a folder organized in a predictable way. When you grasp that pattern, building reusable modules becomes straightforward. At a high level, a Terraform module:
  • Defines resources (the “how”).
  • Declares inputs (the “what” the caller provides).
  • Exposes outputs (values other modules or the root module can consume).
Common files you’ll find in a module:
  • main.tf — resource definitions (execution layer).
  • variables.tf — inputs the module expects (module interface/contract).
  • outputs.tf — values the module exposes to callers.
Separating the “how” (module logic) from the “what” (caller-provided values) makes modules composable and reusable.
The image shows the file structure of a "Storage Module" with files named main.tf, variables.tf, and outputs.tf, highlighting that outputs.tf provides useful information like an endpoint URL.
Outputs are the communication channel between modules. Without outputs, modules cannot hand useful runtime values back to the root module or other modules that depend on them. Below are practical examples showing how to implement this pattern. The examples keep module implementation minimal while demonstrating inputs and outputs clearly. Example: a storage account resource implemented so every configurable attribute references a variable (caller decides name, location, SKU):
If your organization requires a specific configuration (for example, always use the Standard account tier), the module may enforce it by hard-coding that property:
Hard-coding an argument inside a module (e.g., account_tier = "Standard") is a valid way to enforce organizational policies. Do this only for properties you intend to make non-configurable for consumers.
Now we’ll walk through creating two simple modules in Visual Studio Code: a resource_group module and a storage_account module. These examples illustrate a clean module structure and how to wire modules together from a root module. Create the modules directory and subfolders:

Resource Group module

Path: modules/resource_group main.tf
variables.tf
outputs.tf
This module defines a resource group, declares the inputs it needs, and exposes the resource group’s ID so other modules or the root module can consume it.

Storage Account module

Path: modules/storage_account main.tf
variables.tf
outputs.tf
In this example the account_tier is enforced to Standard by the module. All other attributes are configurable, which keeps the module reusable while still meeting organizational constraints.

Using these modules from a root module

Once you have modules/resource_group and modules/storage_account, call them from your root module and wire outputs to inputs. Example minimal root module usage:
This pattern demonstrates:
  • Modules encapsulate implementation details.
  • Inputs (variables.tf) define the contract with callers.
  • Outputs (outputs.tf) enable composition between modules.

Best practices and tips

  • Keep modules focused and small: one module per logical resource or closely related set of resources.
  • Prefer variables over hard-coded values unless enforcing a policy.
  • Document variables and outputs with description fields so consumers know how to use the module.
  • Use semantic names for outputs (e.g., primary_blob_endpoint) so their intent is clear.
  • Version modules if you publish them to a registry or share across teams.
These resources will help deepen your understanding of modules and provider-specific resources.

Watch Video