Handbooks / CI/CD & DevOps / Chapter 3

Infrastructure as Code

7 pages · ~5 min✓ Reviewed

Builds on Testing in the Pipeline. Next up: Deployment Strategies.

CI/CD & DevOpsPage 1

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

DeclarativeImperative
ToolsTerraform, CloudFormationbash + CLI, SDK scripts
You writeDesired end stateExact ordered steps
FreeDiffs, ordering, destroyFull control, flexibility
Hand-writeNothingOrdering, errors, cleanup
  • Idempotent = re-running a config changes nothing if reality already matches
  • Imperative create script run twice → fails or duplicates
  1. Desired stateyour config
  2. Diffvs reality
  3. Orderdependencies
  4. 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
Remember

Declarative = what you want; imperative = how to get there.

CI/CD & DevOpsPage 2

Providers and Resources

1. Providers

  • Provider = plugin turning HCL into API calls
  • Declared in required_providers: source + version
  • terraform init downloads it
hcl
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

2. Resources and Order

hcl
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_on only for hidden dependencies

3. Data, Inputs, Outputs

resourcedata
Blockresource "aws_s3_bucket" "logs"data "aws_ami" "ubuntu"
Doescreates and managesonly reads existing
  • variable takes inputs, output exposes values: reusable config
CI/CD & DevOpsPage 3

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 plan is a read-only preview; terraform apply executes it
  • A saved plan applies exactly what was reviewed: the safe pattern for CI
SymbolMeaning
+create
~update in place
-/+replace: destroy, then create
-destroy
bash
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 = true replaces the deprecated DynamoDB lock
  • Or HCP Terraform, formerly Terraform Cloud
Interview Point

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.

CI/CD & DevOpsPage 4

Everyday Terraform Commands

1. The Core Loop

  1. initproviders, backend
  2. fmtformat files
  3. validatesyntax, types, no API
  4. planpreview
  5. applyexecute
  • terraform destroy removes everything in state
  • apply -target=ADDR limits scope: emergencies only, not routine

2. State and Import

CommandEffect
state listlists addresses in state
state show ADDRshows one resource's attributes
state rm ADDRstops managing, no delete
state mv OLD NEWrenames address, no recreate
import ADDR IDadopts an existing resource
Import

It writes state only. You must write the matching resource block yourself.

hcl
import {
  to = aws_s3_bucket.logs
  id = "my-logs"
}

block form of: terraform import aws_s3_bucket.logs my-logs

3. Interview Point

CI/CD & DevOpsPage 5

Drift, Environments and Common Mistakes

1. Drift

  1. Console editoutside Terraform
  2. planrefreshes, shows diff
  3. applyreverts to config
  • apply -refresh-only records reality in state, resources untouched
  • plan -refresh-only only previews that update

2. Environments

SetupStateFit
Directory per envseparateprod
Module + -var-file=prod.tfvarsseparateprod
Workspacesshared backend + configlight only
Prod

Use separate state and backends. A wrong selected workspace applies to prod.

3. Mistakes

  • Committing tfstate or secrets
  • apply without reading the plan
  • Unpinned provider versions
  • One giant state file
  • Hand edits in the console

4. Interview Point

  • Detect and fix drift? plan shows it; apply reverts 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
CI/CD & DevOpsPage 6

Terraform and IaC Cheat Sheet

1. Mental Model

PieceRole
Configdesired state, declarative and idempotent
Statewhat Terraform believes exists
Planthe diff between them
Applyconverge reality to config
  1. Edit .tfconfig
  2. Planlock, refresh, diff
  3. Apply tfplanchange resource
  4. State savedunlock

2. Workflow

bash
terraform init
terraform fmt
terraform validate
terraform plan -out=tfplan
terraform apply tfplan
terraform destroy

destroy tears down everything in state

3. Plan Symbols

SymbolMeaning
+create
~update in place
-destroy
-/+replace: destroy, then create

4. State Tools

  • state list and state show ADDR inspect state
  • state rm stops managing; state mv renames, no recreate
  • import ADDR ID adopts a resource; write its block yourself
  • plan -refresh-only previews drift; apply -refresh-only records it
  • Team: remote backend with locking (S3 use_lockfile = true, HCP Terraform)
Pause on -/+

A replace on a database or disk destroys data. Read the plan before you apply.

CI/CD & DevOpsPage 7

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-only records 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.audit first