Runbooks
Procedures written down, tested and timed.
Part 1: The problem
A bad release broke checkout at 11pm. Everyone knew the fix was to roll back. Theo had done it twice before — but Theo was on holiday.
Noor, on call, found a chat message from months earlier describing the steps. The second step referred to a server that no longer existed. The third needed a permission her role didn’t have. She woke Sam, who had the permission but had never done a rollback. Together they worked it out.
The rollback took four minutes of actual work. It took fifty minutes to get there.
A runbook turns “someone who knows” into “anyone on call”. This lesson is about writing one that works when it’s needed, and proving that it does.
Part 2: The idea
A runbook is a written procedure for one specific situation, detailed enough that anyone on the team could follow it at 3am, half awake, without asking anyone.
It is not documentation of how the system works. It’s a checklist for one job: “checkout returns 5xx”, “the database disk is nearly full”, “roll back a release”.
The parts of a good runbook
| Part | What it says | Why |
|---|---|---|
| Trigger | Which alarm or symptom sends you here | So you know you’re in the right runbook |
| Impact | What customers experience while this is happening | So you know how urgent it is, and what to tell people |
| Steps | Numbered, exact actions: what to open, what to run, what to look for | So nobody has to think about how under pressure |
| Verification | How to know it’s fixed, as a measurable test | So you don’t stop too early |
| Rollback | How to undo each step that changes something | So a wrong step doesn’t make things worse |
| Escalation | Who to call if it isn’t working, and when | So nobody struggles alone for an hour |
| Owner | The team that keeps it up to date | So someone fixes it when it goes stale |
| Last tested | The date of the last timed dry run, and how long it took | So the reader knows how much to trust it |
Exact steps, not good intentions
| Vague | Exact |
|---|---|
| Check the dashboard | Open the quickcart-checkout dashboard (link). Is the 5xx graph above 1%? |
| Look in the logs | Run the saved Logs Insights query checkout-errors-by-type for the last 30 minutes |
| Check the target group | EC2 → Target Groups → quickcart-checkout-tg → Targets. Are all targets Healthy? |
| Roll back | Run the quickcart-rollback job with VERSION set to the previous version, shown on the job’s last successful run |
| Make sure it works | Done when the 5xx alarm is OK and the 5xx rate stays under 1% for 5 minutes |
Every exact step names where, what, and what to do with the answer. Each step that makes a change also says how to undo it.
Test it, and time it
A runbook nobody has run is a guess, like an alarm nobody has seen fire. Test it with a dry run:
| Kind of test | How | Finds |
|---|---|---|
| Read-through | Someone who didn’t write it reads each step aloud and says what they’d do | Vague steps, missing names, assumptions |
| Timed dry run | Follow it for real, in a staging environment, with a stopwatch | Dead links, missing permissions, slow steps |
| Game day | Break something on purpose in staging, and hand the on-call person only the alarm | Whether the whole chain works: alarm, notification, runbook, fix |
After each test: fix every gap you found, write down the time it took, and update Last tested. If it took 37 minutes and your target is 15, the runbook isn’t finished.
Keep them where people will find them
- Link each runbook from its alarm. Put the link in the alarm’s description; it’s included in the notification, so the first thing the on-call person reads contains the first step.
- Keep runbooks in the repository, as Markdown next to the code. Changes are reviewed like code, and the history shows who changed what.
- Automate the steps that never change. A rollback that is always the same five commands should be one pipeline job. The runbook then says “run this job”, and the job can’t mistype a value. Canary from a pipeline builds one.
A complete example: checkout returns 5xx
This is QuickCart’s runbook for its most important alarm. Use it as a model.
Runbook: checkout returns 5xx
| Field | Value |
|---|---|
| Trigger | Alarm quickcart-target-5xx or quickcart-alb-5xx is in ALARM |
| Impact | Some or all customers can’t place orders. They see “Something went wrong” at checkout |
| Owner | Platform team |
| Target time | Service restored within 15 minutes of the alarm |
| Last tested | Last month, dry run in staging: 14 minutes |
| Escalation | If not improving after 20 minutes, call the second on-call. If customers can’t pay at all, tell the support lead |
Step 1: confirm it’s real (2 minutes). Open the quickcart-checkout dashboard. If the 5xx rate is above 1%, it’s real: carry on. If it’s zero and the alarm has already returned to OK, note it in the alarm review and stop.
Step 2: is the load balancer happy with the servers? (1 minute).
In the AWS console: EC2Load BalancingTarget Groupsquickcart-checkout-tgTargets
| You see | Means | Go to |
|---|---|---|
| All targets Healthy | The servers are up; the errors come from the app | Step 3 |
Some or all Unhealthy, reason Target.Timeout | The load balancer can’t reach them: a security group or network problem | Where to look first, network path |
| Some or all Unhealthy, other reasons | The app is failing its health check | Step 4 — something may have been deployed |
Step 3: what are the errors? (2 minutes).
In the AWS console: CloudWatchLogs Insights
Choose the log group /quickcart/orders, set the range to 30m, and run:
filter level = "ERROR"
| stats count() as errors, avg(latency_ms) as avg_ms by error_type
| sort errors desc| Part | What it means |
|---|---|
filter level = "ERROR" | only error lines |
| stats count() as errors, avg(latency_ms) as avg_ms | count them and average their response time |
by error_type | one row per kind of error |
| sort errors desc | the most common first |
Top error_type | Means | Go to |
|---|---|---|
OperationalError, around 3,000 ms | The app can’t connect to the database: usually its security group | Check quickcart-db-sg allows 3306 from quickcart-app-sg, then CloudTrail for who changed it |
ProgrammingError, a few ms | The database answered with an error, such as a missing table: usually a migration | Step 4 |
| Anything else | A code problem | Step 4 |
Step 4: what changed? (2 minutes). Look at the last two runs of the deploy pipeline. Then open CloudTrail → Event history with Read only set to false for the last hour. If a deploy went out in the hour before the alarm, go to step 5.
Step 5: roll back (3 minutes). Run the quickcart-rollback job with VERSION set to the version before the latest, shown on the job’s last successful run. The on-call role has permission to run it.
- To undo this step: run the same job with the newer version. Only do this once the cause is fixed.
Step 6: verify (5 minutes). It’s fixed when both are true:
- the 5xx alarm is back to OK, and
- the 5xx rate on the dashboard stays under 1% for 5 minutes in a row.
Then post in the incident channel: what happened, what you did, and the times. A postmortem follows within two working days.
Part 3: See it
Below is the checkout runbook as it was first written: from memory, never tested. Run it one step at a time and watch the clock. Then switch to the version written after the dry run and run it again.
- 1. Confirm the alarm is realNext
Runbook says: Check the dashboard.
- 2. Check target healthNot yet
Runbook says: Check the target group.
- 3. Find the error in the logsNot yet
Runbook says: Look in the logs.
- 4. Check what changedNot yet
Runbook says: See if anything was deployed.
- 5. Roll backNot yet
Runbook says: Roll back the last deploy.
- 6. Verify it's fixedNot yet
Runbook says: Make sure it works.
What to notice: the steps are the same six in both versions. What changed is that each one got exact names, a link that works, a saved query, a tested permission and a clear finish line. None of those gaps was visible until someone ran the runbook with a clock.
Part 4: Do it
Free — you’ll write a document and time yourself. Nothing is created in AWS.
You’ll write a runbook for one QuickCart alarm, then dry-run it with a stopwatch and fix what you find. About 30 minutes. You don’t need anything running: a read-through dry run works on paper, and it finds most gaps.
Pick one alarm
Choose one alarm from Alarms that actually fire, other than the 5xx one, which has an example above. Good choices:
Alarm Situation quickcart-unhealthy-hostsA server is failing its health checks quickcart-db-connectionsThe database is running out of connections quickcart-db-storageThe database disk is nearly full Write it from the template
Create a file called
runbooks/followed by a short name, such asrunbooks/db-storage.md, and copy this template into it:runbooks/db-storage.md# Runbook: <what is happening> | Field | Value | |---|---| | Trigger | <the alarm name, and its condition> | | Impact | <what customers experience> | | Owner | <the team> | | Target time | <restored within N minutes of the alarm> | | Last tested | never | | Escalation | <who to call, and after how long> | ## Step 1: confirm it's real (<N> minutes) <exactly what to open, and what number means it's real> ## Step 2: <name> (<N> minutes) <where to go, what to run, what to look for> <what each possible answer means, and which step to go to> ## Step 3: fix (<N> minutes) <the exact action> To undo this step: <the exact way back> ## Step 4: verify (<N> minutes) Fixed when: <a measurable test, and for how long>Replace everything in angle brackets. For each step, write how many minutes you think it will take.
You should seeA runbook of four to six steps. Every step names a place, an action and what to do with the answer. Every step that changes something has a way back.
Read it through as a stranger
Read each step aloud, and for each one ask:
Question If the answer is no Would someone who’s never seen this system know exactly where to click? Add the console path or the link Does it say what to do with each possible answer? Add the branches Could the on-call person do it with their own permissions? Note the permission needed, and check it Is “fixed” something you can measure? Replace it with a number and a duration Mark every gap you find.
Dry-run it with a stopwatch
Start a stopwatch on your phone. Pretend the alarm has just fired, and follow your runbook step by step. If you have the environment from A self-healing environment or a staging copy, do the steps for real. If not, open each console page the step names and find each thing it mentions, without changing anything.
Write down how long each step really took next to your estimate.
You should seeAt least one step took much longer than you expected. That’s normal on a first run, and it’s exactly what the dry run is for.
Fix the gaps and record the test
For every step that was slow or unclear, make it exact. Then update the header:
Field Enter Last tested today’s date, dry runorread-through, and the total timeIf the total is over your target time, run it again after the fixes.
Link it from the alarm
When the alarm exists, put the runbook’s link in its description:
In the AWS console: CloudWatchAlarmsAll alarmsthe alarmActionsEdit
Choose Next until you reach Add name and description.
Field Enter Alarm description One line on what it means, then Runbook:and the link, such ashttps://example.com/runbooks/db-storageYou should seeThe link in the alarm’s details. The next notification will include it.
If you deleted your alarms after the last lesson, keep the description ready for when you build them again in the projects.
Clean-up step: Clean up
Nothing was created in AWS, so there’s nothing to delete. Keep your runbook file: put it in the repository next to your QuickCart code, where you’ll use it in A self-healing environment.
You should seeOne tested runbook, with a real date in Last tested.
Part 5: Check yourself
Which runbook step is written well?
Why write down the rollback in a runbook, not just the fix?
Where should the link to the runbook live, so the person on call finds it fastest?
A runbook was written a year ago and never run. What should you assume?
What does "Last tested" on a runbook tell the reader?
Part 6: Cheat sheet
| Part or practice | In one line |
|---|---|
| Runbook | Exact steps for one situation, followable at 3am by anyone |
| Trigger | The alarm that sends you here |
| Impact | What customers see |
| Steps | Where, what, and what to do with the answer |
| Verification | A measurable “fixed”, with a duration |
| Rollback | How to undo each step that changes something |
| Escalation | Who to call, and after how long |
| Owner | Who keeps it up to date |
| Last tested | The date and time of the last dry run. Trust it accordingly |
| Dry run | Follow it with a stopwatch; fix every gap |
| Link from the alarm | Put the runbook link in the alarm description |
| Automate the fixed parts | A rollback is a job, not five commands to type |