Build and test incident response playbooks for your team. Covers severity classification, communication templates, war room procedures, and post-mortem frameworks.
Incident response playbooks are structured, step-by-step guides that define how teams respond to specific types of incidents. They are critical during high-pressure events—when engineers, security analysts, and operations staff must act quickly, consistently, and correctly under stress. A playbook ensures that no critical step is missed, from initial detection to post-incident review, and it becomes the shared language across teams when the lights go out.
An effective playbook begins by clearly categorizing incidents. Each category should map to a distinct response workflow. Use a simple, consistent taxonomy: Severity, Impact, and Triggering Event. Define severity levels as *Minor*, *Major*, *Critical*, and *Catastrophic*, with clear criteria.
For example:
Each incident type should be associated with a unique identifier: INC-2023-001, INC-2023-002, etc. Use this ID in all communications, alerts, and documentation.
Define the *triggering event* as the signal that activates the playbook. This is typically a metric threshold, a log pattern, or a specific alert condition. For example:
{
"alert_name": "High Error Rate in Checkout Service",
"trigger": "rate(errors, 10m) > 0.1",
"conditions": [
{
"metric": "http.errors",
"window": "10m",
"threshold": 0.1,
"aggregation": "rate",
"comparison": "above"
},
{
"log_query": "service:checkout AND level:error",
"min_events": 50,
"time_window": "5m"
}
]
}
Use a standard alerting format across systems. Alert definitions should be stored in version-controlled files, such as alerts/checkout-service.yaml:
name: checkout-service-high-error-rate
description: >-
Detects increased error rate in the checkout service.
Triggers when error rate exceeds 10% over 10 minutes.
severity: Critical
labels:
team: payments
service: checkout
incident_type: service-outage
for: 10m
query: |
rate(
http_errors{service="checkout"}[10m]
) > 0.1
notifications:
- webhook: https://webhook.example.com/incident
template: |
Incident: {{.Incident.ID}}
Service: {{.Service}}
Error Rate: {{.Value}}%
Duration: {{.Duration}}
Each playbook should follow a defined lifecycle: *Detection*, *Triage*, *Containment*, *Eradication*, *Recovery*, *Post-Mortem*. Each phase contains a checklist of tasks, responsibilities, and expected outcomes.
The first action after an incident is detected is to confirm the event using a canonical script. Use a check.incident.sh script that runs on all relevant systems:
#!/bin/bash
set -euo pipefail
readonly INCIDENT_ID="$1"
readonly SERVICE_NAME="$2"
readonly ALERT_SOURCE="$3"
echo "🔍 Validating incident: $INCIDENT_ID"
echo " Service: $SERVICE_NAME"
echo " Source: $ALERT_SOURCE"
# Check if service is up
if ! curl -sf http://$SERVICE_NAME:8080/health; then
echo "❌ Service is down"
exit 1
fi
# Check logs for recent errors
if ! psql -c "
SELECT count(*) FROM logs
WHERE service = '$SERVICE_NAME'
AND level = 'error'
AND created_at > now() - interval '10 minutes'
" -t -A -F -q; then
echo "❌ High error rate in logs"
fi
# Check queue backlog
if ! docker exec -i kafka-broker bin/kafka-run-class.sh kafka.tools.GetOffsetShell \
--broker-list localhost:9092 \
--topic checkout-events \
--time -1; then
echo "❌ Queue backlog is growing"
fi
echo "✅ Incident validated"
This script runs on every incident detection. It is executed via a CI/CD job or a runner service, and its output is logged and stored in a shared directory.
Once confirmed, the incident enters triage. Assign a primary responder and a secondary. Use a standard triage template:
# Incident: INC-2023-001
## Triage Summary
- **Service**: Checkout
- **Severity**: Critical
- **Detected**: 2023-04-15T08:32:14Z
- **Primary Responder**: [email protected]
- **Secondary Responder**: [email protected]
- **Initial Observations**:
- HTTP 5xx rate: 32%
- 180 errors in last 10 minutes
- Kafka queue backlog: 12,000 messages
- **Next Steps**:
- [ ] Verify database connection pool exhaustion
- [ ] Check recent deployments
- [ ] Run `check.incident.sh` on all instances
The triage phase is documented in a shared document, updated in real time. Use a markdown file at playbooks/incidents/INC-2023-001.md.
Containment aims to stabilize the incident and prevent further degradation. Use a contain.incident.sh script:
#!/bin/bash
# contain.incident.sh
set -euo pipefail
readonly INCIDENT_ID="$1"
readonly THRESHOLD="${2:-0.15}"
echo "🛡️ Containing incident: $INCIDENT_ID"
# Scale up service
kubectl scale deployment checkout --replicas=12 -n checkout
# Enable circuit breaker in API gateway
curl -X PUT http://api-gateway:8080/circuit-breakers/checkout \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"threshold": 0.15,
"timeout": 10000,
"failureThreshold": 5
}'
# Update alert to reduce noise
curl -X PATCH https://alertmanager.example.com/api/v1/alerts \
-H "Content-Type: application/json" \
-d '{
"matchers": [
{ "name": "incident_id", "value": "'$INCIDENT_ID'" }
],
"labels": {
"state": "containing",
"severity": "warning"
},
"annotations": {
"description": "Incident in containment phase"
}
}'
# Log containment action
echo "Containment applied at $(date)" >> /var/log/incidents/$INCIDENT_ID.log
This script is idempotent and runs on each incident. It can be triggered manually or automated via a webhook.
Eradication focuses on root cause analysis. Use a standard query to gather logs, metrics, and traces.
# Gather all data for incident
export INCIDENT_ID="INC-2023-001"
export START_TIME=$(date -Iseconds -d "10 minutes ago")
# Fetch logs from all services
for service in checkout payments inventory; do
psql -c "
COPY (
SELECT * FROM logs
WHERE service = '$service'
AND created_at BETWEEN '$START_TIME' AND now()
ORDER BY created_at
) TO '/tmp/incidents/$INCIDENT_ID/$service-logs.csv'
WITH CSV HEADER;
" -q
done
# Export metrics from Prometheus
curl -s "http://prometheus:9090/api/v1/query?query=rate(http_errors{service=\"checkout\"}[10m])&time=$(date -Iseconds)" \
-o "/tmp/incidents/$INCIDENT_ID/metrics.json"
# Create a trace bundle
docker exec -i jaeger-query bin/jaeger-cli trace \
--service=checkout \
--start-time="$START_TIME" \
--end-time=$(date -Iseconds) \
--format=zip \
> "/tmp/incidents/$INCIDENT_ID/trace.zip"
This bundle is uploaded to a shared storage (e.g., S3, NFS) and linked in the incident document.
Recovery restores full functionality. Use a recover.incident.sh script:
#!/bin/bash
# recover.incident.sh
set -euo pipefail
readonly INCIDENT_ID="$1"
echo "🔄 Recovering incident: $INCIDENT_ID"
# Roll back to stable version
kubectl set image deployment checkout \
checkout=checkout:1.2.3 \
-n checkout
# Disable circuit breaker
curl -X PUT http://api-gateway:8080/circuit-breakers/checkout \
-H "Content-Type: application/json" \
-d '{
"enabled": false
}'
# Verify recovery
if ! curl -sf http://checkout:8080/health; then
echo "❌ Health check failed after recovery"
exit 1
fi
# Send recovery confirmation
curl -X POST https://webhook.example.com/incident \
-H "Content-Type: application/json" \
-d '{
"incident_id": "'$INCIDENT_ID'",
"status": "recovered",
"message": "Service recovered successfully"
}'
echo "✅ Incident $INCIDENT_ID recovered"
After recovery, conduct a post-mortem. Use a standard template with the following sections:
Store this in postmortems/INC-2023-001.md. Use a checklist to ensure consistency:
- [ ] Incident title and ID included
- [ ] Timeline spans from detection to recovery
- [ ] Root cause identified and documented
- [ ] Actions taken listed with owners
- [ ] Lessons learned in bullet form
- [ ] Follow-up tasks with due dates
- [ ] Link to trace bundle, logs, and metrics
A playbook is not static. It must be reviewed and updated after every incident. Create a playbook-review.sh script that runs after each incident:
#!/bin/bash
# playbook-review.sh
set -euo pipefail
readonly INCIDENT_ID="$1"
echo "📝 Reviewing playbook for $INCIDENT_ID"
# Identify missing steps
if ! grep -q "Check database connection pool" \
playbooks/incidents/$INCIDENT_ID.md; then
echo "⚠️ Missing step: Check database connection pool"
echo " Add to playbook: 'check.db.pool.sh'"
fi
# Check for outdated scripts
for script in check.incident.sh contain.incident.sh; do
if ! git diff --exit-code origin/main -- "scripts/$script"; then
echo "🔄 Script $script has changes not in main"
fi
done
# Update playbook version
git add .
git commit -m "Update playbook for $INCIDENT_ID"
git push origin main
This script runs after every incident. It ensures the playbook evolves with experience.
incidents queue (e.g., Kafka or RabbitMQ), and a worker consumes this queue to create a new playbook entry.set -euo pipefail to all scripts. Use echo and date in logs for traceability.config/severities.yaml file and validate them automatically: critical:
error_rate: 0.1
duration: 15m
impact: "core"
major:
error_rate: 0.05
duration: 30m
impact: "significant"
# .git/hooks/pre-commit
#!/bin/bash
if ! git log -1 --pretty=format:%s | grep -q "INC-"; then
echo "⚠️ Commit message must include incident ID"
exit 1
fi
detected, triaged, containing, eradicating, recovered, post-mortem.playbook.join.sh to get access to all incident tools, alerting systems, and documentation.A playbook is only as good as its execution. The most effective playbooks are not just documents—they are executable systems, continuously validated and refined by the very incidents they were built to answer.
This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy