Post-incident reviews (PIRs) and operational runbooks for the pgmac homelab Kubernetes infrastructure.
Published at https://incidents.pgmac.net.au/
- Incidents — PIRs documenting what went wrong, why, and how it was fixed
- Runbooks — Step-by-step recovery procedures for known failure modes
- Templates — PIR and runbook templates, with section-by-section guidance
- Decisions — ADRs covering how the site itself works
Requires mise and Python 3.13.
mise run install # create venv and install dependencies
mise run serve # serve at http://localhost:8000 with live reload
mise run build # build static site
mise run build-strict # strict build (matches CI)Live incidents are triaged and PIRs/runbooks are authored with Claude Code / OpenCode skills from pgmac-net/claude-plugins.
| Skill | Purpose |
|---|---|
/start-incident |
Work a live incident: open a tracking issue immediately, triage via Nagios/Slack, match a runbook, gate every mutating action on confirmation — hands off to /create-pir on resolution |
/create-pir |
Generate a post-incident review: Infinite How's analysis, runbook evaluation, GitHub Issues, commit + PR. Reads a /start-incident tracking issue as its primary source when one exists |
/pickup-ticket |
Work a GitHub Issue end-to-end: read, grill, plan, implement, PR, document |
/grilling |
Stress-test plans and decisions one question at a time |
/domain-modeling |
Build and sharpen domain model: glossary, ADRs, terminology |
Why do I make all of this public? A few reasons:
- I enjoy working in public.
- I'm challenged by working in public. It forces me to produce work I am prepared to show.
- Helps future-me by reducing the number of assumptions and assumed knowledge I sometimes(/usually) leave.
- Hopefully it helps other people not only pick up some SRE knowledge, but also inspire some to work in public and share their experiences, too.
- Name:
YYYY-MM-DD-brief-description.md - Location:
src/incidents/— the nav discovers it automatically, newest-first - Frontmatter must carry
title,date,severity(P1–P4),duration, and normallyresolutionandimpact.main.pyfails the build on a missing field or an unknown severity. Never usestatus:— it is reserved by Material; useresolution: - Add a row to the top of
src/incidents/index.md(newest-first), with severity as a badge:<span class="sev sev--p2">P2</span> - Follow
src/doc-templates/pir-template.md— it documents the full frontmatter contract. Do not write the Date/Duration/Severity/Status block into the body; it is rendered - Use the
/create-pirskill to automate the full flow — see Skills above
- Name:
<service>-<failure-description>.md - Location:
src/runbooks/ - Add a row to
src/runbooks/index.md - Follow
src/doc-templates/runbook-template.md(simple or multi-mode pattern) - Cross-link from the PIR that documented the failure
- The
/create-pirskill evaluates runbook needs during PIR generation — see Skills above
validate.yml— strict build of both configs on every PRdeploy.yml— builds and deploys to GitHub Pages on merge tomain
mkdocs-base.yml holds everything shared. mkdocs.yml (internal) and
incidents-mkdoc.yml (public) are INHERIT plus their own site_url/site_dir, so a
theme change made once applies to both. Neither declares a nav — pages are discovered
from src/ and ordered by .nav.yml files.
main.py generates the home page's recent-incidents list and section counts from PIR
frontmatter, and validates that frontmatter at build time. Macro rendering is opt-in
(render_macros: true), because two documents quote Kubernetes errors containing literal
{{ }} that Jinja would otherwise try to evaluate.
Rationale for these choices is recorded in src/decisions.md.