> The goal here is NOT to memorize every command or every code block. Syntax changes, flags get updated, and the official Terraform docs are always one search away. What you actually need to walk away with is **how Terraform thinks** — the write → plan → apply flow, what state is and why it matters, how providers work, why modules exist, and how teams use it safely in production. Once that mental model clicks, you can look up any specific syntax in seconds. Read these notes to understand the concepts and the workflow. Use the official docs at [developer.hashicorp.com/terraform](https://developer.hashicorp.com/terraform) whenever you need the exact syntax for something specific. -
Before Terraform existed, setting up a server meant logging into the AWS console, clicking through menus, filling in forms, and hoping you remember every setting next time. That process was slow, error-prone, and impossible to repeat exactly. If something broke, you had no record of what you built. Terraform changes this completely. Instead of clicking buttons, you write a simple text file describing what infrastructure you want. Terraform reads that file and creates everything automatically. Want 5 servers, a database, and a load balancer? Write it once, run one command, done. Want to recreate the exact same setup in a different region or for a different client? Run the same file again. This approach is called Infrastructure as Code, or IaC. It means your infrastructure is treated just like your application code — it lives in a file, you can track every change with Git, your whole team can review it, and you can reproduce it exactly any number of times. Why does every DevOps team use Terraform? Because manual infrastructure doesn't scale. When you have dozens of servers, databases, and networks to manage, clicking through consoles becomes a full-time job with constant human errors. Terraform turns that into a few files that machines manage reliably.  ### Infrastructure as Code — The Core Idea ![Add image here: side-by-side comparison — left shows person clicking through AWS console manually, right shows a simple text file with code replacing all those clicks]() Think of it like this. A chef can cook a meal from memory, but they can't guarantee the dish will be identical every time. A chef working from a written recipe produces the same dish reliably, every cook, every kitchen. Terraform is the recipe for your infrastructure. With IaC you get: | Benefit | What It Means in Practice | |---|---| | **Repeatability** | Run the same config in dev, staging, and prod — identical every time | | **Version control** | Track every infra change in Git just like code | | **Collaboration** | Team reviews infrastructure changes in pull requests | | **Speed** | Spin up a whole environment in minutes, not days | | **Recovery** | Infra destroyed? Just run apply again from the same file | ### Declarative vs Imperative — What's the Difference This is an important concept. There are two ways to tell a system what to do. **Imperative** means you write step-by-step instructions: "First create a VPC, then create a subnet inside it, then create an EC2 instance inside that subnet." You are telling it HOW to do things, in order. **Declarative** means you describe the end result you want: "I want a VPC with a subnet and an EC2 instance inside it." You tell it WHAT you want, and the tool figures out the HOW. Terraform is declarative. You describe the desired state, and Terraform calculates the exact steps needed to get there. This is much cleaner because you don't have to think about order or dependencies — Terraform handles all of that automatically. ### Terraform vs Other IaC Tools | Tool | Works With | Language | State Management | |---|---|---|---| | **Terraform** | Any cloud (AWS, Azure, GCP, +1000 more) | HCL — clean and readable | You manage it (local or remote) | | **AWS CloudFormation** | AWS only | JSON or YAML — very verbose | AWS manages it automatically | | **Ansible** | Any system | YAML | No state — stateless | | **Pulumi** | Any cloud | Python, Go, TypeScript | Pulumi Cloud manages it | Terraform wins for multi-cloud and flexibility. CloudFormation wins if you are AWS-only and want zero state management hassle. Ansible is better for configuring software on existing servers, not creating the servers themselves. -
Understanding how Terraform thinks is the key to using it confidently. Many beginners jump straight into writing code without understanding the architecture, and then get confused when things don't work as expected. Terraform has three layers working together: the core engine on your machine, providers that talk to cloud APIs, and a state file that tracks what exists in the real world. Once you understand these three pieces and how they interact, everything else makes sense. The entire Terraform workflow is built around one simple idea: compare what EXISTS (current state) with what you WANT (your config files), then make exactly the changes needed to close that gap. Nothing more, nothing less.  ### The Three Core Components **The Core (the Terraform binary)** is the program you install on your laptop. It reads your `.tf` configuration files, reads the current state from the state file, compares them, and builds a plan of what needs to change. It never talks to AWS or Azure directly. **Providers** are plugins that translate Terraform code into actual API calls for a specific platform. The AWS provider knows how to talk to AWS. The Azure provider knows how to talk to Azure. Terraform downloads the right providers when you run `terraform init`. There are over 1,000 providers — AWS, Azure, GCP, Kubernetes, GitHub, Datadog, Cloudflare, and more. **The State File** (`terraform.tfstate`) is Terraform's memory. It keeps a record of every resource Terraform has ever created — the resource ID, its current settings, and how it maps to your code. When you want to update or delete something, Terraform looks up the real resource ID from the state file. ### The Workflow — Write, Plan, Apply ![Add image here: three-step flow diagram — Write .tf file → terraform plan (shows preview) → terraform apply (infrastructure created), with arrows between each step]() Every Terraform workflow follows the same three steps: ``` Write your .tf files → terraform plan → terraform apply (describe what you want) (preview changes) (make it happen) ``` ```bash # Step 1: Initialize — downloads providers and sets up the workspace terraform init # Step 2: Plan — shows you exactly what will be created, changed, or destroyed # NOTHING happens to your real infrastructure at this step terraform plan # Step 3: Apply — executes the plan and builds the actual infrastructure terraform apply # Step 4: Destroy — tears everything down when you're done terraform destroy ``` The plan step is incredibly valuable. Before touching any real infrastructure, Terraform shows you a preview: "I will create 3 resources and modify 1." You can review this and catch mistakes before anything actually changes. This is one of the most important safety features in Terraform. ### HCL — The Language Terraform Uses Terraform uses HCL (HashiCorp Configuration Language). It is designed to be human-readable. If you can read English, you can understand HCL. ```hcl # This is a comment in HCL # A "resource" block creates a real piece of infrastructure resource "aws_instance" "my_server" { # resource type + your chosen name ami = "ami-0c55b159cbfafe1f0" # which machine image to use instance_type = "t2.micro" # how powerful the server is tags = { Name = "DevOps-Server" # label it in the AWS console } } ``` The syntax is: `resource "TYPE" "NAME" { settings }`. The type comes from the provider (like `aws_instance` from the AWS provider). The name is what YOU choose to identify this resource inside your Terraform code. -
Getting Terraform running on your machine takes about five minutes. It's a single binary — no complex installation, no dependencies to manage. The most important thing to understand before you start: Terraform needs credentials to talk to your cloud provider. For AWS, this means an Access Key ID and Secret Access Key. Terraform uses the same credentials as the AWS CLI, so if you've already set up the AWS CLI, Terraform will automatically pick up those credentials.  ### Installing Terraform ```bash # On macOS (using Homebrew) brew tap hashicorp/tap brew install hashicorp/tap/terraform # On Ubuntu/Debian Linux wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list sudo apt update && sudo apt install terraform # On Windows (using Chocolatey) choco install terraform # Verify it installed correctly terraform -version # Should print: Terraform v1.x.x ``` ### Setting Up AWS Credentials ```bash # Option 1: Environment variables (recommended for local dev) export AWS_ACCESS_KEY_ID="your-access-key-here" export AWS_SECRET_ACCESS_KEY="your-secret-key-here" export AWS_DEFAULT_REGION="us-east-1" # Option 2: AWS CLI config (if you have AWS CLI installed) aws configure # It will ask for: Access Key, Secret Key, Region, Output format # Verify your credentials work aws sts get-caller-identity # Should return your AWS account info ``` Never put real AWS credentials directly inside your `.tf` files. They belong in environment variables or AWS config — never in code that gets committed to Git. ### Your First Terraform Project — File Structure Terraform automatically loads all `.tf` files in the current directory. You can organize them however you want, but there is a conventional structure that most teams follow. ``` my-first-terraform/ terraform.tf ← Terraform settings and provider versions main.tf ← Your main infrastructure resources variables.tf ← Input variable definitions outputs.tf ← Output value definitions terraform.tfvars ← Actual values for your variables (don't commit secrets!) ``` ### terraform.tf — The Configuration Block ```hcl # terraform.tf # This file tells Terraform which version of itself and which providers to use terraform { required_version = ">= 1.2" # Minimum Terraform version required required_providers { aws = { source = "hashicorp/aws" # Where to download the AWS provider from version = "~> 5.0" # Allow 5.x versions but not 6.x # ~> means "pessimistic constraint" — allow minor updates but not major } } } provider "aws" { region = "us-east-1" # All resources go in this region by default } ``` Always pin your provider versions. If you leave this out, Terraform uses the latest version — and new major versions can introduce breaking changes that silently break your infrastructure. -
These are the three building blocks you will use in literally every Terraform configuration. Resources create things. Data sources read existing things. Variables make your config flexible and reusable. Understanding the difference between a resource and a data source confuses many beginners. Think of it this way: a resource is something Terraform owns and manages (it created it, it can modify or delete it). A data source is something Terraform reads but does not own — it just looks up existing information to use in other resources. ### Resources — Creating Infrastructure A resource block tells Terraform to create a real piece of infrastructure and keep it in the state file. ```hcl # Create an EC2 instance (a virtual server on AWS) resource "aws_instance" "web_server" { ami = "ami-0c55b159cbfafe1f0" # Ubuntu machine image ID instance_type = "t2.micro" # Free-tier eligible server size tags = { Name = "my-web-server" Environment = "production" ManagedBy = "Terraform" # Always tag so you know Terraform made this } } # Create an S3 bucket (cloud storage) resource "aws_s3_bucket" "app_storage" { bucket = "my-app-storage-2025" # Bucket names must be globally unique on AWS tags = { Name = "App Storage" } } # Reference one resource from another resource "aws_s3_bucket_versioning" "app_storage" { bucket = aws_s3_bucket.app_storage.id # aws_s3_bucket.NAME.ATTRIBUTE versioning_configuration { status = "Enabled" } } ``` The reference syntax `aws_s3_bucket.app_storage.id` is how you connect resources together. Terraform automatically detects this dependency and creates the bucket before the versioning config. ### Data Sources — Reading Existing Resources A data source block reads information from your cloud provider without creating anything new. Common use: look up the latest Ubuntu AMI ID so you don't have to hardcode it. ```hcl # Read the latest Ubuntu AMI — no hardcoded AMI ID needed data "aws_ami" "ubuntu" { most_recent = true # Get the newest matching image filter { name = "name" values = ["ubuntu/images/hvm-ssd/ubuntu-*-22.04-amd64-server-*"] } owners = ["099720109477"] # Canonical (official Ubuntu publisher) } # Now use the data source in a resource resource "aws_instance" "web_server" { ami = data.aws_ami.ubuntu.id # data.TYPE.NAME.ATTRIBUTE instance_type = "t2.micro" } ``` ### Variables — Making Config Reusable Without variables, you would have to hardcode values like region, instance type, and environment name. Variables let you write one config that works everywhere. ```hcl # variables.tf — define what inputs the config accepts variable "environment" { description = "Which environment: dev, staging, or prod" type = string default = "dev" # Used if no value is provided validation { # Optional: reject invalid inputs condition = contains(["dev", "staging", "prod"], var.environment) error_message = "Must be dev, staging, or prod." } } variable "instance_type" { description = "EC2 instance size" type = string default = "t2.micro" } variable "instance_count" { description = "How many servers to create" type = number default = 1 } variable "allowed_ips" { description = "IPs allowed to access the server" type = list(string) default = ["0.0.0.0/0"] } ``` ```hcl # main.tf — use variables with var.NAME syntax resource "aws_instance" "web" { ami = data.aws_ami.ubuntu.id instance_type = var.instance_type # use var.VARIABLE_NAME tags = { Name = "server-${var.environment}" # string interpolation Environment = var.environment } } ``` ### Setting Variable Values ```bash # Method 1: Command line flag terraform apply -var="environment=prod" -var="instance_type=t3.large" # Method 2: terraform.tfvars file (auto-loaded) # Create terraform.tfvars: environment = "prod" instance_type = "t3.large" instance_count = 3 # Method 3: Environment variables (useful in CI/CD) export TF_VAR_environment="prod" export TF_VAR_instance_type="t3.large" terraform apply # Method 4: Named .tfvars file (pass with -var-file) terraform apply -var-file="prod.tfvars" ``` Variable precedence order (highest wins): CLI `-var` flag → `.tfvars` file → `*.auto.tfvars` → `terraform.tfvars` → environment variables → default value. ### Outputs — Exposing Useful Information After Terraform creates resources, outputs let you print important values like the server's IP address or the database endpoint. ```hcl # outputs.tf output "server_ip" { description = "Public IP address of the web server" value = aws_instance.web.public_ip # grabs the IP after creation } output "server_dns" { description = "Public DNS name of the web server" value = aws_instance.web.public_dns } output "bucket_name" { description = "Name of the S3 bucket" value = aws_s3_bucket.app_storage.bucket } output "db_password" { description = "Database password" value = var.db_password sensitive = true # Hides value in terminal output — shows as <sensitive> } ``` ```bash # See output values after apply terraform output # See a specific output terraform output server_ip # Get all outputs as JSON (useful in scripts) terraform output -json ``` -
State is what makes Terraform fundamentally different from a simple script. Understanding state is the difference between using Terraform confidently and being confused why things aren't working. Every time Terraform creates a resource, it writes the details into a file called `terraform.tfstate`. This file is Terraform's memory. The next time you run `terraform plan`, it reads this file to know what already exists, compares it to your current `.tf` files, and shows only the changes needed. Without state, Terraform would have no way to know that the server it created last week is the same server referenced in your current config. It would try to create a new one every time. State is what gives Terraform the ability to update and delete — not just create.  ### Local State vs Remote State ![Add image here: diagram showing two developers on laptops both connected to a shared S3 bucket in the middle labeled "Remote State", with a DynamoDB lock icon showing only one can write at a time]() By default, Terraform stores state in a `terraform.tfstate` file in your project directory. This works fine when you are working alone, but it breaks down the moment you work with a team. **Problems with local state:** - Only you have the file — your teammate can't run Terraform - If you delete the file, Terraform loses track of everything it created - Two people running Terraform at the same time = corrupt state **Remote state** stores the state file in a shared location everyone can access, with locking to prevent simultaneous writes. ```hcl # backend.tf — configure S3 as the remote state backend terraform { backend "s3" { bucket = "my-terraform-state-bucket" # S3 bucket to store state key = "prod/terraform.tfstate" # path inside the bucket region = "us-east-1" encrypt = true # encrypt the state file at rest dynamodb_table = "terraform-state-lock" # DynamoDB table for locking # Locking prevents two people from running apply at the same time } } ``` After adding a backend config, run `terraform init` to migrate your local state to the remote backend. ### State Commands — Working With State ```bash # List all resources Terraform knows about terraform state list # Show full details of a specific resource terraform state show aws_instance.web_server # Inspect the entire state file in a readable format terraform show # Remove a resource from state WITHOUT destroying the real resource # Use this when you want to "forget" a resource without deleting it terraform state rm aws_instance.web_server # Move a resource to a new name in state (rename without recreating) terraform state mv aws_instance.old_name aws_instance.new_name # Download the remote state to inspect locally terraform state pull > state-backup.json ``` **Critical rules about state:** - Never edit the `.tfstate` file by hand — always use `terraform state` commands - Never commit a state file containing secrets to Git - Always use remote state with locking when working in a team ### Workspaces — Multiple Environments Workspaces let you use the same configuration for multiple environments (dev, staging, prod) each with their own separate state. ```bash # Create a new workspace terraform workspace new dev terraform workspace new staging terraform workspace new prod # Switch to a workspace terraform workspace select prod # See which workspace you're currently in terraform workspace show # List all workspaces terraform workspace list ``` ```hcl # Reference the current workspace in your config resource "aws_instance" "web" { # Use a bigger server in prod, smaller in dev instance_type = terraform.workspace == "prod" ? "t3.large" : "t2.micro" tags = { Environment = terraform.workspace # auto-set to current workspace name } } ``` -
> The goal here is NOT to memorize every command or every code block. Syntax changes, flags get updated, and the officia...
Before Terraform existed, setting up a server meant logging into the AWS console, clicking through menus, filling in for...
Understanding how Terraform thinks is the key to using it confidently. Many beginners jump straight into writing code wi...
Getting Terraform running on your machine takes about five minutes. It's a single binary — no complex installation, no d...
These are the three building blocks you will use in literally every Terraform configuration. Resources create things. Da...
State is what makes Terraform fundamentally different from a simple script. Understanding state is the difference betwee...
As your infrastructure grows, you will notice yourself writing the same patterns over and over. Every time you set up a ...
Every Terraform operation happens through the CLI. Knowing these commands well — what they do, when to use them, and wha...
HCL is not just a static config language — it has logic built in. You can loop over lists, use conditional expressions, ...
The lifecycle block gives you fine-grained control over how Terraform handles specific resources. This is essential in p...
Running Terraform locally is fine when you are learning, but in real teams, everyone needs to be able to run infrastruct...
Learning Terraform is one thing. Using it safely in production is another. These are the habits that separate engineers ...
Three concepts you will encounter quickly once you move beyond solo Terraform work. You do not need to master these on d...
Core Commands Command What It Does terraform init Initialize workspace, download providers and modules terraform validat...
Aligns directly with DevOps, Site Reliability (SRE), and Platform Engineering job descriptions.