Intermediate · Infrastructure as code with Terraform · topic 31 of 53 · 35 min
Terraform basics: HCL, providers, resources and the workflow
Explanation
Files and HCL syntax
Terraform reads every file ending in .tf in the current folder. Together they are one configuration. The language is HCL (HashiCorp Configuration Language). It has three building pieces.
- Blocks: a type, zero or more labels, and a body in curly braces, like resource "aws_s3_bucket" "logs" { ... }.
- Arguments: name = value lines inside a block, like bucket = "college-logs".
- Expressions: the values, such as strings, numbers, lists, maps, references to other things and function calls.
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0" # any 6.x, never 7.0; check the registry for the current major version
}
}
}
provider "aws" {
region = "ap-south-1" # Mumbai
}Providers
A provider is a plugin that knows how to talk to one API: AWS, Azure, Google Cloud, Kubernetes, GitHub, Cloudflare and many more. required_providers says which providers you need and which versions are allowed. The constraint "~> 6.0" means "6.0 or newer, but below 7.0", so a breaking major release is not picked up by surprise. terraform init downloads the providers.
Resources and references
A resource block declares one thing Terraform should create and manage. The first label is the resource type (from the provider), the second is your local name. Together they form the address, such as aws_s3_bucket.logs. You refer to an attribute of a resource with address.attribute, for example aws_s3_bucket.logs.id. Terraform uses these references to work out the order: a resource that refers to another is created after it.
resource "aws_s3_bucket" "logs" {
bucket = "${var.project}-logs-${var.environment}"
tags = local.common_tags
}
resource "aws_s3_bucket_versioning" "logs" {
bucket = aws_s3_bucket.logs.id # reference: created after the bucket
versioning_configuration {
status = "Enabled"
}
}Data sources
A data block reads something that already exists, without managing it. For example, look up the newest Ubuntu image ID or an existing VPC created by another team. You refer to it as data.aws_ami.ubuntu.id.
Variables, outputs and locals
Input variables are the parameters of your configuration. Each has a type, an optional default, an optional description and optional validation rules. Mark secrets with sensitive = true so Terraform hides them in its output. Outputs print useful results after apply (and let other code read them). Locals are named values you compute once inside the configuration to avoid repeating yourself.
variable "environment" {
type = string
description = "dev, staging or prod"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "environment must be dev, staging or prod."
}
}
variable "db_password" {
type = string
sensitive = true # hidden in plan output, but still stored in state
}
locals {
common_tags = { project = var.project, environment = var.environment, owner = "platform-team" }
}
output "logs_bucket_name" {
value = aws_s3_bucket.logs.bucket
}Many copies: count and for_each
count = 3 makes three copies addressed as aws_instance.web[0], [1] and [2]. for_each takes a map or a set of strings and makes one copy per key, addressed by key, like aws_s3_bucket.site["dev"]. Prefer for_each when items have names: if you remove the middle item of a count list, every later index shifts and Terraform wants to destroy and recreate them. With for_each only the removed key is affected.
resource "aws_s3_bucket" "site" {
for_each = toset(["dev", "staging", "prod"])
bucket = "college-site-${each.key}"
}The workflow
terraform init # download providers, set up the backend (run once, and after changes to them)
terraform fmt # format files in the standard style
terraform validate # check syntax and internal consistency
terraform plan # show what would change; changes nothing
terraform apply # show the plan again, ask "yes", then make the changes
terraform destroy # delete everything this configuration manages (careful!)Reading a plan
| Symbol | Meaning | Counts in the summary as |
|---|---|---|
| + | create a new resource | 1 to add |
| ~ | update in place (the resource stays) | 1 to change |
| -/+ | destroy, then create a replacement (some arguments cannot change in place) | 1 to add and 1 to destroy |
| +/- | create the replacement first, then destroy the old one (when create_before_destroy is set) | 1 to add and 1 to destroy |
| - | destroy | 1 to destroy |
| <= | read a data source | not counted |
The plan ends with a line such as "Plan: 2 to add, 1 to change, 1 to destroy." Look for "forces replacement" next to an argument: it means that change will delete and recreate the resource. For a database, that can mean data loss.
Common mistakes
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Terraform basics: HCL, providers, resources and the workflow · 1/8
In this lesson you will write your first Terraform configuration and learn the commands that run it.
Prefer a video by a teacher? Find free videos on “terraform tutorial for beginners aws providers resources plan apply” on YouTube ↗
In real life: A startup's static website bucket
A Bengaluru food-delivery startup keeps its marketing site in an S3 bucket. The configuration has a variable for the environment, one bucket per environment with for_each, and outputs for the bucket names. A developer changes a tag and runs plan: it shows "~ update in place" for three buckets and "Plan: 0 to add, 3 to change, 0 to destroy." Only then does she apply.
# aws_s3_bucket.site["dev"] will be updated in-place
~ resource "aws_s3_bucket" "site" {
~ tags = {
~ "team" = "web" -> "growth"
}
}
Plan: 0 to add, 3 to change, 0 to destroy.Practice (0/4 done)
Which command shows what Terraform would change, without changing anything?
Write summarise_plan(actions). actions is a list of strings, each one of "create", "update", "replace", "delete" or "no-op". Return the Terraform summary line "Plan: X to add, Y to change, Z to destroy." A replace counts as 1 to add AND 1 to destroy. no-op counts as nothing.
A plan line says: -/+ resource "aws_db_instance" "main" ... # forces replacement. What will apply do?
Write a variable block "instance_count" (a number, default 2) with a validation rule that allows only 1 to 5, and an output "bucket_arn" that shows the arn of aws_s3_bucket.logs. If you have an AWS free-tier account, run init, validate and plan (not apply) on it.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Terraform state, backends and driftInfrastructure as code with Terraform · same module
- Modules, environments and Terraform in CIInfrastructure as code with Terraform · same module
- Terraform for the cloudCloud Computing (AWS, Azure, Google Cloud) · Infrastructure as code
- IaC in a team: reviews, pipelines and policyCloud Computing (AWS, Azure, Google Cloud) · Infrastructure as code
- Why infrastructure as codeCloud Computing (AWS, Azure, Google Cloud) · Infrastructure as code
- AWS vs Azure vs Google Cloud: the service mapCloud Computing (AWS, Azure, Google Cloud) · Google Cloud and comparing the clouds
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What does the version constraint "~> 6.0" allow?
Answer: 6.0 or newer, but below 7.0
The pessimistic constraint ~> 6.0 allows 6.x versions but not the next major version.
How do you refer to the id of resource "aws_s3_bucket" "logs"?
Answer: aws_s3_bucket.logs.id
A resource address is TYPE.NAME, and you add the attribute after another dot.
What is a data block used for?
Answer: Reading an existing object without managing it
Data sources look things up, such as an AMI ID or a VPC made elsewhere.
Why is for_each often safer than count?
Answer: Removing one item does not shift the addresses of the others
count uses numbered indexes that shift; for_each uses stable keys.
A variable has sensitive = true. Which is true?
Answer: It is hidden in plan and apply output but can still appear in state
sensitive only redacts the value on screen. State can still hold it in plain text.
Which command checks the syntax and references of your .tf files without contacting the cloud to plan changes?Related
Answer: terraform validate
validate checks the configuration itself. plan is the step that compares it with real infrastructure.
What should happen to .terraform.lock.hcl?Related
Answer: Commit it to Git
The lock file pins exact provider versions and checksums for everyone, so it belongs in Git.
What does terraform state rm aws_s3_bucket.legacy do to the real bucket?Related
Answer: Nothing: Terraform just stops tracking it
state rm only removes the record. The real resource keeps existing.
Your company has 200 resources that were created in the console. What is the usual way to bring them under Terraform?Related
Answer: Import them (import blocks or terraform import), then plan until it shows no changes
Importing adds existing resources to the state; a clean plan proves the code matches them.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “Infrastructure as code with Terraform” before its exam: Terraform state, backends and drift, Modules, environments and Terraform in CI.
- Module 7 exam: Infrastructure as code with Terraform · 13 questions · 20 min, without help. 4 topics of this module still to finish first.
- Then the next module, Configuration management with Ansible, and YAML. 53 topics left in the course before the final exam.