BuildBot

IaC Core

Variables, outputs & modules

Lesson 4 of 5

What you'll learn

  • Parameterize configuration with typed input variables
  • Expose computed values to callers with outputs
  • Package resources into a reusable module with a clean interface

Hard-coded values don't scale across teams or environments. Variables turn a config into a function of its inputs; outputs are its return values.

variable "instance_count" {
  type    = number
  default = 2
}

resource "aws_instance" "web" {
  count         = var.instance_count
  instance_type = "t3.small"
  ami           = "ami-0abc123"
}

output "instance_ids" {
  value = aws_instance.web[*].id
}

A module is a function over infrastructure

A module is a directory of .tf files with declared variable inputs and output returns. Calling it is like calling a function: pass inputs, get named resources and outputs back. This is how you stop copy-pasting a VPC across ten projects.

module "network" {
  source     = "./modules/network"
  cidr_block = "10.0.0.0/16"
  azs        = ["us-east-1a", "us-east-1b"]
}

resource "aws_instance" "api" {
  subnet_id = module.network.subnet_ids[0]   # consume an output
}

The interface is the contract

Callers depend only on a module's inputs and outputs, never its internals. That boundary lets you refactor what's inside — swap resources, add defaults — without breaking consumers, exactly like a well-typed function signature.

Version your shared modules

Pin modules from a registry or Git with a version constraint (version = "~> 3.1"). An unpinned source means a colleague's terraform init can silently pull a breaking change into your plan.

A module as a function

Run it. The module takes inputs and returns named resources plus outputs, with defaults applied.

Loading editor…
Knowledge check

Why can you safely refactor the resources inside a module without breaking its callers?

Saved on this device. Sign in to sync your progress everywhere.