Skip to main content
RunBook Academy

TerraformXXIX · Incident Response: The 3 AM TestProduction Terraform

Preparing for the 3 AM Test

Intermediate⏱ ~14 minbash

What you'll learn

  • List the per-scenario playbooks that must exist before a Terraform incident is possible
  • Describe the contact tree that escalates from on-call to platform owner
  • Specify the state backup cadence that precedes every change
  • Plan a quarterly rehearsal that exercises real apply-failure recovery
  • Assign named ownership for the runbook, the alert, the rollback, and the rehearsal

Prerequisites

None — start here.

Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13

Not yet marked complete on this device.

It is 02:47. A production database was just replaced by an apply that was supposed to refresh a security group. The on-call engineer is reachable. The state file is locked. The recovery runbook in the wiki is dated 2024 and references a backend that no longer exists. The post-incident review, three days later, will name a control that would have prevented the incident. The right time to write that control is now, not after the call.

This lesson is the first of six in the incident response module. It covers the work that must be done before there is an incident: the runbooks, the contact tree, the rehearsal cadence, the named ownership. The remaining lessons in the module assume this preparation has been done.

Why preparation matters more than the response

The instinct at 3 AM is to fix the problem. The instinct is correct, but the fix is only as good as the preparation that made it possible. A runbook written six months ago in a calm room is the difference between a 20-minute recovery and a four-hour outage.

Three things must exist before the first incident:

  1. A runbook for each scenario the team has encountered, plus one walkthrough for a scenario they have not.
  2. A contact tree that names a primary on-call, a secondary, and an escalation owner.
  3. A rehearsal cadence that exercises the runbooks against real-world failure modes.

The per-scenario playbooks

Terraform fails in a small number of ways. Most production incidents fall into one of six scenarios. Each scenario has a playbook. The playbook is a markdown file in the runbooks repository, owned by the team, and rehearsed at least once per quarter.

ScenarioSymptomRunbook
State lock contentionError: Lock acquired by another process for more than 5 minutesterraform-runbook-investigate-state-lock
Partial applySome resources updated, then a resource failed mid-createterraform-runbook-recover-partial-apply
Provider failureError: Failed to query available provider or 5xxterraform-runbook-investigate-provider-failure
Drift on critical resourceOut-of-band change visible in terraform planterraform-runbook-detect-drift
Backend unavailableError: Failed to lock the stateterraform-runbook-backend-unavailable
Conflicting concurrent applyTwo PRs apply to the same workspaceterraform-runbook-conflicting-apply

Each row corresponds to a runbook in the course repository. The runbook is a step-by-step procedure with the exact commands, the exact output to expect, and a clear exit criterion. The runbook is not a paragraph about what to think. It is a procedure.

Anatomy of a good runbook

A runbook for the partial-apply scenario reads like this:

# Terraform: Recover from a Partial Apply

## Trigger
`terraform apply` returns non-zero with one or more resources
not in the expected state. The state is unlocked.

## Symptoms
- Apply log shows `Error:` blocks for one or more resources.
- `terraform plan` shows the failed resources as drifted.
- `terraform state list` shows the resources that succeeded.

## Recovery
1. Snapshot the state. (READ-ONLY on the backend.)

   aws s3 cp s3://tf-state-prod/prod/terraform.tfstate \
     s3://tf-state-prod/snapshots/$(date +%Y%m%d-%H%M%S)-pre-apply.tfstate

2. Identify the failed resource from the apply log.

3. Determine whether the resource was created, updated, or
   replaced. Read the apply log; do not guess.

4. If the resource was created or updated to an undesirable state,
   restore the snapshot from step 1.

5. If the resource failed mid-create, run `terraform apply` again.
   The apply will resume from the failed resource.

6. If the resource failed mid-destroy, the resource exists in the
   real world but not in state. Import it.

## Exit criteria
- `terraform plan` shows no changes.
- The failed resource is in the expected state via the provider API.

The procedure is mechanical. The judgement is in the pre-flight (deciding whether to re-apply or restore). The judgement is exercised during the rehearsal, not at 3 AM.

The contact tree

The contact tree is a single page in the on-call runbook. It names the people, not the aliases. Aliases rotate. People do not.

Primary on-call (Terraform)
  - Name: A. Smith
  - Contact: pager + mobile
  - Coverage: 24x7, 7-day rotation

Secondary on-call (Terraform)
  - Name: B. Jones
  - Contact: pager + mobile
  - Escalation: page after 15 minutes
    of unresolved primary

Platform owner (escalation)
  - Name: C. Patel
  - Contact: mobile
  - Escalation: page after 30 minutes
    of unresolved secondary

