HCL: providers, resources & references

The core building blocks of a config.

Beginner14 min · lesson 2 of 15

You write Terraform in HCL (HashiCorp Configuration Language) — a readable, declarative language purpose-built for describing resources. Three concepts are the whole foundation: providers (plugins that talk to a platform’s API), resources (the things you want to exist), and the .tf files that declare them. Get these three and you can read most Terraform in the wild.

main.tf
terraform {
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" } # which provider, pinned
}
}
provider "aws" {
region = "us-east-1" # configure the provider
}
resource "aws_instance" "web" { # resource TYPE "local name"
ami = "ami-0abc123"
instance_type = "t3.micro"
tags = { Name = "web-server" }
}

Providers: plugins for platforms

A provider is a plugin that knows how to talk to one platform’s API — AWS, Azure, Google Cloud, Kubernetes, GitHub, and hundreds more. You declare which providers you need and pin their versions, Terraform downloads them on terraform init, and each provider exposes a set of resource types you can then declare. The provider is what makes Terraform universal: the same workflow manages anything with a provider.

Resources, arguments & references

A resource block declares one thing that should exist. Inside, arguments set its properties. The powerful part is that resources reference each other’s attributes — aws_security_group.web_sg.id — and from those references Terraform builds a dependency graph and creates things in the correct order automatically. You describe the resources and how they relate; Terraform works out the order of operations.

main.tf
resource "aws_security_group" "web_sg" {
name = "web-sg"
ingress { from_port = 443, to_port = 443, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"] }
}
resource "aws_instance" "web" {
ami = "ami-0abc123"
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.web_sg.id] # reference → SG created first
}
Always pin provider versions and commit the lock file
An unpinned provider means terraform init can pull a newer major version that changed or removed arguments — breaking your config or proposing surprise changes to live infrastructure. Pin providers (version = "~> 5.0") and commit the generated .terraform.lock.hcl so every run, on every machine and in CI, resolves the exact same versions. Same reproducibility discipline the rest of the platform demands, applied to your tooling.