HCL, plan, apply: the declarative cycle
Summary: Terraform revolves around one language (HCL, the HashiCorp Configuration Language) and a four-command cycle (init, plan, apply, destroy). This lesson shows what a real .tf file looks like, dissects it line by line, explains the crucial difference between Terraform's declarative approach and the imperative approach of a shell script, and demonstrates why the terraform plan command has saved thousands of productions.
1. HCL — Terraform's DNA
HCL (HashiCorp Configuration Language) is the proprietary language used by all HashiCorp tools — Terraform, Vault, Consul, Packer, Nomad. It was designed specifically to describe infrastructure configurations.
HCL is not:
- A general-purpose programming language — you cannot write a web application or an algorithm.
- Pure JSON — even though HCL can be exported to JSON, you rarely write JSON by hand.
- YAML — the syntax is different and deliberately more human-readable.
HCL is: a declarative language halfway between JSON and YAML, optimized for infrastructure.
1.1 · What a .tf file looks like
Here is a real Terraform file that creates a virtual machine on AWS. Don't try to memorize it — we are just looking at the shape.
# Block 1: AWS provider configuration
provider "aws" {
region = "us-east-1"
}
# Block 2: resource - an EC2 virtual machine
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-server-production"
Environment = "prod"
ManagedBy = "terraform"
}
}
# Block 3: output - retrieve the public IP address
output "public_ip_address" {
value = aws_instance.web_server.public_ip
}
Block-by-block decoding:
| Block | What it does |
|---|---|
provider "aws" | Tells Terraform we are working with AWS, region us-east-1 |
resource "aws_instance" "web_server" | Declares the creation of an EC2 VM named web_server |
ami = "ami-0c55b159cbfafe1f0" | The disk image to use (Ubuntu 22.04 in this example) |
instance_type = "t3.micro" | The VM size (2 vCPU, 1 GB RAM) |
tags = { ... } | Labels to classify the resource |
output "public_ip_address" | A value to display after creation — here the public IP |
A few important observations:
- The syntax is very readable. A non-technical person roughly understands what the file does.
- No imperative command. There is no "run this then do that". We describe the desired state.
- Blocks can be spread across several
.tffiles — Terraform reads them all together.
2. Declarative vs imperative — the fundamental difference
This is the concept you absolutely must understand to grasp Terraform's magic.
The GPS analogy is telling:
- Imperative: "turn right in 200 metres, then left, then right at the third traffic light". If a traffic jam blocks a street, the whole plan falls apart.
- Declarative: "I want to go to this address". The GPS recalculates on its own in case of a blockage.
Terraform is a GPS for your cloud. You tell it where you want to arrive, it computes the best path, and it recomputes if the real state diverges from the desired state.
3. The complete cycle — the four essential commands
Terraform is mainly used through four commands in a precise order.
In daily practice, your work loop looks like this:
- Edit a
.tffile to add/modify a resource. - Run
terraform planto see what will change. - Review the plan carefully — this is where safety is at stake.
- Run
terraform applyto apply. - Commit the change in Git.
4. The command that has saved thousands of productions: terraform plan
terraform plan is probably the most important command in the entire Infrastructure as Code ecosystem.
4.1 · What a plan displays
Here is what the result of a terraform plan looks like (simplified).
Terraform will perform the following actions:
# aws_instance.web_server will be created
+ resource "aws_instance" "web_server" {
+ ami = "ami-0c55b159cbfafe1f0"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ instance_type = "t3.micro"
+ tags = {
+ "Environment" = "prod"
+ "ManagedBy" = "terraform"
+ "Name" = "web-server-production"
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
Decoding:
- The green
+in front of each line means "creation". - The
(known after apply)values are those that AWS will assign dynamically (IP address, ARN). - The last line summarizes everything: 1 to add, 0 to change, 0 to destroy.
This format also exists with ~ for modifications, and - for destructions.
4.2 · Why it is revolutionary
Before Terraform, making a change in the cloud meant finding out on the fly whether you were breaking something. With terraform plan, you see everything before applying.
Absolute rule in a team: never a terraform apply without a terraform plan reviewed by a human first. This discipline avoids 95% of infrastructure incidents.
5. The syntactic fundamentals of HCL
You do not need to memorize HCL for this discovery course. But here are the 4 fundamental constructs to recognize at first glance.
5.1 · The block — the basic unit
type "type_name" "user_name" {
attribute = value
}
Real examples:
provider "aws" {}— configures a provider.resource "aws_instance" "web" {}— declares a resource.variable "region" {}— declares a variable.output "ip" {}— declares an output.
5.2 · Variables — to make the code parameterizable
variable "region" {
description = "AWS region to use"
type = string
default = "us-east-1"
}
resource "aws_instance" "web" {
ami = "ami-..."
instance_type = "t3.micro"
# The variable is used here
# Note: double quotes surround the reference
}
Variables allow you to separate the static configuration (the .tf files) from the values that change per environment (dev, staging, prod). This is the key to multi-environment reproducibility.
5.3 · References — how to link resources together
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "web" {
# Reference to the ID of the VPC created above
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
}
Terraform uses these references to build a dependency graph and create the resources in the right order — first the VPC, then the subnet that depends on it.
5.4 · Outputs — to expose values after creation
output "public_ip" {
description = "Public IP address of the web server"
value = aws_instance.web.public_ip
}
After an apply, Terraform displays the outputs — useful for retrieving values to pass to other tools (Ansible, test scripts…).
6. A complete example — a static website in 20 lines of HCL
To show Terraform's power, here is an example that creates a complete static website on AWS S3.
# S3 bucket that holds the site's files
resource "aws_s3_bucket" "site" {
bucket = "my-great-site-2026"
}
# Enable web hosting on the bucket
resource "aws_s3_bucket_website_configuration" "site" {
bucket = aws_s3_bucket.site.id
index_document {
suffix = "index.html"
}
}
# Public policy to make the files readable by everyone
resource "aws_s3_bucket_policy" "site" {
bucket = aws_s3_bucket.site.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.site.arn}/*"
}]
})
}
output "site_url" {
value = "http://${aws_s3_bucket.site.bucket_regional_domain_name}"
}
In 20 lines of HCL, you have:
- An S3 bucket.
- Web hosting enabled.
- A public read policy.
- An output that gives you the site's URL.
One terraform apply creates all of this in about thirty seconds. One terraform destroy deletes it just as fast. Repeat n times = n identical sites in n different regions.
That is all of Terraform's magic.
7. What you will only see in the Premium course
This discovery course deliberately stops at this conceptual level. The Premium Terraform Course goes deeper into:
- How to actually structure a Terraform project in production.
- Loops (
for_each,count) to create 10 similar resources in one go. - Conditions (
dynamic, ternaries) to make the code adaptive. - HCL functions (
jsonencode,templatefile,lookup). - Data sources to retrieve existing values.
- Secrets management with Vault and sensitive variables.
- The CI/CD workflow with GitHub Actions or GitLab CI.
Remember in 30 seconds
- HCL (HashiCorp Configuration Language) is Terraform's declarative language, a hybrid between JSON and YAML.
- The Terraform cycle:
terraform init→terraform plan→terraform apply→ (terraform destroyat end of life). - Terraform is declarative: you say what, not how. Terraform computes the path.
terraform planis the most important command — it shows what will change before applying.- Absolute rule in a team: never an
applywithout aplanreviewed by a human. - The HCL syntax rests on 4 constructs: blocks, variables, references, outputs.
Next: Providers, resources, modules: the Terraform trinity →