<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Postmortem on Dexome</title>
        <link>https://blog.dexome.com/tags/postmortem/</link>
        <description>Recent content in Postmortem on Dexome</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language>
        <lastBuildDate>Fri, 05 Jun 2026 00:00:00 +0530</lastBuildDate><atom:link href="https://blog.dexome.com/tags/postmortem/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>How I Write Incident Notes for My Homelab</title>
        <link>https://blog.dexome.com/post/operational-postmortems-future-self/</link>
        <pubDate>Fri, 05 Jun 2026 00:00:00 +0530</pubDate>
        
        <guid>https://blog.dexome.com/post/operational-postmortems-future-self/</guid>
        <description>&lt;p&gt;I keep short incident notes for problems in my homelab. I normally write them
after the service is stable and before closing the terminals used during the
investigation.&lt;/p&gt;
&lt;p&gt;Shell history can show the commands I ran, but it does not explain which output
ruled out the firewall, why one repair did not work or which configuration change
was the permanent fix. By the next day, those details are already harder to write
down correctly.&lt;/p&gt;
&lt;h2 id=&#34;start-with-the-symptom-cause-and-fix&#34;&gt;Start with the symptom, cause and fix
&lt;/h2&gt;&lt;p&gt;I force the incident into a problem-to-outcome sentence:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Off-subnet Traefik ingress timed out because its multi-homed container replied
through the Internet bridge; a lower-metric default route through the ingress
VLAN restored symmetric routing.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;This gives me enough information to decide later whether the complete note is
related to a new problem.&lt;/p&gt;
&lt;p&gt;A weak summary sounds like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Fixed Traefik networking.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;It is short but cannot route a future investigation. It does not distinguish
DNS failure, backend reachability, stale ARP, firewall policy, or asymmetric
routing.&lt;/p&gt;
&lt;p&gt;If I can only write “fixed Traefik networking,” I may have restored the service
without finding the actual cause.&lt;/p&gt;
&lt;h2 id=&#34;the-format-i-use&#34;&gt;The format I use
&lt;/h2&gt;&lt;p&gt;The useful reader is tired, under time pressure, and staring at a similar but
not necessarily identical symptom. That reader needs discriminating evidence,
not a polished chronology of every command.&lt;/p&gt;
&lt;p&gt;My note structure is small:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gh&#34;&gt;# Specific incident title
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gh&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;## Symptom
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;&lt;/span&gt;What users and monitoring observed.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;## Evidence
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;&lt;/span&gt;The few checks that separated plausible causes.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;## Root cause
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;&lt;/span&gt;The mechanism, not only the broken component.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;## Fix
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;&lt;/span&gt;The exact durable change and any one-time repair.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;## Verify and undo
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gu&#34;&gt;&lt;/span&gt;How to prove the fix, and how to back it out.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;figure class=&#34;article-diagram&#34;&gt;
    &lt;pre class=&#34;mermaid&#34;&gt;
flowchart TB
	S[Symptom] --&gt; H[Competing hypotheses]
	H --&gt; E[Discriminating evidence]
	E --&gt; R[Root cause mechanism]
	R --&gt; F[Durable fix]
	F --&gt; V[Verification and rollback]
&lt;/pre&gt;
    &lt;figcaption&gt;A useful incident note compresses the investigation around the evidence that changed the decision.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Most of my notes are short. The important part is keeping the evidence which
