CoursesInfrastructure as Code & automationBeginner · Terraform basics

Terraform: providers, resources & HCL

The core building blocks.

Beginner14 min · lesson 4 of 23

Terraform is where most people start with IaC, so we build it up properly. You write Terraform in its own language, HCL (HashiCorp Configuration Language) — readable, declarative, purpose-built for describing resources. Three concepts are the foundation: providers (plugins that talk to a platform like AWS or Kubernetes), resources (the things you want to exist — a server, a bucket, a DNS record), and the configuration files (.tf) that declare them. Get these three and you can read most Terraform.

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" { # a resource: TYPE "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 a specific platform’s API — there are providers for AWS, Azure, Google Cloud, Kubernetes, GitHub, Cloudflare, and hundreds more. You declare which providers you need (pinned to a version), 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 cloud-agnostic: the same tool and workflow manage anything that has a provider.

Resources, arguments & references

A resource block declares one thing that should exist: resource "aws_instance" "web" says "an AWS instance I will call web." Inside, arguments set its properties (the AMI, the instance type, tags). The powerful part is that resources can reference each other’s attributes — aws_instance.web.id — so Terraform learns the dependencies between them and creates them in the right order automatically. You describe the resources and how they relate; Terraform figures 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 → creates SG first
}
Always pin provider versions
Leaving a provider version unpinned means terraform init can pull a newer major version that changed or removed arguments, breaking your config or — worse — proposing unexpected changes to live infrastructure. Pin providers (version = "~> 5.0") and commit the generated .terraform.lock.hcl lock file so every run, on every machine and in CI, uses the exact same provider versions. This is the same supply-chain/reproducibility discipline from the other courses, applied to your infrastructure tooling.