Terraform Fundamentals for AWS
CloudFormation taught TechLearningPro to declare AWS resources as stacks. Terraform teaches a different contract: you describe the desired graph of resources in HashiCorp Configura
Terraform Fundamentals for AWS
AWS Cloud & DevOps Engineering · Lesson 30 · DevOps Automation and IaC
Lesson Overview
CloudFormation taught TechLearningPro to declare AWS resources as stacks. Terraform teaches a different contract: you describe the desired graph of resources in HashiCorp Configuration Language (HCL), a provider translates that graph into AWS API calls, and a plan shows the diff before anything mutates. When the progress API VPC, the enrollment Lambda role, and the content-worker security group live in Git as Terraform, Monday’s “who changed the NAT?” question becomes a pull request—not a console archaeology session.
Infrastructure as Code with CloudFormation remains valid and AWS-native. This lesson does not replace it; it adds the industry-default multi-cloud language most hiring loops and partner shops expect. You will learn providers, resources, data sources, variables, outputs, dependency edges, and the plan/apply loop for AWS—without inventing flags or pretending prices are fixed. Terraform State, Modules and Workspaces deepens remote state and module versioning next; here you earn the fundamentals so that later depth is not cargo cult.
TechLearningPro’s teaching shape stays fixed: tag lab resources Project=tlp-lesson30, set a budget alert before apply, prefer least-privilege IAM for the identity running Terraform, never hardcode secrets in .tf files, and destroy or otherwise clean up when the lab ends.
Learning Objectives
By the end of this lesson you will be able to:
- Explain why Terraform exists beside CloudFormation for TechLearningPro AWS delivery and when each is the better fit.
- Describe the provider model: how the AWS provider authenticates, which region it targets, and why provider version constraints matter.
- Distinguish resources (managed) from data sources (read-only lookups) and choose correctly for VPCs, AMIs, and account identity.
- Write variables and outputs that make a root module reusable without leaking secrets into state casually.
- Trace implicit and explicit dependencies so Terraform creates security groups before instances that reference them.
- Run
init,plan, andapplyas a deliberate review loop—not as muscle-memory deploy. - Interpret a plan: create, update in place, replace (destroy/create), and destroy—and know when replace is dangerous.
- Structure a small AWS root module: VPC-related networking pieces and a compute or serverless stub suitable for later modules.
- Use
terraform fmtandvalidateas local hygiene before CI. - Map Terraform changes to IAM permissions the runner needs (and why
AdministratorAccessis a lab shortcut, not a production habit). - Avoid storing access keys in code; prefer environment credentials, SSO/OIDC in CI, or instance/role credentials as documented by HashiCorp and AWS.
- Budget, tag, and clean up sandbox resources created by apply; preview remote state and locking for lesson 31.
- Compare Terraform’s graph model to CloudFormation change sets at a conceptual level for interview clarity.
- Recognize provider drift awareness: reality can change outside Terraform and plans will surprise you until state discipline arrives in lesson 31.
- Connect this lesson to CI/CD Pipeline Fundamentals and GitHub Actions for AWS Deployments: plan in PR, apply on protected branches.
Prerequisites
- Infrastructure as Code with CloudFormation: templates, parameters, change sets, stack lifecycle—so you can compare honestly.
- Amazon VPC Architecture and Security Groups, NACLs and VPC Connectivity: what you will express in HCL.
- AWS Identity and Access Management: roles, policies, and least privilege for the Terraform runner.
- AWS CLI, SDKs and Infrastructure Access: credential chain concepts Terraform’s AWS provider also uses.
- DevOps Principles and AWS Delivery Architecture and Git Workflows and Repository Governance: IaC lives in reviewed Git, not on laptops forever.
- A sandbox AWS account with a budget alert configured before the first billable
apply. - Terraform CLI installed from HashiCorp’s current documented distribution method for your OS; pin a version consciously for the lab.
- Willingness to read official provider docs for resource arguments rather than inventing attribute names from memory.
Detailed Core Concepts
Why Terraform after CloudFormation
CloudFormation is AWS’s first-party orchestration: stacks, change sets, drift detection, StackSets for multi-account patterns. It maps cleanly to AWS Organizations stories later in the course.
Terraform is a declarative engine with a provider plugin ecosystem. The AWS provider is maintained to track AWS APIs; the same workflow language can also talk to Kubernetes, Datadog, GitHub, and other systems TechLearningPro may adopt. Hiring markets, open modules, and multi-tool shops make Terraform literacy non-optional for many DevOps roles.
TechLearningPro rule: pick one primary IaC tool per platform boundary and document it in an ADR. Mixing CloudFormation for networking and Terraform for “everything else” without boundaries creates dual sources of truth. Dual tooling can be intentional (for example CDK/CloudFormation for an app team, Terraform for the landing zone)—but accidental dual tooling is how NAT gateways get deleted twice.
The core objects
Provider. Declares which plugin and version implement resource types. For AWS you typically configure region and rely on the standard credential chain (environment variables, shared config/SSO, or role assumption as documented). Constrain versions with a required_providers block so CI and laptops do not silently upgrade into breaking schema changes.
Resource. Something Terraform creates and manages—aws_vpc, aws_subnet, aws_security_group, aws_instance, aws_iam_role, and so on. Each has a type and a local name (aws_vpc.main). Arguments map to AWS properties; lifecycle and provisioners exist but provisioners are a last resort—prefer cloud-init, SSM, or immutable images from earlier lessons.
Data source. Read-only query—latest Amazon Linux AMI ID, caller identity, existing VPC by tag. Data sources do not “own” the object; they look it up. Use them to avoid hardcoding AMI IDs and account numbers.
Variable. Input to a module/root: environment, vpc_cidr, project_tag. Mark sensitive variables appropriately; still remember values can end up in state and logs—treat secrets with Secrets Manager and Systems Manager Parameter Store, not default = "hunter2" in Git.
Output. Values exported after apply: VPC ID, subnet IDs, role ARNs for other stacks or humans. Outputs are how root modules talk to the next lesson’s remote state consumers.
Module (preview). A folder of Terraform intended for reuse. You will call small modules in the lab; versioning, registries, and composition depth belong primarily to lesson 31. Fundamentals still require you to understand module blocks as namespaced resource groups.
Plan and apply as a social contract
terraform init downloads providers and modules, sets up the working directory.
terraform plan computes the desired graph versus state (local by default until lesson 31) and prints actions. Review the plan. Creates of NAT gateways and Application Load Balancers have cost and blast radius. Replaces of databases are outages unless designed for them.
terraform apply executes the approved plan (or prompts). Prefer saving a plan file in CI so apply cannot silently recompute a different plan—exact flags and workflows evolve; follow current HashiCorp docs for -out plan artifacts and your pipeline pattern from lessons 26–28.
terraform destroy tears down managed resources. Labs must destroy. Production uses destroy rarely and with change control.
Dependency graph
Terraform builds a DAG. If an instance’s subnet_id references aws_subnet.private.id, the subnet is created first—implicit dependency. depends_on adds explicit edges when references are not enough (for example IAM eventual consistency edge cases). Prefer references over ritual depends_on; overusing explicit depends_on hides real data flow.
Count, for_each, and lifecycle (awareness)
count and for_each create multiple similar resources (three private subnets across AZs). Prefer for_each with stable keys (AZ names) over count indexes that shift when you remove item zero. lifecycle blocks can ignore tag churn or create_before_destroy for zero-downtime replacements—use deliberately; ignoring changes can hide drift.
State (honest preview)
Even in this fundamentals lesson, state exists: a JSON mapping of resources to real IDs. Local terraform.tfstate on a laptop is fine for a disposable sandbox and dangerous for a team. Lesson 31 moves state to S3 with locking concepts. Until then: do not commit state files with secrets; add them to .gitignore; do not share a laptop state file as “the environment.”
Authentication without hardcoded keys
Never put long-lived access keys in .tf or terraform.tfvars committed to Git. Patterns that match earlier course lessons:
- Local: AWS IAM Identity Center / SSO profiles or short-lived keys in the environment—not in the repo.
- CI: OIDC federation to assume a deploy role (GitHub Actions for AWS Deployments).
- Break-glass roles with MFA and short sessions for humans.
The AWS provider documents the credential chain; verify against current provider docs rather than memorizing env var names from blog posts.
Cost and cleanup without fake prices
NAT gateways, idle ALBs, and always-on EC2 dominate lab bills. Tag everything with Project=tlp-lesson30. Prefer one AZ for throwaway labs when multi-AZ is not the learning goal. Run terraform destroy when finished; confirm in the console that orphan resources (failed destroys, manually created peers) are gone. Budget alerts exist so “forgot destroy” is an email, not a month-end surprise.
Architecture and Design Diagrams
Terraform control plane for TechLearningPro sandbox:
mermaidflowchart LRDev[Engineer / CI runner]HCL[HCL config in Git]TF[Terraform CLI]Prov[AWS provider plugin]Plan[Plan diff]State[(State file)]AWS[AWS APIs]Res[VPC / SG / Compute]Dev --> HCLHCL --> TFTF --> ProvTF --> StateProv --> PlanDev -->|review| PlanPlan -->|apply| ProvProv --> AWSAWS --> ResAWS --> State
Desired graph for a minimal progress-API lab network:
mermaidflowchart TBVPC[aws_vpc]IGW[aws_internet_gateway]Pub[aws_subnet public]Priv[aws_subnet private]RT[aws_route_table]SG[aws_security_group]EC2[aws_instance or launch template later]VPC --> IGWVPC --> PubVPC --> PrivVPC --> RTVPC --> SGPub --> EC2SG --> EC2
Technical Explanation
Anatomy of a root module
A typical small root for this lesson:
versions.tf—terraformrequired_version andrequired_providerswith AWS sourcehashicorp/awsand a pessimistic version constraint.providers.tf—provider "aws" { region = var.aws_region }.variables.tf— typed variables with descriptions; no secrets as defaults.main.tfor split files (network.tf,compute.tf) — resources.outputs.tf— IDs needed by humans or later modules.terraform.tfvars— non-secret values for the lab (gitignored if it ever holds sensitive data); prefer*.auto.tfvarspatterns your team documents.
Resources vs data sources in practice
Resource example (conceptual): create a VPC with a CIDR from a variable, enable DNS hostnames if your design needs them, tag with project and environment.
Data source example (conceptual): aws_ami filtered by owner and name for Amazon Linux; aws_caller_identity to stamp account ID into tags or names; aws_availability_zones to pick AZs without hardcoding—still respect that not every AZ supports every instance type.
Do not “data source” a VPC you should own in this root module. Data sources shine when another stack owns the object (lesson 31 remote state outputs) or when the value is dynamic (AMI).
Variables, locals, and outputs
Variables accept environment-specific CIDRs and instance sizes. Validate blocks (where supported by your Terraform version) catch bad CIDRs early.
Locals compose tags once: local.common_tags = { Project = "tlp-lesson30", ManagedBy = "terraform" } and merge onto resources so tagging stays consistent.
Outputs export vpc_id, public_subnet_ids, security_group_id. Mark outputs sensitive = true when they expose secrets—but prefer not to output secrets at all.
Plan reading skills
Train yourself to scan for:
- +/− destroy and create (replace)—subnet CIDR changes often force replaces of dependent ENIs/instances.
- ~ in-place updates—security group rule changes may be in-place; still review blast radius.
- Resource addresses you do not recognize—copy-paste modules sometimes drag unused resources into scope.
If the plan wants to destroy a production-looking name in a shared account, stop. Sandbox naming conventions (tlp-lesson30-*) make this check easy.
IAM for the runner
Terraform’s effective permissions are the IAM principal’s permissions. Creating VPCs needs ec2: networking actions; creating roles needs iam: permissions with care (pass role constraints). In labs, a broad power-user role may be acceptable in an isolated account. In production, split plan roles (read + limited) from apply roles, and scope by tags or paths where policies allow. Never bake Admin keys into GitHub secrets as a permanent design—use OIDC assume-role.
Modules at teaching depth
hcl# Conceptual — verify argument names against current AWS provider docsmodule "vpc_label" {source = "./modules/tags"project = var.projectenv = var.environment}
A local modules/ folder for tags or a tiny security-group wrapper teaches composition without a registry. Do not invent a 20-module “enterprise” layout in lesson 30; earn simplicity first.
fmt, validate, and providers lock
terraform fmt rewrites HCL to canonical style—run it in pre-commit or CI.
terraform validate checks configuration validity after init.
The dependency lock file (.terraform.lock.hcl) pins provider checksums—commit it so CI and laptops share the same plugins. That is supply-chain hygiene for IaC, analogous to lockfiles in application builds from Amazon ECR and Container Image Supply Chain thinking.
Performance of plans
Huge monoliths plan slowly and review poorly. Split by blast radius later (lesson 31 state boundaries). For now, keep the lab module small so plan output fits a PR comment.
Security
- No secrets in VCS.
- Restrict who can
applyto production. - Encrypt remote state when you introduce it (lesson 31).
- Avoid overly permissive security group CIDRs in examples; use your IP or SSM-only patterns from AWS Systems Manager and Fleet Operations.
- Prefer IMDS hop limits and hardened instance profiles when creating EC2 in labs—you already practiced AMI and ASG thinking in foundations.
Practical Implementation or Design Walkthrough
Problem
TechLearningPro needs a disposable sandbox network for a progress-API experiment: one VPC, public and private subnets in a single AZ for cost control, a security group that does not open SSH to the world, and an optional small EC2 or just networking outputs for later Ansible/SSM work.
Steps
- Confirm budget alert and sandbox account; create a working directory and Git repo branch
lab/lesson30. - Write
versions.tfandproviders.tfwith pinned AWS provider constraint and region variable. - Define variables:
aws_region,vpc_cidr,project,environment. - Create
aws_vpc, Internet Gateway, public subnet, private subnet, route tables and associations as required for your design. - Create
aws_security_groupallowing egress as needed; ingress only for lab HTTP from a known CIDR or none (SSM-only). - Optional: look up AMI via data source; create a
t3.micro(or current free-tier-class type if still applicable—verify) in the public or private subnet with an instance profile stub. terraform init,fmt,validate,plan—screenshot or save plan text as lab evidence.terraform applyafter review; verify resources in console with tags.- Change a tag; re-plan; observe in-place update.
terraform destroy; confirm cleanup; note any destroy failures for troubleshooting practice.
Failure paths to rehearse
- Apply with wrong region in provider vs console mental model—resources “missing” in the UI.
- Overlapping CIDR with an existing lab VPC—API error; fix variable.
- Insufficient IAM—AccessDenied on
ec2:CreateVpc; adjust role or use a proper lab role.
Stub configuration sketch
hcl# Conceptual sketch — confirm every argument in current hashicorp/aws docsterraform {required_version = ">= 1.5.0"required_providers {aws = {source = "hashicorp/aws"version = "~> 5.0"}}}provider "aws" {region = var.aws_region}variable "aws_region" {type = stringdescription = "AWS region for the TechLearningPro lesson 30 lab"}variable "vpc_cidr" {type = stringdescription = "CIDR for disposable lab VPC"}variable "project" {type = stringdefault = "tlp-lesson30"}locals {common_tags = {Project = var.projectManagedBy = "terraform"Environment = "sandbox"}}resource "aws_vpc" "lab" {cidr_block = var.vpc_cidrenable_dns_hostnames = truetags = merge(local.common_tags, { Name = "${var.project}-vpc" })}output "vpc_id" {value = aws_vpc.lab.iddescription = "Lab VPC ID for evidence and later modules"}
Extend with subnets and security groups in your full lab; keep comments explaining why each resource exists.
Enterprise Real-World Scenarios
Console snowflake VPC
A team builds networking in the console “just for a spike.” Six months later nobody can recreate staging. Symptom: audit asks for IaC; there is none. Fix: import carefully (advanced) or rebuild from Terraform in a new CIDR and cut over—prefer prevention via this lesson’s habits.
Provider version surprise
CI upgrades the AWS provider major version implicitly; plans propose hundreds of attribute renames. Symptom: unblockable PR noise. Fix: required_providers constraints and committed lock file; upgrade in a dedicated PR reading the provider changelog.
Secrets in tfvars committed
An engineer puts a database password in terraform.tfvars and pushes. Symptom: secret scanning alert or worse, silent exposure. Fix: rotate immediately; use Secrets Manager; scrub Git history per security process; add pre-commit secret scanning.
Plan not reviewed
Apply from a laptop on Friday night destroys a shared subnet because state pointed at the wrong directory. Symptom: outage. Fix: remote state and CI apply (lesson 31 + pipeline lessons); never share ad-hoc local state for shared envs.
CloudFormation and Terraform co-managing one VPC
Two tools fight over the same subnet tags and routes. Symptom: perpetual drift. Fix: ADR assigning ownership; one writer per resource set.
Counting subnets with count and deleting index 0
All subnet IDs shift; instances recreate. Symptom: surprise replaces. Fix: for_each with AZ keys.
Best Practices
- Pin Terraform and provider versions; commit the lock file.
- Review every plan; prefer saved plans in CI.
- Tag consistently for cost and cleanup.
- Prefer references over unnecessary
depends_on. - Use data sources for AMIs and lookups—not for resources you should manage.
- Keep root modules small until state boundaries are designed.
- Never commit secrets or state files with sensitive data.
- Align IAM for plan/apply with least privilege over time.
- Run fmt/validate locally and in CI.
- Destroy lab resources; budget alert is mandatory.
- Document module purpose in README briefly.
- Name resources with environment and project prefixes.
- Treat replace actions as potential incidents until proven safe.
- Prefer SSM Session Manager patterns over open SSH in lab SGs.
- Hand off to lesson 31 before inviting a second engineer to apply the same root.
Common Mistakes and Anti-Patterns
- Hardcoded AMI IDs — Break across regions; use data sources or SSM public parameters as documented.
- Open SSH 0.0.0.0/0 — Lab convenience that becomes production copy-paste; use SSM.
- AdminAccess forever — Fast labs, slow security reviews; narrow permissions.
- Ignoring plan replace lines — Database or instance rebuilds without change tickets.
- Monolithic 5,000-line root — Unreviewable; split later with state design.
- Committing
.terraform/or state — Bloat and secret risk. - Using provisioners for config management — Prefer images, user data, Ansible (lesson 32), or SSM.
- Inventing resource attributes from memory — Always verify provider schema/docs.
- One state file for all accounts — Blast radius; preview separate states in lesson 31.
- Applying from three laptops to one local state myth — There is no shared local state; chaos follows.
Troubleshooting and Design Review
| Symptom | Likely cause | What to do |
|---|---|---|
| Provider not found | Missing init or network to registry | terraform init; check registry access |
| AccessDenied on create | IAM lacking service permissions | Inspect CloudTrail / error message; adjust role |
| Plan wants to destroy unknown resources | Wrong state or wrong directory | Confirm workspace directory; stop; fix state discipline |
| Cycle error in graph | Circular references | Break cycle with data flow redesign |
| Argument is deprecated | Provider upgrade | Read upgrade guide; replace argument |
| Resources missing in console | Wrong region/account | Check provider region and caller identity |
| Destroy stuck | Dependency or API retention | Check ENIs, manual console attachments; retry carefully |
Design review questions: Who may apply? What is the blast radius of this root? Are tags complete? Is the CIDR documented? Is destroy tested in sandbox?
Interview Questions and Answers
Q1: What problem does Terraform solve for AWS teams?
A: Declarative, reviewable infrastructure changes with a plan/apply loop and a provider ecosystem, stored in Git like application code.
Q2: How does Terraform differ from CloudFormation?
A: CloudFormation is AWS-native stack orchestration; Terraform is provider-based HCL with multi-system reach. Both can manage AWS well; choose with ADR and skills of the team.
Q3: What is a provider?
A: A plugin that implements resource types and talks to an API—here, AWS.
Q4: Resource vs data source?
A: Resources are managed; data sources are read-only lookups.
Q5: Why pin provider versions?
A: Avoid surprise schema and behavior changes across CI and laptops.
Q6: What does a replace in a plan mean?
A: Destroy and recreate—downtime or data loss risk depending on the resource.
Q7: How should CI authenticate to AWS for Terraform?
A: Prefer short-lived credentials via OIDC/role assumption, not long-lived keys in repos.
Q8: Why is local state risky for teams?
A: No durable sharing, weak locking story, easy to diverge or leak files; use remote state (lesson 31).
Q9: Implicit vs explicit dependencies?
A: Implicit via references; explicit via depends_on when needed.
Q10: Where do secrets belong?
A: Secrets Manager / SSM Parameter Store SecureString—not plaintext in HCL.
Q11: What IAM does Terraform need?
A: Whatever APIs the resources require—scoped to least privilege for that root’s blast radius.
Q12: Why tag lab resources?
A: Cost allocation, targeting, and confident cleanup.
Q13: When is Terraform the wrong tool for a change?
A: Emergency break-glass may use console/CLI with documented follow-up import; application config on instances may be Ansible/SSM; some orgs standardize solely on CloudFormation/CDK.
Q14: What is the lock file for?
A: Pinning provider versions and checksums for reproducible init.
Q15: How do you keep labs cheap?
A: Budget alerts, single-AZ labs, destroy promptly, avoid idle NAT/ALB when not required for the learning goal.
Hands-On Exercises
Exercise: Provision a VPC and compute service with reusable modules
Goal. Create a tagged lab VPC with public/private subnets and a small compute resource (EC2 or a documented deferral to instance-less networking-only if compute quota is blocked—state the choice in your write-up), using at least one local module (for example tags or security group).
Constraints. Budget alert on; no SSH open to the world; no hardcoded secrets; provider pinned; evidence of plan before apply; destroy at end.
Evidence. Plan output, apply output (redact account if needed), console screenshots of tags, destroy confirmation, short README.
Challenge ADR
Constraint: monthly sandbox budget is tight and a second engineer must reproduce the lab. Write an ADR choosing Terraform over console, single-AZ vs multi-AZ, and how you will move to remote state in lesson 31 without rewriting everything.
Lab evidence checklist
- Budget alert exists
- Lock file committed
- Plan reviewed before apply
- Tags present
- Destroy completed
- No secrets in Git
Narrative: reading a scary plan
Paste a plan that includes a replace; annotate why it replaces and whether you proceed.
Connecting pipelines
Describe how a GitHub Actions workflow from lesson 28 would run fmt, validate, and plan on PRs for this root (apply deferred until remote state exists).
Anti-pattern deep dive: provisioner-only config
Explain why a remote-exec installing packages forever fights immutable image practice from container and EC2 lessons.
Throughput of reviews
Measure how long a teammate needs to understand your module—if over 15 minutes, simplify.
Cross-account awareness
State why a single root applying to prod and sandbox with only a variable flip is dangerous without separate state and roles.
Hands-on cleanup order
terraform destroy- Confirm VPC and SG gone
- Delete leftover key pairs or unused roles created outside Terraform
- Close the branch notes in your lab journal
Interview talking point
Be ready to whiteboard init → plan → apply and where credentials come from.
Closing bridge to lesson 31
List three reasons your lab’s local state would fail a second engineer tomorrow morning.
Deep walkthrough: one apply
Narrate creating VPC → subnet → SG → instance in dependency order as Terraform would schedule.
Security review script
Checklist: credential source, SG rules, IAM instance profile, secret absence, tag completeness.
Cost control narrative for leadership
Explain NAT and idle EC2 as the usual bill drivers in learning accounts.
Relationship to Project work later
Platform projects will expect Terraform or CDK with remote state—practice hygiene now.
for_each mini-exercise
Refactor two subnets from count to for_each keyed by AZ name; show plan stability when removing one.
Output contract
Define outputs another team needs to attach an ALB later—without exposing secrets.
Validate negative path
Introduce a deliberate typo in a resource argument; show validate or plan failure; fix.
Provider docs discipline
Cite the exact provider resource page you used for aws_vpc (link to current docs).
Module boundary sketch
Draw which resources stay in root vs modules/network for lesson 31 refactor.
Drift tease
Manually rename a tag in console; re-plan; observe drift; revert via apply or console—and explain why uncontrolled drift is toxic.
Continuity links recap
Re-read CloudFormation lesson differences; note one stack you would still leave in CloudFormation if the org standardized on it.
Detailed Core Concepts addendum: the AWS provider surface
The AWS provider exposes hundreds of resource types. You will not memorize them. You will learn a navigation habit: start from the Registry documentation for hashicorp/aws, open the resource page, read required arguments, note ForceNew attributes (those that force replace), and skim timeouts. ForceNew is why changing a subnet’s CIDR is not a friendly in-place edit. When an argument is marked deprecated, schedule an upgrade PR—do not silence warnings forever.
Nested blocks matter: security group ingress/egress rules can live inline or as separate aws_security_group_rule / aws_vpc_security_group_ingress_rule style resources depending on the pattern your team chooses. Inline rules can make diffs noisy; separate rules can be clearer for large SG surfaces. Pick one style per module and stay consistent. For TechLearningPro labs, keep rules minimal and explicit.
Technical addendum: backends are not magic
Even before lesson 31, understand that a backend tells Terraform where state lives. The default is local disk. Changing backends later migrates state—an operation you rehearse in sandbox, not on Friday in production. Do not configure a partial remote backend “just to try” mid-lab without reading the migration steps in HashiCorp docs. Curiosity is good; accidental state split is not.
Technical addendum: terraform.tfvars hierarchy
Values can arrive from environment TF_VAR_, files, and CLI -var. Precedence is documented by HashiCorp—learn the order so CI is deterministic. Prefer explicit tfvars files named per environment (sandbox.tfvars) passed deliberately rather than hidden laptop exports that nobody else has.
Enterprise scenario: the “temporary” NAT
A spike enables a NAT gateway for private subnet package installs and forgets to destroy it. Weeks later Cost Explorer shows a quiet tax. Prevention: tag NAT with the project; budget alert; prefer VPC endpoints for AWS APIs (SSM, S3 gateway endpoint) when the lesson goal does not require general internet from private subnets. Foundations networking already taught endpoints—reuse that knowledge in Terraform resources when appropriate.
Enterprise scenario: IAM role name collisions
Two roots both create tlp-progress-api-role in the same account. Apply fails or one wins. Fix: include environment and unique suffix in names; prefer IAM path prefixes; separate accounts for prod.
Hands-on: negative IAM test
Create a second AWS profile/role that can only ec2:Describe*. Run plan (may succeed for read-heavy plans depending on resources) and apply (should fail on create). Capture the error. This proves least privilege thinking and prepares pipeline roles that separate plan and apply.
Hands-on: tag enforcement narrative
Write a short policy for your team: every resource gets Project, Environment, ManagedBy=terraform. Show locals merging. Explain how Cost Explorer filters and how cleanup scripts could use the AWS Resource Groups Tagging API conceptually—without writing a destructive script you have not tested.
Hands-on: comparing to a CloudFormation mental model
Take one resource from lesson 29 (for example a VPC) and write the Terraform equivalent side by side in your notes. List three things that felt clearer in HCL and three that felt clearer in CloudFormation (change sets UI, intrinsic functions, StackSets tease). This comparison is a common interview prompt.
Observability of apply itself
Terraform apply is an operational event. In enterprises, ship plan/apply logs to your CI system artifacts; alert on failed applies to production; record who approved. CloudTrail still shows the underlying AWS API calls—use it when an apply “succeeds” but a resource is wrong because a parallel console change raced you.
Idempotency mindset
Re-running apply with no code changes should show no diff if nobody drifted the cloud. If you always see noise (tags from ASGs, external scanners), use lifecycle ignore_changes narrowly or stop external writers. Chronic noisy plans train humans to click through blindly—the worst IaC failure mode.
Naming and Refactoring awareness
Terraform resource addresses (aws_vpc.lab) are state keys. Renaming a resource address without moved blocks (where supported) or state surgery looks like destroy+create. Refactor carefully; practice renames only in sandbox. Lesson 31 will stress module extraction—which also moves addresses.
Progressive delivery of infrastructure
Infrastructure changes deserve the same caution as application canaries (lesson 34). Prefer small PRs: add subnet, then SG, then compute. Giant first applies are hard to review and hard to roll back.
Lab scoring self-check
Score yourself honestly against the brief’s rubric weights: conceptual accuracy, working implementation, security, reliability/observability, documentation. If security is weak because SSH is open, fix before calling the lab done.
What “done” means for lesson 30
Done means: a reproducible Git commit of HCL, a reviewed plan, a successful apply, evidence of tags, a destroy, and a written note on what lesson 31 must solve for collaboration. Not done means: “it works on my laptop state.”
Extended lab narrative: TechLearningPro progress API network
Imagine Monday’s board depends on a progress API that will eventually run on ECS or Lambda inside private subnets. Before containers and pipelines harden, the platform team needs a repeatable network skeleton: VPC, subnets across the AZ strategy you can afford in sandbox, route tables, and a security group that defaults to deny inbound from the internet. Terraform is how that skeleton stops being a screenshot in a wiki.
Walk the graph slowly. The VPC resource owns the CIDR. Public subnets route 0.0.0.0/0 to an Internet Gateway for load balancers or bastion-alternatives you hope not to need. Private subnets either route to a NAT gateway (costly for labs) or remain AWS-API-only via endpoints. For lesson 30, it is acceptable to place a single lab instance in a public subnet with a locked-down SG only if you document why and remove it on destroy—better is private + SSM as in lesson 20. The point of the exercise is not the perfect network; it is the habit of expressing the network as code with a plan you can paste into a PR.
When you add an aws_instance, attach an IAM instance profile that allows SSM. User data should be minimal: maybe ensure the agent is running. Package sprawl belongs to images or Ansible later. If the instance is only there to prove networking, prefer the smallest current burstable type that exists in your region and verify it in the EC2 console pricing page—do not copy ancient instance families from outdated blogs.
After apply, use the AWS CLI (aws ec2 describe-vpcs --filters Name=tag:Project,Values=tlp-lesson30) to prove tags. Operators who cannot find resources by tag will not clean them up. Output the VPC ID and subnet IDs; store them in your lab notes. Do not paste temporary credentials into those notes.
Change one input: widen the VPC CIDR incorrectly so it overlaps another lab VPC if you have one, or use an invalid CIDR, and show plan/apply failing fast. Negative proofs belong in the deliverable. Then fix the variable and re-apply.
Finally, destroy. Watch the destroy order: instances before subnets before VPC is what the graph should enforce. If destroy fails because an ENI remains, investigate manually created resources or Lambda ENIs from other experiments—another reason sandbox accounts should not mix unrelated spikes without tags.
Extended comparison table for interviews
| Dimension | Terraform (this lesson) | CloudFormation (lesson 29) |
|---|---|---|
| Language | HCL | YAML/JSON templates |
| Preview | plan | change set |
| State | state file / remote backend | stack resources |
| Multi-cloud providers | yes (ecosystem) | AWS-focused |
| Module culture | registry + local modules | nested stacks / modules |
| Org fit | many hiring loops | deep AWS-native shops |
Speak both fluently. Dogma helps no one; ADRs do.
Extended security deep dive
Terraform can create IAM users with access keys. That does not mean you should. Prefer roles for compute and OIDC for humans/CI. If a lab absolutely creates a user, rotate and delete in the same lab session. Security group rules that allow 0.0.0.0/0 on port 22 will fail TechLearningPro’s internal review even in sandbox write-ups—call it out if you temporarily used it and then removed it.
State files can contain attributes of resources, including some sensitive values when mismanaged. Until remote encrypted state arrives in lesson 31, keep local state off shared drives and out of chat apps. Treat terraform.tfstate like a credential file.
Extended reliability notes
IaC reliability is about recreateability. If the account burned down, could you bring the VPC back from Git? If the answer is “plus twelve console clicks,” the lesson is not finished. Reliability also means not coupling unrelated resources in one root so a bad apply takes down everything—state boundaries in lesson 31 address that.
Extended performance notes
Terraform performance is mostly human: plan readability and apply duration. Providers also rate-limit against AWS APIs; huge fan-out creates can hit throttling. For labs, keep resource counts tiny. For enterprises, split roots and stagger applies.
Knowledge check answers (study aid)
- Problem solved: reviewable, repeatable AWS infrastructure changes.
- Alternatives: CloudFormation, CDK, Pulumi, console/CLI—differ by language, ecosystem, and org standards.
- Permissions: IAM rights for each resource API; credential chain for the provider.
- Failures: AccessDenied, invalid CIDR, dependency cycles, accidental replaces—detect via plan review, CloudTrail, and CI logs.
- Safer/cheaper: least privilege, tags, destroy labs, pin versions, remote state next, no secrets in Git.
Challenge response sketch
Under a constrained budget, choose single-AZ, skip NAT if endpoints suffice, use one micro instance or none, destroy daily, and remote state with lifecycle rules on old state versions later. Multi-account isolation waits for Organizations lessons; for now, separate IAM roles for sandbox vs “fake prod” in the same account if you must—but separate accounts are cleaner when available.
Deliverable packaging
Zip is unnecessary; a Git repo link or folder with README, HCL, plan transcript, screenshots, ADR, and cleanup timestamp meets the brief. Rubric weights match the course brief: conceptual 20, implementation 25, security 20, reliability/observability 20, documentation 15.
Closing operational mantra
Init with pins. Plan with humility. Apply with tags. Destroy with proof. Collaborate only after lesson 31.
Provider upgrade rehearsal
In sandbox, intentionally bump the AWS provider constraint within the same major line after reading the changelog summary for that minor range. Run init -upgrade when you mean to upgrade, review the lock file diff, and plan. If the plan is empty, you learned that upgrades are not always scary—but you still review. If the plan shows renames, treat it as a dedicated change, not a drive-by.
Multi-region awareness without multi-region labs
A second provider "aws" { alias = "dr"; region = "..." } pattern exists for DR resources. Do not build dual-region networking in lesson 30. Do know that aliases exist so interviews are not blank stares, and that state and blast radius multiply with every region you touch.
Import awareness
terraform import brings existing resources under management. It is powerful and easy to get wrong. Mention it; practice only on a disposable SG you created in the console for the exercise, then destroy via Terraform. Full import campaigns are change programs, not afternoon labs.
The human workflow that beats any tool
- Branch from main. 2. Edit HCL. 3. fmt/validate. 4. Plan against the right account. 5. Peer review. 6. Apply via controlled path. 7. Observe. 8. Tag proof. 9. Close ticket with outputs. Terraform without this social workflow is just a faster way to make production mistakes.
Worked example: security group that respects SSM
A TechLearningPro progress host in a private subnet should not publish SSH to the world. Express an aws_security_group that allows egress for package updates only if you truly need the public internet; otherwise rely on VPC endpoints for AWS APIs and skip inbound entirely. Operators use Session Manager. Your Terraform therefore creates an instance profile with SSM permissions and omits port 22 ingress. In the lab write-up, state this choice explicitly—it shows you connected Module 2 fleet operations to Module 3 IaC instead of regressing to key pairs.
If you must validate HTTP to a public lab instance, restrict ingress to your current /32 and remove that rule before destroy. Changing a /32 later should show a small plan, not a replace of the instance. That calm diff is what good IaC feels like.
When the security group ID is referenced by the instance, you have an implicit dependency: Terraform will create the SG first. Delete the reference and try to depend_on incorrectly to see how confusing graphs become—then revert to references. Prefer clarity for the engineer who follows you.
Document the final graph in Mermaid in your deliverable, even if it duplicates this lesson’s diagram. Redrawing forces understanding.
Final self-assessment before you move on
Can you explain providers, resources, data sources, plan versus apply, and why local state is a collaboration hazard without looking at notes? Can you show a Git history where fmt and validate ran before apply? Did you destroy the lab and confirm with a tagged describe call that returned empty? If yes, proceed to remote state with confidence. If no, re-run the lab once more—speed without evidence is how console habits return wearing HCL clothing.
Lesson Summary
Terraform gives TechLearningPro a plan-first, Git-reviewed way to manage AWS through the HashiCorp AWS provider: resources to own, data sources to look up, variables and outputs to parameterize, and a dependency graph that orders creates safely. Pin versions, review plans, tag and budget everything, keep secrets out of HCL, and destroy labs. Local state is enough to learn and insufficient to collaborate—that is the door to lesson 31.
Next: Terraform State, Modules and Workspaces—remote backends, locking concepts, module versioning, and when workspaces help versus when separate state is safer.
Further Reading
- Terraform documentation
- AWS Provider documentation
- Terraform language resources
- Terraform data sources
- Providers overview
- Terraform style guide / fmt
- AWS provider authentication
- Amazon VPC user guide
- IAM best practices
- AWS Budgets