changed the diagnosis.&lt;/p&gt;
&lt;h2 id=&#34;example-from-radarr-and-mergerfs&#34;&gt;Example from Radarr and mergerfs
&lt;/h2&gt;&lt;p&gt;Radarr could not replace a movie after Copyparty renamed its directory. The
directory was owned by Copyparty&amp;rsquo;s UID and the shared media group, but group
write was missing.&lt;/p&gt;
&lt;p&gt;The first diagnosis was correct: change the directory from &lt;code&gt;2755&lt;/code&gt; to &lt;code&gt;2775&lt;/code&gt;.
The first repair was incomplete because I changed only the &lt;code&gt;btank&lt;/code&gt; backing path.
Radarr still failed.&lt;/p&gt;
&lt;p&gt;The visible path was a mergerfs union. A duplicate directory under the &lt;code&gt;btwo&lt;/code&gt;
branch still had mode &lt;code&gt;2755&lt;/code&gt;, and mergerfs exposed that metadata. Repairing only
the branch containing the media file was not enough.&lt;/p&gt;
&lt;p&gt;A poor incident note would say:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Radarr permissions fixed with chmod 2775.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The operational note says:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Copyparty-created 0755 directories blocked Radarr replacement through mergerfs.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Repair every raw backing branch, never only the merged view, then enforce
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Copyparty chmod_d 775 and chmod_f 664 for future entries.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The second note records both the existing filesystem repair and the Copyparty
configuration required for new files and directories.&lt;/p&gt;
&lt;h2 id=&#34;failed-attempts-worth-keeping&#34;&gt;Failed attempts worth keeping
&lt;/h2&gt;&lt;p&gt;An incident can contain dozens of checks. Most do not deserve permanent space.
I keep a failed attempt when it explains a recurring trap:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;root privileges did not bypass Netdata&amp;rsquo;s systemd device cgroup;&lt;/li&gt;
&lt;li&gt;a stopped Podman container remained associated with its network;&lt;/li&gt;
&lt;li&gt;a healthy container did not prove off-subnet return routing;&lt;/li&gt;
&lt;li&gt;setting a flow to disabled did not stop its persisted realtime subscription;&lt;/li&gt;
&lt;li&gt;a synthetic shell respected the umask while the real s6-supervised process
reset it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I omit typos and checks which did not add any information. I keep a failed attempt
when it explains why a reasonable fix did not change the result.&lt;/p&gt;
&lt;h2 id=&#34;keep-the-diagnostic-sequence&#34;&gt;Keep the diagnostic sequence
&lt;/h2&gt;&lt;p&gt;There is the incident timeline: what happened in production. There is also the
reasoning timeline: which evidence changed the hypothesis.&lt;/p&gt;
&lt;figure class=&#34;article-diagram&#34;&gt;
    &lt;pre class=&#34;mermaid&#34;&gt;
flowchart TB
	subgraph INCIDENT[Production timeline]
		I1[Change activated] --&gt; I2[Service degraded] --&gt; I3[Service restored]
	end
	subgraph REASONING[Reasoning timeline]
		R1[Suspect firewall] --&gt; R2[Packet arrives on ingress]
		R2 --&gt; R3[Route lookup selects wrong gateway]
		R3 --&gt; R4[Pin lower-metric ingress route]
	end
	I2 -. starts .-&gt; R1
	R4 -. produces .-&gt; I3
&lt;/pre&gt;
    &lt;figcaption&gt;Operational chronology and diagnostic reasoning overlap, but the note should preserve the decisions, not every event.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;For future debugging, &lt;code&gt;packet arrives&lt;/code&gt; and &lt;code&gt;route lookup selects wrong gateway&lt;/code&gt;
