A complete, hands-on walkthrough of Terraform on AWS, organized as a series of small, focused folders. Each folder isolates one concept, ships with a working example, and includes a dedicated explanation.md that breaks the concept down from first principles.
If you want to learn Terraform end-to-end — from a single hardcoded EC2 instance all the way to remote state, dynamic blocks, and importing existing infrastructure — this repository is designed to be a one-stop resource. Read the folders in order, run the code, break it, fix it, and graduate with the skills used in real production codebases.
- Concept-per-folder. No giant monolithic example. Each folder teaches exactly one idea so you can study it in isolation.
- Working code first. Every folder is a runnable Terraform project (
init,plan,apply,destroy). - Detailed explanations. Each folder contains an
explanation.mdcovering theory, syntax, gotchas, and "when to use vs. avoid." - Realistic AWS examples. EC2, Security Groups, Route53, AMIs, VPC lookups, S3 + DynamoDB backends — the building blocks of real infrastructure.
- Progressive difficulty. The first folder is a single resource; the last folders cover state security, importing, and remote backends.
| Requirement | Notes |
|---|---|
| Terraform CLI | Version 1.5+ recommended. Install from terraform.io/downloads. |
| AWS account | A free-tier account works for almost every example. |
| AWS credentials | Configured via aws configure, environment variables, or an IAM role. Never hardcode credentials in .tf files. |
| AWS CLI | Optional but useful for verification (aws ec2 describe-instances, etc.). |
| An editor with HCL support | VS Code + the official HashiCorp Terraform extension is recommended. |
-
Clone the repository.
-
Pick a folder (start with
ec2/if you are new to Terraform). -
Read its
explanation.mdfirst — it explains what the folder teaches. -
Run the standard workflow inside that folder:
cd <folder-name> terraform init terraform plan terraform apply terraform destroy
-
Move to the next folder once you are comfortable.
Always run
terraform destroyafter experimenting, so you do not incur AWS charges.
The folders build on each other. This is the suggested order:
ec2 → variables → conditions → count → data-source → locals
↓
dynamic-blocks
↓
provisioners
↓
remote-state → state-secure → import
The starting point. Provisions a single EC2 instance and a Security Group with hardcoded values.
- The
terraform,provider, andresourceblocks - The core CLI workflow:
init,fmt,validate,plan,apply,destroy - Implicit dependencies between resources
- The role of the state file (
terraform.tfstate)
Replaces every hardcoded value from ec2/ with input variables.
- The
variableblock:type,default,description - Variable types:
string,number,bool,list,map,object - The five ways to set a variable, in precedence order: CLI,
.tfvars, environment variables, defaults, interactive prompt - Why parameterization is the foundation of reusable infrastructure code
Introduces conditional expressions to make configuration adapt per environment.
- The ternary operator:
condition ? value_if_true : value_if_false - Comparison and logical operators (
==,!=,&&,||) - Conditional resource creation using
count = condition ? 1 : 0 - When to graduate from ternaries to
lookup()andlocalmaps
Provisions three EC2 instances (mysql, backend, frontend) and matching Route53 records using a single resource block.
- The
countmeta-argument andcount.index - Built-in functions:
length(),merge() - String interpolation (
"${...}") and list indexing - Tag composition with
merge()for shared vs. per-resource tags countvs.for_each: when to use which, and the reordering pitfall
Looks up the latest AMI and the default VPC at plan time, instead of hardcoding values.
- The
datablock and how it differs fromresource - Filters and
most_recentfor AMI lookups - The
outputblock andterraform output - Common data sources:
aws_ami,aws_vpc,aws_subnets,aws_caller_identity,aws_region,terraform_remote_state - Why dynamic lookups beat hardcoded IDs in real projects
Combines variables, data sources, and resources, then uses locals to glue them together.
- The
localsblock and thelocal.prefix - The difference between
var.,data., andlocal. - Naming conventions like
<project>-<environment>-<component> - Eliminating duplication and documenting intent
- When to use a
localinstead of avariable
Builds a Security Group whose ingress rules are generated from a list of port objects.
- The
dynamicblock, thefor_eachargument, and thecontenttemplate - The
iterator,.key, and.valueaccessors - When dynamic blocks improve readability and when they hurt it
- Difference between
for_eachon a resource (multiple resources) anddynamicinside a resource (multiple nested blocks) - Realistic patterns for Security Groups and IAM policies
Installs NGINX on a freshly provisioned EC2 instance using local-exec and remote-exec.
- The three provisioner types:
local-exec,remote-exec,file - The
connectionblock (SSH / WinRM) when = destroy— graceful shutdown hookson_failureand thenull_resource+triggerspattern- Why HashiCorp recommends provisioners as a last resort, and what to use instead (
user_data, Packer, Ansible, cloud-init)
Configures an S3 + DynamoDB backend so state is shareable, lockable, and durable.
- The
backend "s3"block:bucket,key,region,dynamodb_table - Why local state is unsafe for teams
- How DynamoDB-based state locking works
- Bootstrapping the bucket and lock table (chicken-and-egg problem)
- Migrating from local state to a remote backend
- Sharing outputs across stacks via the
terraform_remote_statedata source
A small variant of remote-state/ focused on security best practices for the state file.
- Why the state file is sensitive (it can contain plaintext secrets)
- Encryption at rest in S3 and KMS-managed keys
- Bucket policies, public-access blocks, and least-privilege IAM
- Versioning for recovery from accidental destroys
- Separating the bootstrap stack (bucket + table) from the consumer stacks
Demonstrates how to manage a resource that was originally created outside Terraform (manually in the AWS console, by another tool, etc.).
- Writing a minimal
resourceblock that matches the existing infrastructure - Using the
terraform importcommand (or theimportblock in Terraform 1.5+) to link real-world resources into state - Reconciling drift between the live resource and the configuration
- A required step when adopting Terraform in an existing environment
These commands apply to every folder in this repository.
| Command | What it does |
|---|---|
terraform init |
Downloads provider plugins; sets up the backend. |
terraform fmt |
Formats .tf files to canonical style. |
terraform validate |
Static syntax and config validation. |
terraform plan |
Shows what changes will be made; never modifies infra. |
terraform apply |
Applies the plan; creates / updates / deletes resources. |
terraform destroy |
Tears down everything Terraform manages in the working directory. |
terraform output |
Prints output values from the last apply. |
terraform state list |
Lists every resource currently tracked in state. |
terraform import <addr> <id> |
Brings an existing resource under Terraform management. |
- Each folder is self-contained — its own
provider.tf,.tffiles, and state. - AWS region is
us-east-1throughout, for consistency. - The
.gitignoreexcludes.terraform/,*.tfstate,*.tfstate.backup, and.terraform.lock.hclto keep the repository clean. - All examples use the AWS provider (
hashicorp/aws). - Resources are intentionally minimal so the concept is the focus, not the AWS service.
- AWS charges apply. Most examples stay within the free tier, but always run
terraform destroywhen finished. - Some folders reference specific AMIs, Route53 zones, and S3 buckets that exist only in the original author's account. Update those values to ones you own before applying.
- Never commit credentials. AWS access keys belong in
~/.aws/credentials, not in.tffiles or.tfvars. - Do not commit state files to source control. The
.gitignorealready prevents this; do not override it.
Once you have worked through every folder, you are ready for:
- Modules — packaging reusable Terraform components.
- Workspaces — managing multiple environments from the same code.
- Terraform Cloud / Enterprise — managed runs, policy as code, team workflows.
- Policy as code with Sentinel or OPA.
- Multi-cloud and multi-provider designs.
This repository is structured to be the single best place to learn Terraform on AWS in a guided, progressive way. Each folder is small enough to read in one sitting, deep enough to be production-relevant, and connected to the next folder so your understanding compounds as you work through the material.
Start with ec2/. Finish with import/. Build everything in between. By the end, you will have written, applied, destroyed, and explained every core feature of Terraform that production teams actually use.