<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: George Michalakis</title>
    <description>The latest articles on DEV Community by George Michalakis (@thegm26).</description>
    <link>https://dev.to/thegm26</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3823163%2Fad37f758-bf59-43eb-8bb7-537b099906ff.jpeg</url>
      <title>DEV Community: George Michalakis</title>
      <link>https://dev.to/thegm26</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thegm26"/>
    <language>en</language>
    <item>
      <title>K8s: The Affinity Club</title>
      <dc:creator>George Michalakis</dc:creator>
      <pubDate>Sun, 16 Aug 2026 10:26:02 +0000</pubDate>
      <link>https://dev.to/thegm26/k8s-the-affinity-club-4c2</link>
      <guid>https://dev.to/thegm26/k8s-the-affinity-club-4c2</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you have fiddled with Kubernetes long enough to worry about which node your Pods are actually running on, you can probably relate.&lt;/p&gt;

&lt;p&gt;First: what does &lt;em&gt;affinity&lt;/em&gt; even mean?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Affinity:&lt;/strong&gt; a strong feeling that you understand or like someone or something; a close relationship between people or things with similar qualities.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Okay. Fair enough.&lt;/p&gt;

&lt;p&gt;In Kubernetes, affinity is a family of scheduling rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node affinity&lt;/strong&gt; defines hard requirements and soft preferences for which nodes can run a Pod.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pod affinity&lt;/strong&gt; lets us place a Pod near other Pods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pod anti-affinity&lt;/strong&gt; lets us keep Pods away from other Pods.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, loosely, if I were a Pod, I would say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I have an affinity for being near or away from these Pods.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But why care?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At the end of the day, I can always scale to more replicas. I’m safe… right?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fap7g99un2wouabo5li9e.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fap7g99un2wouabo5li9e.gif" alt="Asking are you sure about that?" width="250" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Let’s see it in action
&lt;/h3&gt;

&lt;p&gt;Let’s take the worst-case scenario.&lt;/p&gt;

&lt;p&gt;Imagine a cluster with two worker nodes. We have a critical service, but all of its Pods happen to be scheduled on &lt;code&gt;worker-1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Traffic grows, so we scale the Deployment. But every new replica still lands on &lt;code&gt;worker-1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpeyzeipzp7yq3wnsvq6r.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpeyzeipzp7yq3wnsvq6r.gif" alt="Lab 01 placement visual" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After scaling, two things become obvious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;worker-1&lt;/code&gt; is becoming stressed.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;worker-1&lt;/code&gt; fails, every replica fails with it—and the application is down.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scheduler is not random, but without a placement rule, &lt;strong&gt;it has no obligation&lt;/strong&gt;* to spread our replicas across workers.&lt;/p&gt;

&lt;p&gt;In this scenario, &lt;code&gt;worker-2&lt;/code&gt; is right there... &lt;strong&gt;empty&lt;/strong&gt;..&lt;/p&gt;

&lt;p&gt;Ok. Can we force Kubernetes to keep replicas of the same service on different workers?&lt;/p&gt;




&lt;p&gt;Yes. That is exactly what pod anti-affinity is for.&lt;/p&gt;

&lt;p&gt;Below, we apply pod anti-affinity to our critical service’s Deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before: no placement rule
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;drainlab&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.27-alpine&lt;/span&gt;
          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;250m&lt;/span&gt;
              &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;128Mi&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  After: hard pod anti-affinity
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;drainlab&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;affinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;-Magic starts to happen here&lt;/span&gt;
        &lt;span class="na"&gt;podAntiAffinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
          &lt;span class="na"&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;labelSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
              &lt;span class="na"&gt;topologyKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes.io/hostname&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;-finishes here&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.27-alpine&lt;/span&gt;
          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;250m&lt;/span&gt;
              &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;128Mi&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the scheduler:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;requiredDuringScheduling “A new &lt;code&gt;app: web&lt;/code&gt; Pod cannot be scheduled onto a node that already runs another matching &lt;code&gt;app: web&lt;/code&gt; Pod.”&lt;/p&gt;

&lt;p&gt;If every eligible worker already runs one, the new Pod stays &lt;code&gt;Pending&lt;/code&gt; rather than placing two replicas on the same node.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;More specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;requiredDuringScheduling&lt;/code&gt;: if no valid worker exists, the new Pod stays &lt;code&gt;Pending&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IgnoredDuringExecution&lt;/code&gt;: Kubernetes does not evict an already-running Pod merely because later changes violate the anti-affinity condition.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;topologyKey: kubernetes.io/hostname&lt;/code&gt;: each worker node is treated as a separate failure domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwzqz3wjpix4gr2wac7o2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwzqz3wjpix4gr2wac7o2.gif" alt="01lab-02" width="719" height="353"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;But wait… if I have two eligible worker nodes and need four replicas, do the other two remain &lt;code&gt;Pending&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwabmxep9e7ahawrn4et3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwabmxep9e7ahawrn4et3.gif" alt="Shocked Cat" width="220" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before anti-affinity, all four Pods could run on &lt;code&gt;worker-1&lt;/code&gt;, even though one node failure would take down the entire service.&lt;/p&gt;

&lt;p&gt;Now, Kubernetes protects us from that false sense of safety but hard pod anti-affinity &lt;strong&gt;limits&lt;/strong&gt; us to one matching Pod per worker.&lt;/p&gt;

&lt;p&gt;So we gained failure isolation, but we gave up that &lt;strong&gt;easy&lt;/strong&gt; scaling?&lt;/p&gt;

