Handbooks / CI/CD & DevOps / Chapter 3
Infrastructure as Code
7 pages · ~5 min✓ Reviewed
Builds on Testing in the Pipeline. Next up: Deployment Strategies.
Why IaC and Declarative vs Imperative
1. What IaC Is
- Infra as version-controlled text files
- Reproducible environments
- Reviewable in PRs
- Auditable Git history
- Not built by clicking in a console
2. Declarative vs Imperative
| Declarative | Imperative | |
|---|---|---|
| Tools | Terraform, CloudFormation | bash + CLI, SDK scripts |
| You write | Desired end state | Exact ordered steps |
| Free | Diffs, ordering, destroy | Full control, flexibility |
| Hand-write | Nothing | Ordering, errors, cleanup |
- Idempotent = re-running a config changes nothing if reality already matches
- Imperative
createscript run twice → fails or duplicates
- Desired stateyour config
- Diffvs reality
- Orderdependencies
- Applychanges only
3. Interview Point
- Why declarative? Diffs, ordering, destroy come free; PR-reviewable
- Idempotent? Re-run changes nothing if reality matches
- Imperative still right? One-off tasks, glue steps
Declarative = what you want; imperative = how to get there.
Providers and Resources
1. Providers
- Provider = plugin turning HCL into API calls
- Declared in
required_providers: source + version terraform initdownloads it
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}2. Resources and Order
resource "aws_s3_bucket" "logs" { bucket = "my-logs" } resource "aws_s3_bucket_public_access_block" "p" { bucket = aws_s3_bucket.logs.id }
first address: aws_s3_bucket.logs
- Type + local name = address, unique per config
- A reference makes an implicit dependency
depends_ononly for hidden dependencies
3. Data, Inputs, Outputs
| resource | data | |
|---|---|---|
| Block | resource "aws_s3_bucket" "logs" | data "aws_ami" "ubuntu" |
| Does | creates and manages | only reads existing |
variabletakes inputs,outputexposes values: reusable config
State, Plan and Apply
1. What State Is
- terraform.tfstate maps each resource address to its real-world ID and attributes
- Terraform diffs config vs state vs refreshed reality
2. Plan and Apply
terraform planis a read-only preview;terraform applyexecutes it- A saved plan applies exactly what was reviewed: the safe pattern for CI
| Symbol | Meaning |
|---|---|
| + | create |
| ~ | update in place |
| -/+ | replace: destroy, then create |
| - | destroy |
terraform plan -out=tfplan terraform apply tfplan
applies the reviewed plan, nothing newer
3. Remote Backend
- Shares state across a team and locks it: no two applies at once
- S3:
use_lockfile = truereplaces the deprecated DynamoDB lock - Or HCP Terraform, formerly Terraform Cloud
No hand-edit or Git? Plaintext secrets, and edits desync state from reality. Locking? Stops two concurrent applies. -/+ ? Destroy first: risk to a database or disk.
Everyday Terraform Commands
1. The Core Loop
- initproviders, backend
- fmtformat files
- validatesyntax, types, no API
- planpreview
- applyexecute
terraform destroyremoves everything in stateapply -target=ADDRlimits scope: emergencies only, not routine
2. State and Import
| Command | Effect |
|---|---|
state list | lists addresses in state |
state show ADDR | shows one resource's attributes |
state rm ADDR | stops managing, no delete |
state mv OLD NEW | renames address, no recreate |
import ADDR ID | adopts an existing resource |
It writes state only. You must write the matching resource block yourself.
import { to = aws_s3_bucket.logs id = "my-logs" }
block form of: terraform import aws_s3_bucket.logs my-logs
3. Interview Point
Drift, Environments and Common Mistakes
1. Drift
- Console editoutside Terraform
- planrefreshes, shows diff
- applyreverts to config
apply -refresh-onlyrecords reality in state, resources untouchedplan -refresh-onlyonly previews that update
2. Environments
| Setup | State | Fit |
|---|---|---|
| Directory per env | separate | prod |
Module + -var-file=prod.tfvars | separate | prod |
| Workspaces | shared backend + config | light only |
Use separate state and backends. A wrong selected workspace applies to prod.
3. Mistakes
- Committing
tfstateor secrets applywithout reading the plan- Unpinned provider versions
- One giant state file
- Hand edits in the console
4. Interview Point
- Detect and fix drift?
planshows it;applyreverts it, or update config to keep it - Workspaces vs directories? Workspaces share a backend; directories isolate state
- Avoid wrong-env prod apply? Separate backend,
-var-file, read the plan
Terraform and IaC Cheat Sheet
1. Mental Model
| Piece | Role |
|---|---|
| Config | desired state, declarative and idempotent |
| State | what Terraform believes exists |
| Plan | the diff between them |
| Apply | converge reality to config |
- Edit .tfconfig
- Planlock, refresh, diff
- Apply tfplanchange resource
- State savedunlock
2. Workflow
terraform init terraform fmt terraform validate terraform plan -out=tfplan terraform apply tfplan terraform destroy
destroy tears down everything in state
3. Plan Symbols
| Symbol | Meaning |
|---|---|
+ | create |
~ | update in place |
- | destroy |
-/+ | replace: destroy, then create |
4. State Tools
state listandstate show ADDRinspect statestate rmstops managing;state mvrenames, no recreateimport ADDR IDadopts a resource; write its block yourselfplan -refresh-onlypreviews drift;apply -refresh-onlyrecords it- Team: remote backend with locking (S3
use_lockfile = true, HCP Terraform)
A replace on a database or disk destroys data. Read the plan before you apply.
Check yourself
The plan shows -/+ aws_db_instance.main. What happens on apply?
- It destroys, then creates the database, so the data is lost
- Find the attribute that forces replacement and snapshot first
Someone edits a security group in the console. What do plan and apply do?
- Plan shows
~update back to the config; apply reverts the edit - To keep the edit:
apply -refresh-onlyrecords it in state, then update the config
You rename the block logs to audit in the .tf file. What does plan show, and how do you avoid it?
-destroy logs and+create audit, because state still holds the old address- Run
terraform state mv aws_s3_bucket.logs aws_s3_bucket.auditfirst