Terraform uses HCL (HashiCorp Configuration Language). The full language reference is at developer.hashicorp.com/terraform/language.

The two building blocks

Everything in HCL is either an argument or a block (docs).

Arguments assign a value to a name:

zone = "us-central1-a"

Blocks group related arguments under a keyword, with optional labels:

resource "google_compute_instance" "vm" {
  # arguments and nested blocks go here
}

The pattern is: block_type "label_1" "label_2" { ... }. Not all block types take labels — terraform {} takes none, resource takes two (type + local name), variable takes one (name).

Block types you’ll encounter

BlockPurpose
resourceCreates and manages an infrastructure object
dataReads existing infrastructure — no create or modify
variableDeclares an input the caller must provide
outputExposes a value for display or for parent modules to consume
localsComputes intermediate values for reuse within the module
moduleCalls a child module (a folder of .tf files)
providerConfigures the cloud provider (credentials, region, etc.)

Reference patterns

Values are referenced by a dotted path. The type of the first segment tells you what you’re reading (docs):

ReferenceReads from
var.zoneInput variable named zone
google_compute_instance.vm.idid attribute of a resource block labelled vm
data.google_compute_zones.available.namesnames attribute of a data block
local.project_prefixA locals block value
module.network.subnet_idOutput subnet_id exported by the module "network" call

data vs resource

A resource block creates something. A data block only reads something that already exists — Terraform makes no changes to it (docs):

# creates a VM
resource "google_compute_instance" "vm" { ... }
 
# reads available zones — nothing is created
data "google_compute_zones" "available" {
  region = var.region
}

locals

locals lets you name a computed expression and reuse it, avoiding repetition (docs):

locals {
  name_prefix = "${var.project}-${var.env}"
}
 
resource "google_compute_instance" "vm" {
  name = "${local.name_prefix}-vm"
}

Meta-arguments

Every resource block accepts a set of meta-arguments that control its lifecycle (docs):

Meta-argumentWhat it does
depends_onForces Terraform to complete another resource before this one, even without an implicit reference
countCreates N identical copies of the resource
for_eachCreates one resource per entry in a map or set
lifecycleControls create/destroy behaviour (see below)

Common lifecycle rules:

lifecycle {
  create_before_destroy = true   # spin up replacement before destroying old
  prevent_destroy       = true   # block any apply that would destroy this resource
  ignore_changes        = [tags] # don't drift-detect changes to these attributes
}

Modules

A module block calls a child module — a separate folder of .tf files. The label in quotes is a local name you assign to that call:

module "network" {
  source = "./modules/network"
  zone   = var.zone
}
  • "network" — local label, arbitrary; used to reference the module’s outputs as module.network.<output>
  • source — path to the folder containing the child module’s .tf files
  • everything else — input variables passed into the child

The same module can be called multiple times under different labels to create separate instances:

module "network_us" { source = "./modules/network"; zone = "us-central1-a" }
module "network_eu" { source = "./modules/network"; zone = "europe-west1-b" }

Variable scoping: child modules do not inherit the parent’s variables. The child must declare its own variables.tf, and values must be passed explicitly. Nothing leaks in automatically (docs).

See also