&lt;p&gt;No, This is where topology spread constraints come in.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;*Kubernetes may already prefer to spread Pods using soft scheduling preferences. Exact placement depends on available resources and scheduler configuration. This example demonstrates why relying on a preference is different from declaring a hard availability rule.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Labs / Visualizations for this article can be found &lt;a href="https://github.com/Thegm26/drainlab-k8" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>sre</category>
    </item>
    <item>
      <title>Save yourself from Architectural Amnesia: ADRs</title>
      <dc:creator>George Michalakis</dc:creator>
      <pubDate>Sat, 14 Mar 2026 02:03:07 +0000</pubDate>
      <link>https://dev.to/thegm26/save-yourself-from-architectural-amnesia-adrs-506p</link>
      <guid>https://dev.to/thegm26/save-yourself-from-architectural-amnesia-adrs-506p</guid>
      <description>&lt;h2&gt;
  
  
  When you see it...
&lt;/h2&gt;

&lt;p&gt;From smaller teams to larger ones, from the senior SWE managing the entire backlog to EOs, POs, and SMs juggling overlapping responsibilities, I have often found myself in meetings or PR discussions nitpicking scope details and suddenly realizing one of two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I disagree with the broader implementation picture.&lt;/li&gt;
&lt;li&gt;I do not even remember whether I agreed with it in the first place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second one is worse.&lt;/p&gt;

&lt;p&gt;For context, I am working in a SAFe Scrum setup, so these roles and handoffs are a very real part of day-to-day delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Half-measures (Best-case scenario)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Uhh, there is a Confluence page about this decision. We had a meeting for that."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Clicks the link.&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Scans a page with 10 comments&lt;/em&gt; (8 resolved, 2 still open).&lt;br&gt;&lt;br&gt;
OK, this makes a bit more sense now.&lt;/p&gt;

&lt;p&gt;Still, I do not remember what we said. I do not remember how long it took, and it probably took longer than it should have.&lt;/p&gt;

&lt;p&gt;Even in this best-case scenario, I am still left with two problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I do not remember the back-and-forth, and some of it was probably important.&lt;/li&gt;
&lt;li&gt;If I now have a different opinion, I really do not want to reopen the topic. That usually means more meetings, more pages, and more comments. Atlassian may be doing a great job with version control, but I do not want to be stuck restoring version 23 of a Confluence page that nobody will care about two weeks later.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Give credit where it is due
&lt;/h2&gt;

&lt;p&gt;That does not mean Confluence is useless.&lt;/p&gt;

&lt;p&gt;Architects, PMs, and business stakeholders need version control, comments, and discussions too. You cannot expect a product owner:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To log in to GitHub every time they want to sync with the architect on whether the team is migrating the DB.&lt;/li&gt;
&lt;li&gt;To align on the confidence level of PI objectives with different POs inside a pull request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Confluence has its place.&lt;/p&gt;

&lt;p&gt;But at the same time, nobody wants to spend all of this just to discuss something like a new or existing PR label:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 minutes to raise it during the daily stand-up&lt;/li&gt;
&lt;li&gt;5 minutes to schedule a call&lt;/li&gt;
&lt;li&gt;20 minutes to prepare a page explaining the current state and the proposal&lt;/li&gt;
&lt;li&gt;35 minutes for the meeting itself&lt;/li&gt;
&lt;li&gt;20 minutes for the notes afterward&lt;/li&gt;
&lt;li&gt;20 minutes updating the page with the latest comments and decisions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And yes, those numbers are completely realistic.&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture Decision Records
&lt;/h2&gt;

&lt;p&gt;This is where ADRs help.&lt;/p&gt;

&lt;p&gt;An ADR is a lightweight record of an important technical decision: why it was needed, what options were considered, what was chosen, and what tradeoffs came with that choice.&lt;/p&gt;

&lt;p&gt;At its simplest, it is just a markdown file living in your repo, somewhere like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docs/
  adrs/
    0001-use-topic-a.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is basically it.&lt;/p&gt;

&lt;p&gt;A small document, committed with the code, with sections such as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Context: what problem are we trying to solve?&lt;/li&gt;
&lt;li&gt;Decision: what did we choose?&lt;/li&gt;
&lt;li&gt;Alternatives considered: what were the other realistic options?&lt;/li&gt;
&lt;li&gt;Consequences: what do we gain, and what do we give up?&lt;/li&gt;
&lt;li&gt;Status: proposed, accepted, superseded, deprecated&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nothing fancy. Just enough to preserve the reasoning behind a technical decision close to the codebase where engineers will actually look for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic rules
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Keep ADRs for technical topics that are likely to be searched while someone is working in the repo. If you used a specific pattern, tool, or extension, I would much rather &lt;code&gt;Ctrl+F&lt;/code&gt; the repo and find the reasoning in the docs than dig through a GitHub page or old meeting notes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make the person opening the PR the driver of the discussion. They should gather feedback, collect comments, help the team converge on a decision, and eventually merge or close the PR with the ADR alongside the change.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do not optimize for unanimous agreement. Optimize for a clear decision with explicit tradeoffs and enough context that the next person can understand why it happened.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the real value: not perfect documentation, but recorded reasoning close to the codebase.&lt;/p&gt;

&lt;p&gt;If you want a good collection of ADR templates, look &lt;a href="https://github.com/joelparkerhenderson/architecture-decision-record" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PS1: Use this as a starting point for introducing ADRs to your team.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PS2: An agent helped with syntax refactoring. The expressions, the main writing, the flow and the pain is mine :)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>adr</category>
      <category>documentation</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