VP Engineering (executive escalation)
  - Name: D. Khan
  - Contact: mobile
  - Escalation: page only for SEV-1
    or customer-facing outage

The timing is the agreement. It is rehearsed. It is not left to the on-call engineer to decide when to escalate. The decision is encoded in the runbook.

The state backup cadence

State is the recovery story. The cadence must precede the change, not follow it.

For a Terraform workspace that may be applied at any time, the minimum cadence is:

  • Before every apply. A snapshot of the state file is taken immediately before the apply. The snapshot is stored in a versioned, encrypted location separate from the state itself.
  • After every apply. A snapshot is taken immediately after the apply completes. The pre-apply snapshot is retained for at least 30 days.
  • Nightly. A scheduled job takes a snapshot regardless of apply activity. This catches unattended state corruption.
  • Weekly. A full export from the backend is taken to offsite storage. This is the disaster recovery backup.
# Severity: READ-ONLY. Snapshot the state before the apply.
aws s3 cp s3://tf-state-prod/prod/terraform.tfstate \
  s3://tf-state-prod/snapshots/$(date +%Y%m%d-%H%M%S)-pre-apply.tfstate

The command lives in the apply wrapper. The on-call engineer does not have to remember to run it. The CI pipeline runs it.

The rehearsal cadence

A runbook that has never been rehearsed is documentation, not a runbook. The rehearsal schedule:

  • Quarterly. A scheduled game day. The team receives a paged scenario. The on-call engineer follows the runbook. The clock is real. The failure is real.
  • Monthly. A tabletop walkthrough. The on-call engineer narrates the runbook out loud. The team asks questions. No real infrastructure is touched.
  • On every change to the runbook. The runbook author rehearses the updated procedure in a non-production environment before the change is merged.

The quarterly game day is the most important. It is the closest rehearsal to the actual incident. The scenario is chosen from the historical incident list. The team rotates so the same engineer is not always the one being tested.

The ownership

The preparation work has owners. The owners are named in the runbook repository.

  • Runbook owner. Maintains the per-scenario runbooks. Reviews the runbook at every quarterly rehearsal. Updates the runbook when the platform changes.
  • Alert owner. Maintains the alerting rules. The alert owner is the one who is paged when the alert shape is wrong.
  • Rollback owner. Maintains the rollback plan for the top three production workspaces. The rollback owner is the one who is paged when the rollback fails.
  • Rehearsal owner. Schedules the quarterly game day. The rehearsal owner is the one who is held accountable for the cadence.

The owners rotate annually. The rotation is announced. The runbook lists the current owners.

Validation

The state of the preparation can be measured. The measures are dashboards, not opinions.

  • Runbook coverage. Number of runbooks against the number of scenarios in the historical incident list. Target: 100% of the top ten scenarios.
  • Rehearsal cadence. Number of game days held in the last 12 months. Target: 4.
  • Snapshot freshness. Time since the last successful pre-apply snapshot. Target: 0 at the start of every apply.
  • Contact tree freshness. Date of the last update. Target: refreshed within 30 days.

What comes next

The next lesson is Detecting a Production Incident. The runbooks are written. The contacts are named. The cadence is rehearsed. The next lesson is about the alerts that page the on-call before the customer notices.

Verification

# Severity: READ-ONLY. Confirm the runbook repository exists.
test -d /srv/runbooks/terraform && echo "runbooks present"

# Severity: READ-ONLY. Confirm the latest snapshot was taken today.
aws s3 ls s3://tf-state-prod/snapshots/ \
  --recursive | grep "$(date +%Y%m%d)" | head -5

# Severity: READ-ONLY. Confirm the contact tree has been refreshed in the last 30 days.
stat -c %y /srv/runbooks/terraform/CONTACTS.md

# Severity: READ-ONLY. Confirm the rehearsal log entry exists for the current quarter.
test -f /srv/runbooks/terraform/GAMEDAY/$(date +%Y-Q$(($(date +%m)/3+1))).md \
  && echo "game day planned"

The preparation is verified when the commands succeed and the outputs are recent.

Knowledge check · 7 questions

  1. Q1. What is the first thing that must exist before a Terraform incident can be handled at 3 AM?

  2. Q2. How often should the state backup be taken for a production workspace?

  3. Q3. A runbook that has never been rehearsed is documentation rather than a runbook.

  4. Q4. Which of the following must be assigned named ownership? (Select all that apply.)

  5. Q5. A new lead joins the team. The runbook repository is a year old. The first responsibility is to:

  6. Q6. What is the role of the contact tree?

  7. Q7. When should the state backup be retained for at least 30 days?

Passing score: 75%. Answers are checked in this browser.