Skip to content

Terraform — Basics

Declarative IaC. HCL (HashiCorp Configuration Language). Provisions cloud + SaaS resources via providers. Plans diffs before apply.

  • Provider — plugin per cloud/service (aws, google, kubernetes, github, datadog).
  • Resource — managed thing (aws_instance, aws_s3_bucket).
  • Data source — read-only lookup (data "aws_ami" ...).
  • Variable — input. variable "region" { type = string default = "eu-west-1" }.
  • Output — exposed value. output "bucket_arn" { value = ... }.
  • Local — computed value. locals { name = "${var.env}-app" }.
  • Module — reusable group of resources. Path = source.
  • State — JSON file mapping config → real resources. Source of truth.
  • Backend — where state lives (local, S3+DynamoDB lock, GCS, Terraform Cloud).
  • Workspace — separate state per env within same config (terraform workspace).
Terminal window
terraform init # download providers + init backend
terraform fmt # format
terraform validate # syntax/type check
terraform plan # diff
terraform apply # apply (interactive confirm or -auto-approve)
terraform destroy # tear down
terraform state list / state show / state mv / state rm / import
lifecycle {
prevent_destroy = true
create_before_destroy = true
ignore_changes = [tags["LastModified"]]
replace_triggered_by = [aws_iam_policy.x.policy]
}
variable "env" {
type = string
description = "deployment env"
default = "dev"
validation {
condition = contains(["dev","stg","prod"], var.env)
error_message = "env must be dev/stg/prod"
}
}

Sources (precedence): CLI -var / -var-file > *.auto.tfvars > terraform.tfvars > env TF_VAR_x > defaults.

module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "main"
cidr = "10.0.0.0/16"
azs = ["eu-west-1a", "eu-west-1b"]
...
}

Can be local path, git, registry, S3.

Pin versions! version = "~> 5.0" allows patch/minor; = 5.0.0 exact.

terraform {
backend "s3" {
bucket = "tfstate-prod"
key = "platform/network.tfstate"
region = "eu-west-1"
dynamodb_table = "tflock"
encrypt = true
}
}

DynamoDB table provides state locking — prevents two apply from clobbering.

length, concat, merge, lookup, try, for, flatten, format, jsonencode, templatefile, cidrsubnet, regex, replace, sort.

locals {
subnets = [for i in range(3) : cidrsubnet("10.0.0.0/16", 8, i)]
}
# count
resource "aws_instance" "x" {
count = 3
ami = ...
}
# for_each on map (preferred — stable keys)
resource "aws_iam_user" "x" {
for_each = toset(["alice","bob"])
name = each.key
}

for_each is safer than count because adding/removing keys doesn’t shift indexes.

  • aws, google, azurerm
  • kubernetes, helm
  • github, gitlab
  • cloudflare, fastly
  • datadog, pagerduty
  • random, tls, null, time, archive