Skip to main content

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:

BlockWhat 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 .tf files — 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:

  1. Edit a .tf file to add/modify a resource.
  2. Run terraform plan to see what will change.
  3. Review the plan carefully — this is where safety is at stake.
  4. Run terraform apply to apply.
  5. 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.

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 initterraform planterraform apply → (terraform destroy at end of life).
  • Terraform is declarative: you say what, not how. Terraform computes the path.
  • terraform plan is the most important command — it shows what will change before applying.
  • Absolute rule in a team: never an apply without a plan reviewed by a human.
  • The HCL syntax rests on 4 constructs: blocks, variables, references, outputs.

Next: Providers, resources, modules: the Terraform trinity →