Why Markdown Wins for Runbooks (Over Notebooks)
Jupyter and VS Code notebooks are powerful, but they weren’t designed for operational runbooks. Markdown was—and here’s why it matters.
For runbook tool comparisons, see our interactive runbook tools comparison.
The Format Problem
Open a Jupyter notebook in a text editor:
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": ["# Check Pod Status\n", "\n", "Run this to see current pods."]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [{"name": "stdout", "output_type": "stream", "text": ["NAME..."]}],
"source": ["!kubectl get pods"]
}
]
}
Now open the same runbook in markdown:
# Check Pod Status
Run this to see current pods.
```bash
kubectl get pods
```
Which would you rather read at 3am during an incident?
Why Plain Text Matters for Ops
Reason 1: Incidents Don’t Wait for Tools
During an incident:
- Your IDE might not be available
- You might be SSH’d into a bastion host
- You might be on a borrowed laptop
- Your tool might be down (ironic, right?)
Markdown works everywhere:
# Read a runbook with any tool
cat runbook.md
less runbook.md
vim runbook.md
Reason 2: Git Actually Works
JSON notebook diffs are unreadable:
- "source": ["kubectl get pods -n production"]
+ "source": ["kubectl get pods -n production -o wide"]
Markdown diffs are clear:
- kubectl get pods -n production
+ kubectl get pods -n production -o wide
This means:
- Code review works for runbooks
- History is meaningful
- Merge conflicts are resolvable
- Blame shows who changed what
Reason 3: No Lock-In
Notebooks require specific software:
- Jupyter needs Python runtime
- VS Code notebooks need VS Code
- Each has its own kernel requirements
Markdown is universal:
- Any text editor
- Any git host renders it
- Any documentation system accepts it
- Any static site generator builds it
Reason 4: Copy-Paste Friendly
From a notebook, copying a command means:
- Open the notebook
- Find the cell
- Click into the cell
- Select the code
- Copy
From markdown:
- View the file (raw or rendered)
- Copy the command
When you’re in a hurry, every step matters.
What Notebooks Do Better
Notebooks aren’t all bad. They excel at:
| Use Case | Why Notebooks Win |
|---|---|
| Data analysis | Rich output (tables, charts) |
| Machine learning | Visualizations, state management |
| Interactive exploration | Cell-by-cell execution with context |
| Teaching | Step-by-step with visible output |
But these aren’t typical runbook use cases.
What Runbooks Need
Runbooks have different requirements:
| Requirement | Markdown | Notebooks |
|---|---|---|
| Emergency readability | ✅ | ⚠️ |
| Version control | ✅ | ⚠️ |
| Low tooling requirements | ✅ | ❌ |
| Shell-first workflows | ✅ | ⚠️ |
| Team adoption | ✅ | ⚠️ |
| Works over SSH | ✅ | ❌ |
Making Markdown Executable
The knock against markdown: it’s static. Commands are just text.
But this is a tooling problem, not a format problem.
The Traditional Approach
# Restart Service
## Stop the service
```bash
systemctl stop myservice
```
## Wait for cleanup
```bash
sleep 10
```
## Start the service
```bash
systemctl start myservice
```
To execute: copy each block, paste into terminal, run.
The Executable Approach
Same markdown, but with tooling that adds execution:
# Restart Service
## Stop the service
```bash
systemctl stop myservice
```
[Run] ← Click to execute, output appears below
## Wait for cleanup
```bash
sleep 10
```
[Run]
## Start the service
```bash
systemctl start myservice
```
[Run]
The format stays readable. The tooling adds capability.
Markdown Runbook Best Practices
Structure for Clarity
# [Service] [Procedure] Runbook
## Overview
Brief description of when to use this runbook.
## Prerequisites
- Access to production cluster
- kubectl configured
## Steps
### Step 1: Verify Current State
```bash
kubectl get pods -l app=service
```
### Step 2: Apply Change
```bash
kubectl apply -f config.yaml
```
### Step 3: Verify Success
```bash
kubectl rollout status deployment/service
```
## Troubleshooting
Common issues and solutions.
## Rollback
How to undo if something goes wrong.
Use Fenced Code Blocks with Language
```bash
# This gets syntax highlighting
kubectl get pods
```
```sql
-- This does too
SELECT * FROM users;
```
Add Context Around Commands
## Check pod logs
This shows the last 50 lines of logs. Look for ERROR or WARN patterns.
```bash
kubectl logs -l app=api --tail=50
```
**Expected output**: Normal startup messages
**If you see**: Connection refused → Check database connectivity
Make Commands Copy-Paste Ready
# Don't add prompts
```bash
kubectl get pods # Good
```
```bash
$ kubectl get pods # Bad - $ gets copied
```
Stew: Markdown Execution Done Right
Stew keeps your runbooks as plain markdown while adding execution:
- Write in any editor
- Store in any git repo
- Read with any tool
- Execute with Stew
The format you trust + the execution you need.
# Your markdown stays clean
```bash
kubectl get pods
```
Stew adds the run button. Your runbook stays portable.
Join the waitlist and make your markdown executable.