We are still cooking the magic in the way!
HCL Syntax & First Resources
HCL Syntax & First Resources
Terraform is configured entirely in HashiCorp Configuration Language (HCL) — a declarative, human-readable language purpose-built for infrastructure. Before you can write meaningful infrastructure code, you need to internalize HCL's three core constructs: blocks, arguments, and expressions. Once those click, you will also need to master the four commands that constitute Terraform's core workflow: init, plan, apply, and destroy. Every Terraform action you ever take in production is a variation on these fundamentals.
Blocks, Arguments, and Expressions
Everything in an HCL file is a block. A block has a type, zero or more labels, and a body enclosed in braces. Inside the body, you assign values to named arguments. Those values can be literal strings, numbers, booleans, lists, maps, or full expressions — references to other values, function calls, or conditional logic.
Key block types you will use daily: terraform (version constraints and backend config), provider (configure a cloud API), resource (declare a real infrastructure object), data (read existing infrastructure), variable (accept inputs), output (expose values), locals (intermediate computations), and module (call reusable modules).
Expressions in Depth
HCL expressions are more powerful than they look. You can reference any resource attribute, call built-in functions, use conditionals, and iterate with for expressions — all inside an argument value.
type.name.attribute. A resource reference like aws_s3_bucket.app_artifacts.arn tells Terraform to look up the arn attribute of the aws_s3_bucket resource named app_artifacts. This reference also encodes an implicit dependency: Terraform will not create anything that references this bucket until the bucket itself exists. Understanding this is the foundation of correct resource ordering.
The Core Workflow: init, plan, apply, destroy
Terraform operations are always deterministic and predictable when you follow the four-step workflow. This is not a suggestion — in production, you must never run apply without first reviewing a plan.
plan output before apply; never skip this step in production.Here is a complete walkthrough. Create a directory, write your first resource, and run all four commands:
What init Actually Does
terraform init performs three tasks: it downloads provider plugins into .terraform/providers/, initializes the configured backend (local by default — just a terraform.tfstate file), and installs any module sources. You must re-run init any time you change provider versions, add a new provider, or change the backend configuration. The .terraform.lock.hcl file it generates pins exact provider checksums — commit this file to Git so every team member and every CI run uses identical provider versions.
terraform apply -auto-approve against a freshly computed plan. The correct pattern is: terraform plan -out=tfplan in the plan stage, store the tfplan artifact, then terraform apply tfplan in the apply stage (gated by a manual approval step). This guarantees that what you reviewed in the plan is exactly what gets applied — no race conditions if infrastructure changed between stages.
Reading Plan Output
The plan output is the most important output in all of Terraform. Every senior engineer reads it word for word before approving an apply. The summary line tells you the action counts (Plan: 3 to add, 1 to change, 0 to destroy), but the detail block tells you what changes on each attribute. A ~ (update) on a resource attribute is safe; a -/+ (replacement) means the resource will be destroyed and recreated, often causing downtime if you are not careful.
-/+ resource "aws_db_instance" "main" (forces replacement), Terraform will delete the database and create a new one. The old data is gone unless you have a snapshot. Several changes force replacement on critical resources: renaming an RDS instance, changing the engine version of an ElastiCache cluster, modifying the subnet_ids of an EKS cluster. Before applying any plan that contains a replacement, verify you have a backup and a tested restore procedure. In production, use lifecycle { prevent_destroy = true } on stateful resources to make Terraform error rather than silently replace them.
File Organisation Conventions
Terraform loads all *.tf files in a directory as a single configuration. The community convention — and what you will see in every open-source module — is to split concerns across named files: main.tf for resources, variables.tf for input declarations, outputs.tf for output declarations, providers.tf for provider and terraform blocks, and locals.tf for local value blocks. This is not enforced by the tool, but violating the convention will make your code unreadable to every other Terraform practitioner on your team.
In the next lesson you will dive into providers and versioning — how the registry works, how to pin and upgrade providers safely, and how to configure multiple provider instances (multiple AWS regions, multiple accounts) in a single root module.