are more useful than the exact minute when each &lt;code&gt;curl&lt;/code&gt; command ran.&lt;/p&gt;
&lt;h2 id=&#34;write-the-note-before-cleaning-up&#34;&gt;Write the note before cleaning up
&lt;/h2&gt;&lt;p&gt;I start the note after service is stable but before deleting scratch files,
closing logs, or relying on memory. The fastest workflow is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write the one-line outcome.&lt;/li&gt;
&lt;li&gt;Paste only the decisive errors and commands.&lt;/li&gt;
&lt;li&gt;Explain the mechanism in plain language.&lt;/li&gt;
&lt;li&gt;Separate temporary recovery from the permanent fix.&lt;/li&gt;
&lt;li&gt;Add verification and rollback.&lt;/li&gt;
&lt;li&gt;Remove secrets and irrelevant identifiers.&lt;/li&gt;
&lt;li&gt;Link the note from a small topic index.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I can edit the wording later. Recreating the decisive command output is more
difficult after the containers, logs and temporary files have changed.&lt;/p&gt;
&lt;h2 id=&#34;record-the-recovery-and-permanent-fix-separately&#34;&gt;Record the recovery and permanent fix separately
&lt;/h2&gt;&lt;p&gt;Several incidents taught me to record two fixes explicitly.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Recovery action&lt;/th&gt;
          &lt;th&gt;Durable correction&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Clear a stale neighbor entry&lt;/td&gt;
          &lt;td&gt;Pin the macvlan MAC across recreations&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Repair existing directories&lt;/td&gt;
          &lt;td&gt;Change creation policy and inherited ACLs&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Restart Netdata&lt;/td&gt;
          &lt;td&gt;Configure collector autodetection retry&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Delete a stuck KV key&lt;/td&gt;
          &lt;td&gt;Replace TTL presence with timestamp comparison&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Manually restart a container&lt;/td&gt;
          &lt;td&gt;Correct its systemd dependency or health contract&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This prevents a one-time recovery command from being mistaken for the permanent
configuration change.&lt;/p&gt;
&lt;h2 id=&#34;add-it-to-the-topic-index&#34;&gt;Add it to the topic index
&lt;/h2&gt;&lt;p&gt;A folder full of excellent postmortems still fails if nobody knows what is in
it. I keep a routing index grouped by service. Each note contributes its
problem-to-outcome sentence.&lt;/p&gt;
&lt;figure class=&#34;article-diagram&#34;&gt;
    &lt;pre class=&#34;mermaid&#34;&gt;
flowchart LR
	N[Short dated incident note] --&gt; I[One-line entry in topic index]
	Q[Future symptom] --&gt; I
	I --&gt;|summary matches| N
	N --&gt; A[Apply prior check or avoid prior mistake]
&lt;/pre&gt;
    &lt;figcaption&gt;The cost of a postmortem includes writing it and finding it again; a compact index reduces the second cost.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I use full-text search for exact errors. The index helps when the new symptom is
similar but does not contain the same error message.&lt;/p&gt;
&lt;h2 id=&#34;keep-old-notes-but-mark-them-superseded&#34;&gt;Keep old notes but mark them superseded
&lt;/h2&gt;&lt;p&gt;Operational knowledge changes. A note may describe a temporary architecture, a
version-specific bug, or a test later disproved in production.&lt;/p&gt;
&lt;p&gt;I do not silently rewrite the old conclusion. I mark it superseded and link it
to the replacement; the replacement links back. That preserves why the earlier
decision was reasonable while making the current instruction obvious.&lt;/p&gt;
&lt;p&gt;Periodically, I scan for stale statuses, contradictory guidance, duplicated
incidents, broken links, and notes that should be promoted into stable service
documentation. A tiny coverage script checks that every dated note appears in
the index. Humans still decide whether the summaries are true.&lt;/p&gt;
&lt;h2 id=&#34;when-i-create-a-note&#34;&gt;When I create a note
&lt;/h2&gt;&lt;p&gt;I write an operational note when at least one of these is true:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the symptom had more than one plausible cause;&lt;/li&gt;
&lt;li&gt;the winning check was non-obvious;&lt;/li&gt;
&lt;li&gt;a reasonable attempted fix made no difference;&lt;/li&gt;
&lt;li&gt;recovery and durable correction were different;&lt;/li&gt;
&lt;li&gt;state outside the visible configuration mattered;&lt;/li&gt;
&lt;li&gt;the same mistake could affect another service;&lt;/li&gt;
&lt;li&gt;rollback required knowledge not encoded in the change itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Routine changes do not need an incident note. I create one when the investigation
found something which is not obvious from the final configuration.&lt;/p&gt;
&lt;p&gt;Once the fix is validated, I write the short note and add its one-line summary
to the index. That is normally enough for the next investigation to start from
the useful check instead of repeating the complete incident.&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
