Infrastructure as Code — Junior¶
At junior level, focus on this question:
Can you write, plan, and apply a small Terraform configuration, then reproduce the exact same infrastructure from scratch?
Use the smallest realistic scenario that exposes the decision and its failure behavior.
1. What Infrastructure as Code Actually Means¶
Infrastructure as Code (IaC) means you describe the servers, networks, storage, and other cloud resources you need as text files, check those files into version control, and let a tool create, change, or delete the real resources to match what the text says. The alternative — clicking buttons in a cloud console to create a bucket or a virtual machine — is sometimes called ClickOps. ClickOps works once. It does not survive a second environment, a second engineer, or a memory lapse six months later about which checkbox you ticked.
IaC tools fall into two families:
- Declarative — you describe the end state you want ("there should be exactly one S3 bucket named
acme-nightly-reports-dev"), and the tool figures out what actions get you there. Terraform, CloudFormation, and Pulumi (in declarative mode) work this way. - Imperative — you describe the steps to take ("create a bucket, then enable versioning on it"), similar to a shell script. Most cloud CLIs and hand-rolled bash scripts work this way.
This module uses Terraform as the reference tool because its plan/apply workflow is the clearest way to learn the core ideas, and those ideas transfer directly to Pulumi, CloudFormation, or any other declarative provisioning tool.
| Term | Meaning |
|---|---|
| Provider | A plugin that knows how to talk to one platform's API (aws, google, azurerm) |
| Resource | One concrete thing to create — a bucket, a VM, a database instance |
| Configuration (HCL) | The .tf files where you declare providers and resources |
| State | A file (usually JSON) recording what Terraform believes currently exists, mapping each resource block to a real, live object |
| Plan | A dry run: compares configuration + state + real infrastructure, and shows exactly what would change |
| Apply | Executes the plan: creates, updates, or destroys real resources to match the configuration |
2. The Repeatable Method¶
Every IaC change, from a single test bucket to a production database, follows the same loop:
terraform initdownloads the provider plugin and sets up the backend that stores state. You run it once per new configuration, and again whenever you add a provider.terraform planis the step juniors skip and regret skipping. It never touches real infrastructure — it only computes and prints a diff. Read it every time, even on the tenth "trivial" change.terraform applyperforms the actions the plan described. By default it re-shows the plan and asks for confirmation (yes) before doing anything.- Verify independently — with the cloud CLI or console — that what you asked for is what you got. Don't trust "Apply complete" alone; trust the evidence.
3. A Worked Example: One S3 Bucket¶
Assume you need a bucket to hold nightly report exports, in a fresh AWS account, region ap-southeast-1.
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-southeast-1"
}
resource "aws_s3_bucket" "reports" {
bucket = "acme-nightly-reports-dev"
tags = {
Environment = "dev"
ManagedBy = "terraform"
}
}
resource "aws_s3_bucket_versioning" "reports" {
bucket = aws_s3_bucket.reports.id
versioning_configuration {
status = "Enabled"
}
}
Run terraform init, then terraform plan:
Terraform will perform the following actions:
# aws_s3_bucket.reports will be created
+ resource "aws_s3_bucket" "reports" {
+ bucket = "acme-nightly-reports-dev"
+ id = (known after apply)
+ tags = {
+ "Environment" = "dev"
+ "ManagedBy" = "terraform"
}
}
# aws_s3_bucket_versioning.reports will be created
+ resource "aws_s3_bucket_versioning" "reports" {
+ bucket = (known after apply)
+ id = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.
The + marks show only new resources — nothing existed before, so nothing changes or is destroyed. This is the moment to stop and read: does "2 to add, 0 to change, 0 to destroy" match what you expected? If a plan ever says "1 to destroy" when you thought you were only adding something, stop before applying.
Run terraform apply, type yes when prompted:
aws_s3_bucket.reports: Creating...
aws_s3_bucket.reports: Creation complete after 1s [id=acme-nightly-reports-dev]
aws_s3_bucket_versioning.reports: Creating...
aws_s3_bucket_versioning.reports: Creation complete after 0s [id=acme-nightly-reports-dev,Enabled]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Verify independently:
Now run terraform plan again with no changes to the file. You should see:
This is the property that makes IaC worth learning: the configuration is a reproducible description of reality. If you deleted this bucket and ran terraform apply again from the same .tf file, on the same or a different machine, you would get the identical bucket back (same name, same tags, same versioning setting) — that is reproducibility, and it is the whole point.
4. Success Criteria¶
| Check | What it proves |
|---|---|
terraform plan shows the exact count of adds/changes/destroys you expect | Your mental model matches the tool's |
terraform apply completes with "0 destroyed" when you only intended to add | Nothing was accidentally removed |
| The resource is visible via the cloud CLI/console, not just in Terraform's output | The change actually reached the real infrastructure |
A second terraform plan reports "No changes" | Configuration and reality are in sync — no drift |
| Deleting the resource and re-applying recreates it identically | The configuration is reproducible, not a one-off |
5. Common Beginner Mistakes¶
- Applying without reading the plan. Typing
yeson autopilot is how a junior discovers, too late, that a rename was actually a destroy-and-recreate. - Hand-editing a Terraform-managed resource in the console. Change a tag by hand in the AWS console, and the next
terraform applywill either silently revert it (if the attribute is managed) or leave it inconsistent — this is called drift, and it starts eroding trust in the state file immediately. - Committing secrets in the configuration. Hardcoding an access key or a database password directly in a
.tffile puts it in your Git history forever, even if you delete the line later. - Not pinning the provider version. Omitting
version = "~> 5.0"means a futureterraform initcan silently pull a newer provider with different defaults, and your "unchanged" configuration suddenly plans a diff. - Forgetting to destroy throwaway resources. A learning sandbox bucket or test VM left running after the exercise quietly accrues cost — always pair
applywith a plannedterraform destroywhen you're done. - Reusing a resource name across learners or environments. Two people running the same tutorial in the same account with
bucket = "reports"collide — bucket names in S3 are globally unique, and generic names are a frequent source of confusing plan errors.
Apply it¶
- Write a Terraform configuration with one
aws_s3_bucketresource named uniquely to you (for example,<yourname>-iac-junior-exercise). - Run
terraform initthenterraform plan, and read the diff before doing anything else — confirm it says exactly "1 to add, 0 to change, 0 to destroy." - Run
terraform apply, then verify the bucket exists usingaws s3api head-bucket --bucket <name>(or your provider's equivalent CLI command). - Add an
aws_s3_bucket_versioningresource pointing at the same bucket, runplanagain, and confirm it now shows "1 to add" for the new resource only — nothing about the bucket itself should change. - Run
terraform destroy, confirm the CLI verification command now fails with "not found," and runterraform planonce more to confirm it proposes recreating both resources from nothing.
Verify your work¶
- The
planoutput before everyapplymatched the number of adds/changes/destroys you expected. - An independent CLI or console check confirms the resource exists with the exact name and settings from your configuration.
- A
planrun immediately afterapplyreports "No changes." terraform destroyremoves the resources, and a follow-up CLI check proves they are gone.
Review questions¶
- What is the difference between
terraform planandterraform apply, and why should you never skip reading the plan? - Why does hand-editing a Terraform-managed resource in the cloud console cause problems on the next apply?
- What does it mean for a Terraform configuration to be "reproducible," and how would you prove it?
- Why is committing a hardcoded secret into a
.tffile dangerous even if you remove it in a later commit?