<?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: Mike</title>
    <description>The latest articles on DEV Community by Mike (@techiemike).</description>
    <link>https://dev.to/techiemike</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%2F3962837%2Fd01490d0-dea6-4e9f-b226-ea0ef2357113.png</url>
      <title>DEV Community: Mike</title>
      <link>https://dev.to/techiemike</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/techiemike"/>
    <language>en</language>
    <item>
      <title>Passkeys Are the Death of Passwords — What CS Students and Self-Hosters Need to Know Now</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Thu, 23 Jul 2026 02:00:01 +0000</pubDate>
      <link>https://dev.to/techiemike/passkeys-are-the-death-of-passwords-what-cs-students-and-self-hosters-need-to-know-now-4g4f</link>
      <guid>https://dev.to/techiemike/passkeys-are-the-death-of-passwords-what-cs-students-and-self-hosters-need-to-know-now-4g4f</guid>
      <description>&lt;h1&gt;
  
  
  Passkeys Are the Death of Passwords — What CS Students and Self-Hosters Need to Know Now
&lt;/h1&gt;

&lt;p&gt;Passkeys have reached the annoying phase.&lt;/p&gt;

&lt;p&gt;That is usually how you can tell a technology is real.&lt;/p&gt;

&lt;p&gt;This week a Hacker News thread titled &lt;a href="https://hn.algolia.com/?query=Passkeys%20were%20invented%20by%20engineers%20with%20zero%20understanding%20of%20consumer%20brain&amp;amp;tags=story" rel="noopener noreferrer"&gt;"Passkeys were invented by engineers with zero understanding of consumer brain"&lt;/a&gt; blew up. Two years earlier, firstyear's &lt;a href="https://fy.blackhats.net.au/blog/2024-04-26-passkeys-a-shattered-dream/" rel="noopener noreferrer"&gt;"Passkeys: A shattered dream"&lt;/a&gt; did even more damage to the happy marketing story. That post is worth reading because it is not anti-crypto nonsense. It is a technical complaint from someone who actually builds WebAuthn software.&lt;/p&gt;

&lt;p&gt;I think both threads are right in the same way. The security model is better. The rollout has been messy.&lt;/p&gt;

&lt;p&gt;I use Bitwarden passkeys daily. I also run an Authentik-based login layer in my homelab, with the same nginx auth pattern I have used elsewhere. So this is not a theory post for me. It is the next layer after the transport side I covered in &lt;a href="https://www.techiemike.com/tls-certificates-internal-services-https-homelab/" rel="noopener noreferrer"&gt;TLS Certificates for Internal Services&lt;/a&gt;. Last month was the padlock on the wire. This is the proof of who is at the keyboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a passkey actually is
&lt;/h2&gt;

&lt;p&gt;A passkey is not magic. It is a public-private key pair created on your device.&lt;/p&gt;

&lt;p&gt;The server stores the public key. Your phone, laptop, password manager, or security key keeps the private key. When you log in, the server sends a challenge. Your device signs it with the private key. The server verifies the signature with the public key. That is why this matters so much for teaching. It is Cambridge 9618 Chapter 6 authentication and Chapter 17 asymmetric cryptography turned into a login screen.&lt;/p&gt;

&lt;p&gt;The old password model says, "send me the secret again." The passkey model says, "prove you hold the secret without sending it."&lt;/p&gt;

&lt;p&gt;That difference removes three common password problems immediately.&lt;/p&gt;

&lt;p&gt;First, there is no password for a fake site to phish out of you. Second, there is no password database to leak in the usual way because the server stores a public key, not a reusable shared secret. Third, the biometric step stays local. Touch ID, Face ID, Windows Hello, or a PIN unlocks the private key on your device. The biometric is not what the website receives.&lt;/p&gt;

&lt;p&gt;If you teach A-Level Computer Science, this is one of the cleanest real-world examples you can put in front of a class. Students learn public and private keys, digital signatures, and authentication. Passkeys put all three in one system they already recognise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where passkeys live decides whether they feel brilliant or awful
&lt;/h2&gt;

&lt;p&gt;This is the part vendors do not put on the poster.&lt;/p&gt;

&lt;p&gt;A passkey can live in three places.&lt;/p&gt;

&lt;p&gt;Platform keychains come first. Apple wants iCloud Keychain. Google wants Google Password Manager. Microsoft wants Windows Hello and its own ecosystem. These are convenient until your device mix stops matching the vendor's fantasy of your life.&lt;/p&gt;

&lt;p&gt;Password managers are the second option. Bitwarden's &lt;a href="https://bitwarden.com/help/storing-passkeys/" rel="noopener noreferrer"&gt;Autofill Passkeys&lt;/a&gt; documentation is the reason I take passkeys seriously in everyday use. The passkey sits in the vault, the vault syncs across devices, and the browser or mobile app can autofill it like any other credential. That is the escape hatch from platform lock-in.&lt;/p&gt;

&lt;p&gt;The third option is a hardware security key. This is still the cleanest model if you want a separate physical authenticator, but it has real limits. The complaints in &lt;a href="https://github.com/orgs/community/discussions/54450" rel="noopener noreferrer"&gt;GitHub's public passkey beta feedback thread&lt;/a&gt; and in firstyear's post keep coming back to the same point: resident key storage is finite, and older hardware keys can fill up fast.&lt;/p&gt;

&lt;p&gt;If passkeys only work well when you stay obediently inside one vendor's ecosystem, that is not a solved authentication problem. That is a product strategy.&lt;/p&gt;

&lt;p&gt;For homelab users, the best compromise is obvious. Put the passkeys in a password manager you control. I use Bitwarden. If you want the full self-hosted route, that points naturally towards Bitwarden/Vaultwarden-style setups rather than handing your whole login future to Apple, Google, or Microsoft.&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%2Fg48izo208jndgce9hwn3.webp" 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%2Fg48izo208jndgce9hwn3.webp" alt="Passkey storage choices showing a phone, a password manager vault, and a hardware security key beside a homelab dashboard" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The recovery question is the one normal people ask first
&lt;/h2&gt;

&lt;p&gt;Every security enthusiast wants to talk about phishing resistance. Normal people ask a simpler question.&lt;/p&gt;

&lt;p&gt;What happens when I lose my phone?&lt;/p&gt;

&lt;p&gt;That is not a silly question. It is the whole trust test.&lt;/p&gt;

&lt;p&gt;The answer depends on where the passkey lives. Platform sync works if you stay inside the platform family. If your iPhone dies but you still have a Mac or iPad on the same Apple account, the passkey is still there. Password-manager sync works the same way, except it is not tied to one vendor's hardware stack. Bitwarden, 1Password, and similar tools turn passkeys into another item in an encrypted vault rather than another reason to stay loyal to one phone brand.&lt;/p&gt;

&lt;p&gt;There is also cross-device authentication, where a site shows a QR code and your phone completes the sign-in. It exists. It works. It is also slower and clumsier than typing a password when you are on a borrowed machine. I would not sell that flow as elegant.&lt;/p&gt;

&lt;p&gt;Most services still keep a backup route as well. Recovery codes matter. Backup passwords still matter in plenty of deployments. Passkeys are replacing passwords, but the migration is not finished.&lt;/p&gt;

&lt;p&gt;My own rule is simple. I keep passkeys in Bitwarden, I keep recovery codes, and I do not trust a single device with my whole identity. That is the difference between a nice demo and a setup I would actually live with.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ugly part: passkeys still break in very ordinary ways
&lt;/h2&gt;

&lt;p&gt;This is where the critics have a point.&lt;/p&gt;

&lt;p&gt;firstyear's &lt;a href="https://fy.blackhats.net.au/blog/2024-04-26-passkeys-a-shattered-dream/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; documents Apple Keychain deleting passkeys on multiple occasions. The &lt;a href="https://github.com/orgs/community/discussions/67791" rel="noopener noreferrer"&gt;GitHub feedback discussions&lt;/a&gt; are full of the other class of problems: credentials saved on the client but not the server, duplicate entries, Android bugs, full resident-key slots, and devices that need resets before they behave properly.&lt;/p&gt;

&lt;p&gt;That does not mean passkeys are a failure. It means the weakest part of the system is now the account and device plumbing around the cryptography.&lt;/p&gt;

&lt;p&gt;This is the same lesson I made in my &lt;a href="https://www.techiemike.com/cisa-github-leak-homelab-security-lessons/" rel="noopener noreferrer"&gt;CISA GitHub leak write-up&lt;/a&gt;. Security failures are often boring. They are not spectacular cryptographic breaks. They are operational mistakes, storage mistakes, sync mistakes, and recovery mistakes.&lt;/p&gt;

&lt;p&gt;So if you are moving to passkeys, do it with your eyes open.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep recovery codes.&lt;/li&gt;
&lt;li&gt;Do not rely on one device.&lt;/li&gt;
&lt;li&gt;Prefer a cross-platform vault if you use mixed hardware.&lt;/li&gt;
&lt;li&gt;Expect some sites to implement the UX badly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters. Plenty of websites support passkeys in a way that still makes you fight the browser, the phone, and the fallback screen before you are allowed in. The protocol is better than the product design.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like in a homelab
&lt;/h2&gt;

&lt;p&gt;This is the part that matters to me most.&lt;/p&gt;

&lt;p&gt;A lot of homelab services still do not support passkeys directly. Some do OIDC well. Some do it badly. Some have no business being exposed without an auth layer in front of them.&lt;/p&gt;

&lt;p&gt;That is why I keep coming back to identity at the edge. Authentik's &lt;a href="https://docs.goauthentik.io/docs/add-secure-apps/providers/oauth2/" rel="noopener noreferrer"&gt;OAuth2 and OpenID Connect provider docs&lt;/a&gt; and Pocket ID's &lt;a href="https://pocket-id.org/" rel="noopener noreferrer"&gt;OIDC provider overview&lt;/a&gt; both point in the right direction: centralise the sign-in flow, then let applications trust the identity provider instead of rolling their own login logic.&lt;/p&gt;

&lt;p&gt;For services that do not speak OIDC cleanly, the reverse proxy can still do useful work. Authentik's current &lt;a href="https://docs.goauthentik.io/add-secure-apps/providers/proxy/server_nginx/" rel="noopener noreferrer"&gt;nginx proxy integration docs&lt;/a&gt; use the outpost path &lt;code&gt;/outpost.goauthentik.io/auth/nginx&lt;/code&gt;, which is the shape I would point people at now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;auth_request&lt;/span&gt; &lt;span class="n"&gt;/outpost.goauthentik.io/auth/nginx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_page&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@goauthentik_proxy_signin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;auth_request_set&lt;/span&gt; &lt;span class="nv"&gt;$auth_cookie&lt;/span&gt; &lt;span class="nv"&gt;$upstream_http_set_cookie&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Set-Cookie&lt;/span&gt; &lt;span class="nv"&gt;$auth_cookie&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:11434&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/outpost.goauthentik.io&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://authentik.company:9000/outpost.goauthentik.io&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Original-URL&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;//&lt;/span&gt;&lt;span class="nv"&gt;$http_host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass_request_body&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Content-Length&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not a full WebAuthn setup by itself. It is the shape of the gate. nginx hands the auth decision to Authentik's outpost, and the application behind it does not need to know anything about passkeys directly.&lt;/p&gt;

&lt;p&gt;This is why I think the passkeys conversation makes more sense in homelabs than on random borrowed devices. I control the browser, the phone, the password manager, and the identity layer. That removes most of the chaos people hit in enterprise fleets or public computers.&lt;/p&gt;

&lt;p&gt;It also completes the security picture from the &lt;a href="https://www.techiemike.com/tls-certificates-internal-services-https-homelab/" rel="noopener noreferrer"&gt;TLS post&lt;/a&gt;. TLS proves you reached the right server. Passkeys prove the right user is logging in. And if you already liked the least-privilege argument I made in the &lt;a href="https://www.techiemike.com/cloudflare-self-managed-oauth-homelab-security/" rel="noopener noreferrer"&gt;Cloudflare self-managed OAuth post&lt;/a&gt;, passkeys feel like the same idea applied to human sign-in.&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%2F716jwhlu4ejkdwel5ct9.webp" 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%2F716jwhlu4ejkdwel5ct9.webp" alt="Homelab login flow showing nginx, Authentik, and passkey-based sign-in protecting internal services" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why CS students need to care now
&lt;/h2&gt;

&lt;p&gt;Because this is not optional background knowledge any more.&lt;/p&gt;

&lt;p&gt;Students already learn authentication, biometrics, digital signatures, TLS, and asymmetric encryption as separate syllabus ideas. Passkeys are one of the first mainstream systems that make all of them visible in one place.&lt;/p&gt;

&lt;p&gt;A good exam question almost writes itself.&lt;/p&gt;

&lt;p&gt;Explain how a website using passkey-based authentication is more secure than one using only passwords.&lt;/p&gt;

&lt;p&gt;The model answer is short.&lt;/p&gt;

&lt;p&gt;For the passkey credential, the server stores a public key rather than a reusable shared secret. The client signs a challenge with a private key instead of sending a reusable secret. The signature proves possession of the key. The biometric or PIN unlock happens locally on the device, so the secret does not leave the client.&lt;/p&gt;

&lt;p&gt;That is Chapter 6 and Chapter 17 with a real browser in front of it.&lt;/p&gt;

&lt;p&gt;The other reason students should care is practical. Enterprise platforms are pushing hard towards passwordless sign-in, and Microsoft Entra is part of that direction even if the user experience is still catching up. By the time today's students hit serious workplace systems, passkeys will not be an optional curiosity. They will be normal. And if you want a parallel lesson in what happens when self-hosted authentication is treated casually, read my piece on &lt;a href="https://www.techiemike.com/ollama-security-self-hosted-ai/" rel="noopener noreferrer"&gt;self-hosted AI security&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  My view after using them
&lt;/h2&gt;

&lt;p&gt;Passkeys are not an upgrade you install. They are an architectural shift.&lt;/p&gt;

&lt;p&gt;That is why the transition feels messy. Passwords were simple, portable, and terrible. Passkeys are stronger, less phishable, and tied to real devices, but they expose every weak assumption vendors made about sync, recovery, and platform control.&lt;/p&gt;

&lt;p&gt;I am still on the side of passkeys.&lt;/p&gt;

&lt;p&gt;Not because the current UX is perfect. It is not. I am on their side because the old model has run out of road. We have spent years teaching people to make longer passwords, store them properly, avoid phishing, rotate them after leaks, and stop reusing them. That advice still matters, but it is maintenance on a bad design.&lt;/p&gt;

&lt;p&gt;Passkeys replace the bad design.&lt;/p&gt;

&lt;p&gt;That is why I think they are the death of passwords, even if the funeral is taking longer than the vendors promised.&lt;/p&gt;

</description>
      <category>security</category>
      <category>linux</category>
      <category>tutorial</category>
      <category>developer</category>
    </item>
    <item>
      <title>How to Determine Which Old Drives Are Still Good — SMART Data, badblocks, and Homelab Hardware Longevity</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Mon, 20 Jul 2026 07:40:01 +0000</pubDate>
      <link>https://dev.to/techiemike/how-to-determine-which-old-drives-are-still-good-smart-data-badblocks-and-homelab-hardware-38ak</link>
      <guid>https://dev.to/techiemike/how-to-determine-which-old-drives-are-still-good-smart-data-badblocks-and-homelab-hardware-38ak</guid>
      <description>&lt;h1&gt;
  
  
  How to Determine Which Old Drives Are Still Good — SMART Data, badblocks, and Homelab Hardware Longevity
&lt;/h1&gt;

&lt;p&gt;Someone on r/homelab posted a photo of a pile of old drives and asked one simple question: how do I determine which of these are still good? Every self-hoster ends up in the same position. Drives accumulate. Labels fade. One came out of an old desktop, one came from a USB dock, one has been in a drawer for three years, and now you have to decide what you can trust.&lt;/p&gt;

&lt;p&gt;I do not make that decision from the drive's age or from one reassuring word in a terminal. I make it from SMART data, the right self-tests, and a very blunt rule: trends matter more than a single PASSED result.&lt;/p&gt;

&lt;p&gt;That matters in my own setup. My homelab is not a theoretical diagram. I have a 465.8 GB Gigabyte NVMe boot drive in my Hermes host, a 931 GB P3 SATA SSD in my Proxmox box, and a 1.9 TB Solidigm NVMe handling local-lvm storage. Those are different drive types, different roles, and different failure patterns. The method is the same on all of them: pull the data, read the few numbers that matter, then decide whether the drive is fit for real work, background storage, or the recycling pile.&lt;/p&gt;

&lt;p&gt;If you are building a small server around a Mini PC with upgrade room, like I described in my &lt;a href="https://www.techiemike.com/chuwi-minibook-x-vs-bmax-pro-8-which-budget-homelab-machine/" rel="noopener noreferrer"&gt;Chuwi Minibook X vs BMAX Pro 8 comparison&lt;/a&gt;, this is the step people skip. They happily add an old disk, then act surprised when the machine starts queueing I/O or losing data six weeks later.&lt;/p&gt;

&lt;h2&gt;
  
  
  SMART data is useful, but only if you read the right parts
&lt;/h2&gt;

&lt;p&gt;The first trap is believing SMART gives you one simple answer. It does not. &lt;code&gt;smartctl -a&lt;/code&gt; gives you a wall of attributes, and most of them do not matter in the same way on every vendor.&lt;/p&gt;

&lt;p&gt;These are the fields I care about first on an HDD or SATA SSD:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Reallocated_Sector_Ct&lt;/code&gt;: sectors the drive has already given up on and remapped&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Current_Pending_Sector&lt;/code&gt;: sectors the drive is worried about but has not remapped yet&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Offline_Uncorrectable&lt;/code&gt;: sectors it could not read properly&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UDMA_CRC_Error_Count&lt;/code&gt;: usually a cable problem, not a dying drive&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Temperature_Celsius&lt;/code&gt;: context, not panic, but still worth watching&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Power_On_Hours&lt;/code&gt;: useful background, not a verdict by itself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mistake I see all the time is someone spotting one non-zero field and deciding the drive is dead. That is not how this works. A drive with stable numbers is a different story from a drive whose counts are growing every week.&lt;/p&gt;

&lt;p&gt;This is why I think the overall SMART health line is nearly useless on its own. &lt;code&gt;smartctl -H /dev/sda&lt;/code&gt; gives you PASSED or FAILED, and people treat that like an exam result. It is not. A drive can still say PASSED while collecting pending sectors and quietly moving towards the cliff. A drive can still report PASSED even when individual attributes are starting to deteriorate. By the time it flips to FAILED, you are already late.&lt;/p&gt;

&lt;p&gt;My rule is simple: save the output, run it again later, and compare. If &lt;code&gt;Reallocated_Sector_Ct&lt;/code&gt; sits at the same value for months, that is one conversation. If it grows from 0 to 3 in a week, that drive has started its decline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;smartctl &lt;span class="nt"&gt;-a&lt;/span&gt; /dev/sda
smartctl &lt;span class="nt"&gt;-H&lt;/span&gt; /dev/sda
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is another trap here: vendor interpretation. Seagate, WD, HGST and others do not all present raw values the same way. &lt;code&gt;Raw_Read_Error_Rate&lt;/code&gt; is the classic example. On one vendor, a big raw number can be normal. On another, zero is what you expect from a healthy drive. If you copy the wrong interpretation from a random forum comment, you can talk yourself into binning a drive that is fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The USB dock problem catches almost everybody once
&lt;/h2&gt;

&lt;p&gt;If you test old drives externally, this is the frustration you are likely to hit first. You run &lt;code&gt;smartctl /dev/sdb&lt;/code&gt; and it tells you the device does not support SMART. Then you move the same drive to a direct SATA connection and suddenly the data appears.&lt;/p&gt;

&lt;p&gt;The drive was not lying. The USB bridge was.&lt;/p&gt;

&lt;p&gt;SMART is an ATA feature, and many cheap USB-to-SATA bridges do a poor job passing those commands through. In that case you need to tell smartctl what kind of translation layer it is dealing with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;smartctl &lt;span class="nt"&gt;-d&lt;/span&gt; sat &lt;span class="nt"&gt;-a&lt;/span&gt; /dev/sdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some bridges need device-specific flags such as &lt;code&gt;-d usbjmicron&lt;/code&gt; or &lt;code&gt;-d usbprolific&lt;/code&gt;, but &lt;code&gt;-d sat&lt;/code&gt; is the one that fixes the problem most often. This is one of those details that wastes an hour the first time you hit it, because most tutorials quietly assume the drive is connected internally.&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%2Fh9483o3s5kssyuvmqzye.webp" 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%2Fh9483o3s5kssyuvmqzye.webp" alt="Terminal output from smartctl checking an old drive over USB with SAT passthrough" width="800" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The tests that actually tell you something
&lt;/h2&gt;

&lt;p&gt;If you only run &lt;code&gt;-H&lt;/code&gt;, you are barely checking the drive. The useful test sequence is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;smartctl &lt;span class="nt"&gt;-t&lt;/span&gt; short /dev/sda
smartctl &lt;span class="nt"&gt;-t&lt;/span&gt; long /dev/sda
smartctl &lt;span class="nt"&gt;-l&lt;/span&gt; selftest /dev/sda
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The short test is the quick screen. It takes a couple of minutes and is worth doing on anything you have just pulled out of a drawer.&lt;/p&gt;

&lt;p&gt;The long test is where the real evidence starts. That is the one that scans the surface properly and gives you a better chance of catching unreadable areas before you trust the disk with backups, media, or VM images. On large drives it takes hours, not minutes. On USB enclosures, it can also fail quietly if the enclosure sleeps or times out, which is why I always check &lt;code&gt;-l selftest&lt;/code&gt; afterwards instead of assuming the command finished cleanly.&lt;/p&gt;

&lt;p&gt;This is the same mindset behind my &lt;a href="https://www.techiemike.com/minimal-homelab-mini-pc-docker-compose/" rel="noopener noreferrer"&gt;minimal homelab build&lt;/a&gt;: assume components fail eventually, and design around that reality. In that post I made the point that I want a server I can rebuild quickly if an SSD dies. SMART checks are the maintenance version of the same philosophy. You are not trying to predict the future perfectly. You are trying to avoid preventable surprises.&lt;/p&gt;

&lt;h2&gt;
  
  
  badblocks is useful, but it is not the answer for every drive
&lt;/h2&gt;

&lt;p&gt;People recommend &lt;code&gt;badblocks&lt;/code&gt; like it is a universal health check. It is not.&lt;/p&gt;

&lt;p&gt;For an old HDD you are about to reuse, &lt;code&gt;badblocks&lt;/code&gt; can be very helpful. For an SSD, the wrong mode is a bad idea. For NVMe, it is the wrong tool entirely.&lt;/p&gt;

&lt;p&gt;The two modes most people see are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;badblocks &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-sv&lt;/span&gt; /dev/sda
badblocks &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="nt"&gt;-sv&lt;/span&gt; /dev/sda
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-n&lt;/code&gt; is the non-destructive read-write mode. It preserves the data already on the HDD, but it still writes across the disk, takes ages on large drives, and is something I would run only on an unmounted disk I am deliberately testing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-w&lt;/code&gt; is destructive. It writes patterns across the disk and checks them. It is thorough, but it also erases the drive. More importantly, it is the wrong thing to do on SSDs because you are burning write cycles for no good reason. It is also a poor fit for SMR drives, where the translation layer can make the process painfully slow and occasionally misleading.&lt;/p&gt;

&lt;p&gt;My rule here is straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Old HDD with no data you care about: &lt;code&gt;badblocks -w&lt;/code&gt; is fine if you have time&lt;/li&gt;
&lt;li&gt;Old HDD you want to keep intact: &lt;code&gt;badblocks -n&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;SATA SSD: skip destructive badblocks, use SMART and vendor tools instead&lt;/li&gt;
&lt;li&gt;NVMe SSD: use NVMe health data, not badblocks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters more now than it did a few years ago. My Hermes and Proxmox boxes both rely on NVMe somewhere in the stack. For those drives I care about &lt;code&gt;Percentage Used&lt;/code&gt;, &lt;code&gt;Media_Errors&lt;/code&gt;, and &lt;code&gt;Critical_Warning&lt;/code&gt; far more than I care about old HDD-style surface testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvme list
nvme smart-log /dev/nvme0n1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One number that gets misread a lot is &lt;code&gt;Percentage Used&lt;/code&gt;. If an NVMe reports 50%, that does not mean the drive is half-dead in the dramatic way people imagine. It means the drive estimates it has used half of its write endurance. In a homelab boot role, that can still mean years of service left.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one SMART problem that deserves immediate attention
&lt;/h2&gt;

&lt;p&gt;If I had to pick one field that changes my tone fastest, it is &lt;code&gt;Current_Pending_Sector&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A pending sector is a sector the drive is struggling to read reliably but has not remapped yet. That means you are in the awkward middle ground where the drive knows there is trouble, but it has not fully resolved it. Sometimes a forced read or write will clear the issue. Sometimes it will confirm the damage.&lt;/p&gt;

&lt;p&gt;A simple read pass is often the first thing I would try. Before you run it, double-check the device name. &lt;code&gt;dd&lt;/code&gt; is read-only in this example because the output is &lt;code&gt;/dev/null&lt;/code&gt;, but disk commands are still the kind of thing you do not want to aim at the wrong drive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/sda &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/null &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1M &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;progress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That forces the drive to read the entire surface, which makes it a useful diagnostic first step. After that, run &lt;code&gt;smartctl -a&lt;/code&gt; again and see whether the pending count dropped, stayed the same, or grew. If the count is still there, the next meaningful change usually comes only when the drive manages a successful write to the bad area and remaps it. If it stayed or increased, I stop pretending the drive is trustworthy.&lt;/p&gt;

&lt;p&gt;This is also where backups stop being a nice idea and become the actual point. In my &lt;a href="https://www.techiemike.com/self-hosting-toolkit-10-apps/" rel="noopener noreferrer"&gt;self-hosting toolkit post&lt;/a&gt;, I mentioned backing up databases to an external drive with a simple cron job. That matters here because old disks are not where you should discover your backup strategy is imaginary.&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%2Fgccx28ub6xjqm0ju9hty.webp" 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%2Fgccx28ub6xjqm0ju9hty.webp" alt="Portrait photo of mixed HDDs and SSDs beside a notebook showing keep, monitor, retire decisions" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My keep, monitor, or retire framework
&lt;/h2&gt;

&lt;p&gt;This is the decision table I would actually use.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;What I do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SMART attributes normal, self-tests clean&lt;/td&gt;
&lt;td&gt;Keep&lt;/td&gt;
&lt;td&gt;Fine for normal use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reallocated sectors present but stable&lt;/td&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;Use for non-critical work, check again later&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reallocated sectors growing&lt;/td&gt;
&lt;td&gt;Replace soon&lt;/td&gt;
&lt;td&gt;Do not trust it long term&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pending sectors above zero&lt;/td&gt;
&lt;td&gt;Rescue or retire&lt;/td&gt;
&lt;td&gt;Re-test after a read/write pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline uncorrectable above zero&lt;/td&gt;
&lt;td&gt;Retire&lt;/td&gt;
&lt;td&gt;The drive has recorded unreadable sectors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UDMA CRC errors rising&lt;/td&gt;
&lt;td&gt;Check cable first&lt;/td&gt;
&lt;td&gt;Often not the drive itself&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://www.backblaze.com/blog/backblaze-drive-stats-for-2025/" rel="noopener noreferrer"&gt;Backblaze's annual drive statistics&lt;/a&gt; line up with this. Reallocated sectors and pending sectors are far better warning signs than raw age alone. A 30,000-hour drive is not automatically worse than a younger one. A stable old enterprise disk can be less worrying than a younger consumer drive whose counts have started moving.&lt;/p&gt;

&lt;p&gt;That is also a useful lesson for students. Storage hardware sounds tidy in the syllabus and messy in real life. In class you learn what sectors, flash cells, fragmentation and endurance mean. In the lab, you learn when those ideas turn into real decisions about remapping, wear, and whether a warning is just background noise or the start of a failure curve.&lt;/p&gt;

&lt;p&gt;That is why I keep tying homelab work back to the classroom. The theory only really sticks when you have to decide whether the drive in front of you is safe enough to keep using.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final point: stop asking if the drive is good or bad
&lt;/h2&gt;

&lt;p&gt;That is the wrong question.&lt;/p&gt;

&lt;p&gt;The useful question is this: what is the trend, what role do I want this drive to play, and how much failure am I prepared to tolerate?&lt;/p&gt;

&lt;p&gt;A clean SMART report plus a completed long self-test is enough for me to keep using a drive. Stable minor warnings mean monitor it and keep it away from anything important. Pending sectors, growing reallocations, or uncorrectable errors mean I stop negotiating with it.&lt;/p&gt;

&lt;p&gt;That is the method. Pull the SMART data. Run the real tests. Watch the trend. Then decide whether the drive belongs in your Proxmox node, your backups shelf, or the bin.&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>linux</category>
      <category>proxmox</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SSH Honeypot Live — What Watching Bots Attack a Server Teaches CS Students About Network Security</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Sun, 19 Jul 2026 02:00:01 +0000</pubDate>
      <link>https://dev.to/techiemike/ssh-honeypot-live-what-watching-bots-attack-a-server-teaches-cs-students-about-network-security-3mae</link>
      <guid>https://dev.to/techiemike/ssh-honeypot-live-what-watching-bots-attack-a-server-teaches-cs-students-about-network-security-3mae</guid>
      <description>&lt;h1&gt;
  
  
  SSH Honeypot Live — What Watching Bots Attack a Server Teaches CS Students About Network Security
&lt;/h1&gt;

&lt;p&gt;Every internet-connected server lives in a shooting gallery.&lt;/p&gt;

&lt;p&gt;Open &lt;a href="https://honeypotlive.cc/" rel="noopener noreferrer"&gt;https://honeypotlive.cc/&lt;/a&gt; for a minute and the point lands immediately. The screen fills with source IPs, login attempts, usernames, guessed passwords, and commands being fired at a fake Linux box by machines that think they have found a real one. There is no drama needed here. This is just what the internet does to exposed SSH.&lt;/p&gt;

&lt;p&gt;That is why I like the project so much as a teaching tool. Cowrie, the Python SSH and Telnet honeypot behind it, strips the theory out of network security and replaces it with evidence. Students do not have to imagine a brute force attack. They can watch one. They do not have to memorise that weak credentials are risky. They can see bots trying &lt;code&gt;root&lt;/code&gt;, &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;pi&lt;/code&gt;, &lt;code&gt;ubuntu&lt;/code&gt;, &lt;code&gt;123456&lt;/code&gt;, and &lt;code&gt;password&lt;/code&gt; in real time.&lt;/p&gt;

&lt;p&gt;I use SSH constantly, whether that is tunnelling into a box, checking a service, or fixing something late at night. I wrote about the practical side of that in &lt;a href="https://www.techiemike.com/ssh-tunnels-practical-guide/" rel="noopener noreferrer"&gt;my SSH tunnels guide&lt;/a&gt;. The security side is less comfortable. If you leave SSH exposed to the internet, bots start work long before you have finished your coffee.&lt;/p&gt;

&lt;p&gt;I think that is one of the most useful lessons a CS student can learn early: the question is not whether a service will be probed. The question is what you put in front of it when that probing starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the honeypot is actually showing you
&lt;/h2&gt;

&lt;p&gt;The attraction of honeypotlive.cc is that it does not hide the messy bits. You see the boring repetition that real attack traffic is built from.&lt;/p&gt;

&lt;p&gt;Most of it is not clever. It is industrial.&lt;/p&gt;

&lt;p&gt;A bot connects, guesses a username, guesses a password, then moves on. Another one does the same thing from somewhere else. Then another. On a busy public SSH honeypot, that pattern keeps repeating all day. Community data around Cowrie deployments regularly shows &lt;code&gt;root&lt;/code&gt; as one of the most targeted usernames, with &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;pi&lt;/code&gt;, &lt;code&gt;ubuntu&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt;, and &lt;code&gt;user&lt;/code&gt; close behind. The passwords are exactly as bad as you would expect: &lt;code&gt;123456&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;12345&lt;/code&gt;, &lt;code&gt;1234&lt;/code&gt;, &lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is useful because students tend to imagine attackers as individuals picking targets one by one. Most early attack traffic is not personal. It is automated scanning plus credential spraying at scale.&lt;/p&gt;

&lt;p&gt;Cowrie records all of that. Every login attempt. Every command. Every file transfer if the attacker gets that far. That gives you something far better than a textbook paragraph. It gives you data you can sort, count, graph, and argue about.&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%2Fvflue5fycttuyd7jy245.webp" 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%2Fvflue5fycttuyd7jy245.webp" alt="SSH Honeypot Live dashboard showing real-time login attempts and credential guessing against a fake Linux server" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My own logs tell the same story from the defensive side
&lt;/h2&gt;

&lt;p&gt;The live honeypot is the public demo. My own security logs are the quieter version of the same lesson.&lt;/p&gt;

&lt;p&gt;On my own setup, the Hermes host at 192.168.1.117 is a private LAN machine, and public-facing services reach it through Cloudflare Tunnel rather than a directly exposed SSH port. My Proxmox node at 192.168.1.25 is also tucked away properly. That is deliberate. I use Cloudflare Tunnel and other layers in front of services because exposed admin ports are an invitation.&lt;/p&gt;

&lt;p&gt;When I wrote about the CISA GitHub leak and homelab security, I showed the fail2ban output I actually use on Linux boxes that need protecting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Status for the jail: sshd
|- Filter
|  |- Currently failed: 3
|  `- Total failed:     19
`- Actions
   |- Currently banned: 1
   `- Total banned:     4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the same story as the honeypot, just from the side that is trying to keep the gate shut. Failed attempts accumulate. IPs cross a threshold. Bans get applied. The traffic is not theoretical and it is not rare. I covered that defensive playbook in &lt;a href="https://www.techiemike.com/cisa-github-leak-homelab-security-lessons/" rel="noopener noreferrer"&gt;my CISA GitHub leak post&lt;/a&gt;. The honeypot is the visual proof of why that playbook exists.&lt;/p&gt;

&lt;p&gt;This is the part students remember. "Defense in depth" sounds vague until they can match it to a sequence of events. A scan finds the host. SSH answers. Credentials get guessed. fail2ban notices. The firewall limits what else is reachable. An authentication layer stops the next mistake becoming a breach.&lt;/p&gt;

&lt;p&gt;That chain is much easier to understand when each step leaves a visible trace.&lt;/p&gt;

&lt;h2&gt;
  
  
  How bots find a fresh SSH server so quickly
&lt;/h2&gt;

&lt;p&gt;The speed shocks people the first time they see it.&lt;/p&gt;

&lt;p&gt;Put a basic VPS online with SSH exposed and it does not stay undiscovered for long. Internet-wide scanners such as Shodan and Censys are built to catalogue exposed services, and tools like masscan can sweep huge parts of IPv4 at absurd speed. That is how fresh SSH services get noticed and indexed so quickly. The failed logins usually come from a different layer of cheap brute-force bots and credential-spray scripts that follow once exposed SSH is easy to find.&lt;/p&gt;

&lt;p&gt;This is also where weak security advice starts to fall apart.&lt;/p&gt;

&lt;p&gt;People sometimes talk about changing the SSH port as if it is either magic or useless. It is neither. Against a determined scan that checks every port, obscurity is thin. Against the enormous amount of lazy automation that only bangs on port 22, it still cuts noise. Students should understand both truths at the same time. Network security is full of that kind of answer: one measure is not enough, but one measure can still be worth having.&lt;/p&gt;

&lt;p&gt;That is also why I keep pointing people toward stronger patterns such as tunnels and proper access layers. If you need a cleaner model for that, &lt;a href="https://www.techiemike.com/cloudflare-self-managed-oauth-homelab-security/" rel="noopener noreferrer"&gt;my Cloudflare self-managed OAuth write-up&lt;/a&gt; and &lt;a href="https://www.techiemike.com/self-hosting-toolkit-10-apps/" rel="noopener noreferrer"&gt;my self-hosting toolkit post&lt;/a&gt; both make the same point in different ways: do not expose admin surfaces casually when a better route exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden lesson: even the honeypot has failure modes
&lt;/h2&gt;

&lt;p&gt;A honeypot is a security tool, but it is still software, and software still breaks in ordinary ways.&lt;/p&gt;

&lt;p&gt;That is one reason I would use this topic in class. Honeypots also teach the ordinary systems failures that sit underneath security monitoring.&lt;/p&gt;

&lt;p&gt;A Cowrie deployment in Docker, for example, can fail silently if the log directory is bind-mounted with the wrong ownership. The container runs, accepts SSH sessions, and looks healthy enough. Meanwhile the logs do not get written because the &lt;code&gt;cowrie&lt;/code&gt; user inside the container cannot write to the host directory. That is exactly the kind of bug students miss at first because the service appears to be working.&lt;/p&gt;

&lt;p&gt;Busy honeypots also create very normal infrastructure problems. JSON logs grow fast. A mid-size public deployment can collect thousands of login attempts per day, and every attempt is another event written to disk. SFTP capture adds a different problem again: now the system is storing whatever malware, tools, or junk an attacker uploads. One careless setup on a cheap VPS and your "monitoring box" becomes a disk-space lesson.&lt;/p&gt;

&lt;p&gt;Containment matters too. Cowrie emulates a shell and filesystem well enough to keep attackers interested, which is the whole point. But that also means isolation is not optional. Run it in Docker or a separate VM. Do not place a honeypot beside production services and pretend that is good enough. I have already written about sensible homelab separation in &lt;a href="https://www.techiemike.com/proxmox-homelab-setup-on-a-mini-pc-ubuntu-vms-beyond/" rel="noopener noreferrer"&gt;my Proxmox homelab setup post&lt;/a&gt;. The same rule applies here: if a security tool can attract hostile traffic, it deserves its own blast radius.&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%2Fvux67adqauc2r0wa7mif.webp" 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%2Fvux67adqauc2r0wa7mif.webp" alt="Student-friendly diagram showing scanners, SSH login attempts, fail2ban bans, and isolated honeypot containment" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this maps neatly onto the CS syllabus
&lt;/h2&gt;

&lt;p&gt;This is the rare security topic that lands in both the logs and the exam specification.&lt;/p&gt;

&lt;p&gt;Cambridge IGCSE Computer Science already asks students to understand brute force attacks, hacking, malware, data interception, and security measures. A live SSH honeypot gives them a concrete picture for several of those terms at once. The repeated login attempts are brute force. The guessed credentials show why weak passwords fail. The source IP churn hints at distributed infrastructure. The fail2ban response is a clear example of an automated security measure.&lt;/p&gt;

&lt;p&gt;It also turns networking topics into something less sterile. Ports stop being numbers in a table and become doors that attract traffic. IP addressing stops being a diagram and becomes the reason a scanner can find a box on the public internet. Encryption stops being an abstract property and becomes the awkward reality that attackers are trying to abuse a legitimate secure protocol rather than break the maths underneath it.&lt;/p&gt;

&lt;p&gt;That is why I would rather teach this than another tidy slide deck full of definitions. Students learn security terms faster when the system misbehaves in front of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The simplest experiment is still the best one
&lt;/h2&gt;

&lt;p&gt;If you want the lesson without building a whole project, keep it small.&lt;/p&gt;

&lt;p&gt;Spin up a cheap VPS. Lock it down properly first: use SSH keys, disable password authentication, and never test with real or reused credentials. Then install fail2ban.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;fail2ban
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then watch the logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Failed password"&lt;/span&gt; /var/log/auth.log | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;fail2ban-client status sshd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want the fuller demonstration, run Cowrie in isolation. The current Cowrie README on the main branch still uses this quick-start container example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 2222:2222 cowrie/cowrie:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Port 2222 makes a cleaner demo and avoids throwing a default SSH service straight onto 22, but it also means you will not attract the same volume or pattern of lazy bot traffic that a public port-22 service gets.&lt;/p&gt;

&lt;p&gt;Now compare what your own logs show against what honeypotlive.cc streams publicly. That is a better network security lesson than most classroom worksheets because it answers the question students usually care about first: "What is actually happening out there?"&lt;/p&gt;

&lt;p&gt;And the answer is blunt. Bots are already trying. They do not care whether the box belongs to a student, a hobbyist, or a government agency. The traffic is the same. The difference is whether you left the door open, whether you can see the attempts, and whether your defences are awake when they arrive.&lt;/p&gt;

&lt;p&gt;That is why I like SSH honeypots so much. They make network security stop sounding like advice and start looking like evidence.&lt;/p&gt;

</description>
      <category>security</category>
      <category>linux</category>
      <category>tutorial</category>
      <category>developer</category>
    </item>
    <item>
      <title>The $20 Server — What Extreme Budget Computing Teaches CS Students</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Sat, 18 Jul 2026 02:00:01 +0000</pubDate>
      <link>https://dev.to/techiemike/the-20-server-what-extreme-budget-computing-teaches-cs-students-3jm0</link>
      <guid>https://dev.to/techiemike/the-20-server-what-extreme-budget-computing-teaches-cs-students-3jm0</guid>
      <description>&lt;h1&gt;
  
  
  The $20 Server — What Extreme Budget Computing Teaches CS Students
&lt;/h1&gt;

&lt;p&gt;The most educational server in the room is usually the worst one.&lt;/p&gt;

&lt;p&gt;Give students unlimited cloud credits and they can launch machines all afternoon without learning much about limits. Give them a battered $20 office desktop with 4 GB of RAM and an old hard drive, and suddenly every operating system lesson becomes real. Containers fight for memory. Disk latency stops being a diagram and starts being the reason the machine feels stuck. "Virtual memory" is no longer a sentence to memorise for an exam. It is the pause they can feel in their hands.&lt;/p&gt;

&lt;p&gt;I think we make systems teaching too comfortable. Comfortable hardware hides the lesson.&lt;/p&gt;

&lt;p&gt;My own homelab is not running on scrap. The Hermes box here is an i7-1255U machine with 24 GB of RAM. My Proxmox node is an i5-8260U with 16 GB. Those are sensible little servers. They are reliable, quiet, and good at real work. But they are almost too polite as teaching tools. They have enough headroom to forgive bad habits. A $20 machine does not forgive anything, and that is exactly why it teaches so well.&lt;/p&gt;

&lt;h2&gt;
  
  
  A cheap box makes operating systems visible
&lt;/h2&gt;

&lt;p&gt;A lot of CS students meet operating systems through neat definitions: process scheduling, paging, interrupts, secondary storage, background services. The problem is that on decent modern hardware, many of those ideas stay invisible.&lt;/p&gt;

&lt;p&gt;On a constrained machine, they stop being invisible.&lt;/p&gt;

&lt;p&gt;Open a few browser tabs, pull a container image, start a database, and then watch the machine try to keep up. That is process management with consequences. If a lesson asks a student to explain how memory pressure pushes pages out of RAM and into virtual memory, the answer is no longer abstract. They have already seen the machine slow down when it happens.&lt;/p&gt;

&lt;p&gt;That is why I like this topic so much. A cheap server is not just a budget trick. It is a working model of the syllabus.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a $20 server can actually do
&lt;/h2&gt;

&lt;p&gt;People either romanticise these little boxes or dismiss them. Both are wrong.&lt;/p&gt;

&lt;p&gt;A $20 used desktop can do useful work. It can run Linux properly. It can host a reverse proxy, a small Python app, a static site, a local Git service, a DNS blocker, or a couple of light Docker containers. It can teach SSH, permissions, package management, logs, services, backups, and the discipline of keeping a machine tidy.&lt;/p&gt;

&lt;p&gt;What it cannot do is pretend to be a modern all-in-one homelab. This is not the box for 25 containers, heavy VMs, Plex transcoding, or local AI models. If that is your goal, skip the fantasy and buy stronger hardware. I wrote about that trade-off in &lt;a href="https://www.techiemike.com/chuwi-minibook-x-vs-bmax-pro-8-which-budget-homelab-machine/" rel="noopener noreferrer"&gt;Chuwi Minibook X vs BMAX Pro 8 — Which Budget Homelab Machine?&lt;/a&gt;. If you can stretch to a proper mini PC, life gets easier very quickly.&lt;/p&gt;

&lt;p&gt;But easier is not always better for learning.&lt;/p&gt;

&lt;p&gt;A small machine forces choices. Do you really need a database for this project, or will SQLite do? Do you need another container, or have you just become lazy because Docker Compose makes laziness easy? Do you actually understand what is running on the box, or have you stacked services until the machine became a mystery?&lt;/p&gt;

&lt;p&gt;That is systems thinking, and expensive hardware often delays it.&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/blog-vault%2F04-images%2Fthe-20-server-what-extreme-budget-computing-teaches-cs-students%2Fbody-01-used-office-pc.webp" 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/blog-vault%2F04-images%2Fthe-20-server-what-extreme-budget-computing-teaches-cs-students%2Fbody-01-used-office-pc.webp" alt="Used office desktop on a small desk with Linux terminal open, representing a $20 budget server for CS students" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  RAM is where the lesson starts
&lt;/h2&gt;

&lt;p&gt;If I had to pick one concept that cheap hardware teaches best, it would be memory pressure.&lt;/p&gt;

&lt;p&gt;Students hear that RAM is fast and disks are slow. Fine. They hear that virtual memory uses secondary storage when RAM runs short. Also fine. Then they sit an exam and write the sentence back.&lt;/p&gt;

&lt;p&gt;A 4 GB machine makes them understand it.&lt;/p&gt;

&lt;p&gt;Run these before and during the experiment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;free &lt;span class="nt"&gt;-h&lt;/span&gt;
vmstat 1
dmesg | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; oom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;free -h&lt;/code&gt; for a memory snapshot, &lt;code&gt;vmstat 1&lt;/code&gt; to watch activity live, and the &lt;code&gt;dmesg&lt;/code&gt; command afterward if a process disappears.&lt;/p&gt;

&lt;p&gt;That is a better lesson than three slides full of definitions.&lt;/p&gt;

&lt;p&gt;When available memory shrinks, swap activity rises, and the system eventually cannot satisfy another memory request, the kernel may invoke the &lt;a href="https://docs.kernel.org/mm/oom.html" rel="noopener noreferrer"&gt;OOM killer&lt;/a&gt;. Then the student can see the operating system making trade-offs in real time. Suddenly the OOM killer is not a strange term from a textbook. It is the reason the database vanished. The machine is telling them, very bluntly, that every service has a cost.&lt;/p&gt;

&lt;p&gt;This is also where bad habits get exposed. Students love overbuilding. They install a full stack because a tutorial told them to. On a roomy cloud VM, that habit survives for months. On a $20 box, it gets punished in an hour. Good. Better to learn restraint early.&lt;/p&gt;

&lt;p&gt;I would rather teach a student to respect 4 GB properly than let them waste 32 GB carelessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Old disks teach I/O bottlenecks without any speech from me
&lt;/h2&gt;

&lt;p&gt;RAM usually fails first, but the disk is where the machine becomes honest.&lt;/p&gt;

&lt;p&gt;A cheap server often comes with the sort of storage that makes modern users impatient within minutes. That sounds like a drawback. In a classroom, it is gold.&lt;/p&gt;

&lt;p&gt;Start an update, unpack a container image, and do a few file operations at the same time. On a modern NVMe drive, the box may shrug. On an old mechanical disk, the machine starts queueing work badly enough that everyone notices. The operating system is still functioning, but it is clearly waiting on storage.&lt;/p&gt;

&lt;p&gt;That moment matters because it connects three ideas students often treat separately: throughput, latency, and contention. They stop thinking of storage as a silent cupboard where files live and start seeing it as a shared resource that can become the bottleneck for everything else.&lt;/p&gt;

&lt;p&gt;It also teaches one of my favourite engineering rules: when a cheap upgrade fixes a structural bottleneck, do that first. On old office PCs, that usually means fitting even a modest SSD before doing anything clever. A tiny SSD will do more for the learning experience than another hour spent pretending a dying hard drive is "good enough."&lt;/p&gt;

&lt;h2&gt;
  
  
  Weak CPUs teach scheduling and background noise
&lt;/h2&gt;

&lt;p&gt;CPU scheduling is another topic that sounds clean in notes and messy on real hardware.&lt;/p&gt;

&lt;p&gt;On a budget desktop, background work stops being background in the magical sense. You can feel when the box is busy. A package install, a container restart, a compression job, and a browser tab all want time on the processor. The machine cannot hide the queue.&lt;/p&gt;

&lt;p&gt;That is why I like giving students boring commands on weak machines. Open &lt;code&gt;top&lt;/code&gt;. Start something heavier than the box likes. Watch what gets CPU time and what has to wait. Talk about foreground responsiveness, daemon work, and why "the computer is slow" is often really "the scheduler is making a sensible choice under pressure."&lt;/p&gt;

&lt;p&gt;A strong machine can mask poor judgement. A weak one teaches prioritisation.&lt;/p&gt;

&lt;p&gt;That lesson scales upward too. Once students understand why a small box struggles, they make better decisions later on bigger hardware. They stop assuming another service is free. They ask what it will cost in CPU time, memory, and storage pressure. That mindset matters far more than memorising a definition of round-robin scheduling and forgetting it a week later.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is why I do not worship free cloud credits
&lt;/h2&gt;

&lt;p&gt;Cloud labs have their place. I use them when they fit. But I do not think they are a good first teacher for systems thinking.&lt;/p&gt;

&lt;p&gt;A cloud dashboard gives students the illusion that resources arrive by menu. Two vCPUs here. Eight gigabytes there. Another volume if you feel like it. The machine appears from nowhere and, just as importantly, the machine disappears from the student's mind. Hardware becomes abstract too early.&lt;/p&gt;

&lt;p&gt;A cheap local box does the opposite. It makes every resource visible. It makes waste obvious. It teaches that there is no such thing as "just install one more thing" when the machine has no spare room.&lt;/p&gt;

&lt;p&gt;That is a much healthier starting point.&lt;/p&gt;

&lt;p&gt;If a student learns on constrained local hardware first, then moves into cloud platforms later, they usually make better choices. They size services more sensibly. They monitor memory sooner. They understand why idle background tasks matter. They respect logs and backups because they have already broken a real machine once or twice.&lt;/p&gt;

&lt;p&gt;That is a better outcome than teaching someone to click "bigger instance" every time they hit trouble.&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/blog-vault%2F04-images%2Fthe-20-server-what-extreme-budget-computing-teaches-cs-students%2Fbody-02-ram-pressure.webp" 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/blog-vault%2F04-images%2Fthe-20-server-what-extreme-budget-computing-teaches-cs-students%2Fbody-02-ram-pressure.webp" alt="Diagram showing RAM pressure, swap, and disk wait on a budget server used for teaching CS students" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell students to buy in Thailand
&lt;/h2&gt;

&lt;p&gt;The exact listings change all the time, so I would not pretend there is one magic model. But when I say "$20 server" in Thailand, I mean the sort of ex-office Dell OptiPlex, HP ProDesk, or Lenovo ThinkCentre box that turns up around ฿700-฿1,000 with 4 GB of RAM and a hard drive still inside it. That is not glamorous hardware. It is exactly the point.&lt;/p&gt;

&lt;p&gt;Buy ex-office hardware, not somebody else's failed gaming experiment.&lt;/p&gt;

&lt;p&gt;Old office desktops are boring, and boring is what you want. They are easier to service, usually cleaner inside, and more predictable than mystery machines built from leftovers. Ask the seller to boot into BIOS. Check the RAM amount properly. Check whether storage is a hard drive or an SSD. Listen for ugly fan noise. Look for missing screws, broken front ports, and proprietary power supplies that will annoy you later.&lt;/p&gt;

&lt;p&gt;If the machine only has a hard drive, budget for a small SSD immediately. If it has 4 GB of RAM, treat that as a teaching box, not a forever server. If you can find 8 GB cheaply, take it. That is the point where Linux starts to feel less like punishment and more like a platform.&lt;/p&gt;

&lt;p&gt;And if your budget can stretch beyond the bargain-bin tier, do it deliberately. A modest mini PC is still a far better long-term homelab host. My piece on &lt;a href="https://www.techiemike.com/minimal-homelab-mini-pc-docker-compose/" rel="noopener noreferrer"&gt;minimal homelab design with a mini PC and Docker Compose&lt;/a&gt; is the route I would recommend once the student understands the basics. For students who want the journey from first Linux box to self-hosting habits, &lt;a href="https://www.techiemike.com/high-school-to-homelab-a-students-linux-journey/" rel="noopener noreferrer"&gt;High School to Homelab — A Student's Linux Journey&lt;/a&gt; is the right mindset.&lt;/p&gt;

&lt;h2&gt;
  
  
  The classroom exercise I would actually run
&lt;/h2&gt;

&lt;p&gt;If I were teaching this as a practical lesson, I would keep it brutally simple.&lt;/p&gt;

&lt;p&gt;Install Debian. Enable SSH. Add Docker. Run one lightweight web service. Then add one more. Keep &lt;code&gt;free -h&lt;/code&gt;, &lt;code&gt;vmstat 1&lt;/code&gt;, and &lt;code&gt;journalctl -f&lt;/code&gt; open in separate terminals before and after. Now ask the students what changed and why. If the box actually falls over, run &lt;code&gt;dmesg | grep -i oom&lt;/code&gt; and make them explain that too.&lt;/p&gt;

&lt;p&gt;That is enough.&lt;/p&gt;

&lt;p&gt;Do not start with Kubernetes. Do not start with a dozen containers. Do not start with theory-heavy diagrams about "modern distributed systems." Start with one weak machine and one extra service too many. The operating system will do the explaining for you.&lt;/p&gt;

&lt;p&gt;That is the real gift of budget hardware. It turns hidden computer science into visible computer science.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the machine teaches
&lt;/h2&gt;

&lt;p&gt;The point of the $20 server is not that everyone should chase the cheapest hardware on earth. The point is that constrained hardware makes students look at the system instead of waving at it.&lt;/p&gt;

&lt;p&gt;On a box like this, they check memory before blaming the app. They notice disk wait before reinstalling Linux. They learn very quickly that background services are not free and that every extra process is spending somebody's RAM, CPU time, or storage I/O.&lt;/p&gt;

&lt;p&gt;That is why I would put a grubby used desktop in front of a CS student before I handed over a big cloud budget. When the machine only has a little room to spare, the operating system stops being chapter three in a textbook and starts acting like a real machine with limits.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>hardware</category>
      <category>tutorial</category>
      <category>developer</category>
    </item>
    <item>
      <title>GitLost: How AI Agents Can Leak Your Code Through a Single GitHub Issue</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Fri, 17 Jul 2026 02:00:00 +0000</pubDate>
      <link>https://dev.to/techiemike/gitlost-how-ai-agents-can-leak-your-code-through-a-single-github-issue-53f0</link>
      <guid>https://dev.to/techiemike/gitlost-how-ai-agents-can-leak-your-code-through-a-single-github-issue-53f0</guid>
      <description>&lt;h1&gt;
  
  
  GitLost: How AI Agents Can Leak Your Code Through a Single GitHub Issue
&lt;/h1&gt;

&lt;p&gt;A public GitHub issue should not be able to pull text out of your private repositories.&lt;/p&gt;

&lt;p&gt;With a dangerously over-permissioned GitHub Agentic Workflow, that is exactly what Noma Labs demonstrated.&lt;/p&gt;

&lt;p&gt;Their July 2026 write-up showed a simple but nasty chain: an attacker opens an issue in a public repository, an AI agent reads it as part of an automated workflow, the agent reaches into other repositories it can access, and the results come back as a public comment. No stolen login. No insider access. No clever memory corruption. Just a workflow that trusted the wrong text.&lt;/p&gt;

&lt;p&gt;I run Hermes agents against GitHub-adjacent workflows on my own kit, and this is the class of bug I worry about far more than model benchmark scores. Once an agent can read one place, write somewhere else, and decide its own next step in between, your problem is no longer "AI output quality." Your problem is access control.&lt;/p&gt;

&lt;p&gt;My view is blunt: if an agent can read private code and post publicly in the same run, the design is already wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GitLost actually showed
&lt;/h2&gt;

&lt;p&gt;Noma Labs published GitLost on 6 July 2026. The attack targeted GitHub's new agentic workflow pattern: Markdown-defined workflows tied to GitHub Actions, with an AI agent reading issues, calling tools, and replying automatically.&lt;/p&gt;

&lt;p&gt;The dangerous part was not "AI" in the abstract. It was the permission mix.&lt;/p&gt;

&lt;p&gt;According to Noma's write-up, the vulnerable pattern looked like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a public repository in the same GitHub organization as private repositories&lt;/li&gt;
&lt;li&gt;an automated workflow triggered by issue activity such as &lt;code&gt;issues.assigned&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;an agent that reads the issue title and body&lt;/li&gt;
&lt;li&gt;permission for that agent to read other repositories in the organization&lt;/li&gt;
&lt;li&gt;permission for that same agent to post back into the public issue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination is enough.&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%2Fm0w53she5akdu2hqdrob.webp" 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%2Fm0w53she5akdu2hqdrob.webp" alt="GitLost attack flow showing a public GitHub issue steering an AI agent to read a private repository and post the contents back into a public comment" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Noma's proof of concept showed the agent fetching &lt;code&gt;README.md&lt;/code&gt; from both a public repo and a private repo, then posting the contents back into a public comment thread where anyone could read them. Their write-up says the leaked files came from repositories named &lt;code&gt;poc&lt;/code&gt; and &lt;code&gt;testlocal&lt;/code&gt;, and that the workflow was triggered after the public issue was assigned.&lt;/p&gt;

&lt;p&gt;That matters because it turns a boring workflow event into a trust-boundary failure.&lt;/p&gt;

&lt;p&gt;A lot of people still hear "prompt injection" and imagine someone typing "ignore previous instructions" into ChatGPT for fun. This is not that. This is a live automation path with real repository permissions attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  The attack is simple because the system is over-trusted
&lt;/h2&gt;

&lt;p&gt;The rough sequence looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An attacker opens an issue in a public repository.&lt;/li&gt;
&lt;li&gt;The issue looks harmless enough to survive normal human scanning.&lt;/li&gt;
&lt;li&gt;The workflow assigns or otherwise triggers the agent.&lt;/li&gt;
&lt;li&gt;The agent reads the issue body as part of its input.&lt;/li&gt;
&lt;li&gt;Hidden instructions in that issue push the agent to read other repositories and publish what it finds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Noma says one small wording change, adding the word "Additionally", was enough in their testing to push the model around GitHub's intended guardrails and make it reframe the answer instead of refusing it.&lt;/p&gt;

&lt;p&gt;That is the part people need to sit with.&lt;/p&gt;

&lt;p&gt;This was not a case of the attacker breaking GitHub authentication. The attacker used the workflow exactly as exposed. The workflow itself bridged public input and private data too freely.&lt;/p&gt;

&lt;p&gt;If you teach computer science, the classroom analogy is straightforward. This is like letting a student write a note on the whiteboard, having an assistant treat that note as an instruction sheet, then letting the same assistant walk into the locked filing cabinet and pin the contents back on the classroom door.&lt;/p&gt;

&lt;p&gt;The filing cabinet is not the weak point there. The procedure is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters even if you do not use GitHub's exact setup
&lt;/h2&gt;

&lt;p&gt;Most people will read GitLost and ask, "Am I affected if I do not use GitHub Agentic Workflows?"&lt;/p&gt;

&lt;p&gt;Maybe not by this exact proof of concept.&lt;/p&gt;

&lt;p&gt;But the broader lesson applies to every agent stack, including self-hosted ones.&lt;/p&gt;

&lt;p&gt;I wrote recently about &lt;a href="https://www.techiemike.com/right-to-local-intelligence/" rel="noopener noreferrer"&gt;why local AI matters&lt;/a&gt; and about &lt;a href="https://www.techiemike.com/ollama-security-self-hosted-ai/" rel="noopener noreferrer"&gt;securing self-hosted Ollama&lt;/a&gt;. Running your models locally does help with privacy. It stops your prompts and code from automatically leaving for a cloud provider.&lt;/p&gt;

&lt;p&gt;It does not solve this problem.&lt;/p&gt;

&lt;p&gt;GitLost is not mainly a model-hosting problem. It is an orchestration problem.&lt;/p&gt;

&lt;p&gt;If your agent can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read public issues or comments&lt;/li&gt;
&lt;li&gt;access private repositories, secrets, or internal docs&lt;/li&gt;
&lt;li&gt;open pull requests, comments, emails, or tickets automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then you have built a system where untrusted text can influence trusted actions.&lt;/p&gt;

&lt;p&gt;That is the trust boundary that matters.&lt;/p&gt;

&lt;p&gt;I see the same misunderstanding in a lot of AI tool conversations. People obsess over which model is smartest and ignore which tool has the broadest token, the loosest repository scope, or the most dangerous default action. That is backwards. In practice, the model is often the least interesting part. I made the same argument in my post on &lt;a href="https://www.techiemike.com/multi-agent-debate-ai-architecture/" rel="noopener noreferrer"&gt;multi-agent debate and AI architecture&lt;/a&gt;: the intelligence people admire usually lives in the orchestration, and so do the failure modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real mistake: one agent, too many powers
&lt;/h2&gt;

&lt;p&gt;The cleanest way to think about GitLost is this: one actor in the system was allowed to do too many kinds of work.&lt;/p&gt;

&lt;p&gt;It could inspect untrusted public text.&lt;br&gt;
It could fetch private information.&lt;br&gt;
It could speak publicly.&lt;/p&gt;

&lt;p&gt;That is too much authority for one automated step.&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%2F9px232d3x8gez3w4wy5e.webp" 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%2F9px232d3x8gez3w4wy5e.webp" alt="Diagram showing safer AI agent permission design: separate agents or tokens for reading public issues, accessing private repositories, and posting externally" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If I gave a student account permission to read every exam paper, every answer sheet, and every staff note, I would not also let that same account publish messages to the whole class without review. Yet that is roughly what some agent workflows are doing.&lt;/p&gt;

&lt;p&gt;And this is why GitLost is a better lesson than a lot of headline-friendly AI panic. It exposes a design error you can actually fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five changes that reduce this risk fast
&lt;/h2&gt;

&lt;p&gt;None of these make an agent workflow "safe." That word is too absolute for security work. They do reduce the attack surface.&lt;/p&gt;

&lt;p&gt;GitHub Agentic Workflows are read-only by default and separate write operations through safe outputs, but those controls cannot compensate for an agent that has unnecessarily broad read access and is allowed to publish sensitive output.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Scope each agent to one repository whenever possible
&lt;/h3&gt;

&lt;p&gt;Cross-repository access is convenient, but it is also how a public issue in one repo turns into a leak from another.&lt;/p&gt;

&lt;p&gt;If a workflow only needs access to the repository where the issue was opened, give it only that. If a second private repository is genuinely required, make that an explicit separate step with its own token and its own review point.&lt;/p&gt;

&lt;p&gt;This is the same least-privilege logic that applies to any secret or service account. If the agent does not need the key, do not hand it the key.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Split public-writing steps away from private-reading steps
&lt;/h3&gt;

&lt;p&gt;This is the biggest architectural fix.&lt;/p&gt;

&lt;p&gt;Do not let the same workflow run both of these operations automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read internal or private material&lt;/li&gt;
&lt;li&gt;publish directly to a public thread&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need both, add a gate between them. Make the first step collect data into a private review channel. For sensitive workflows, require human approval before anything goes back out publicly.&lt;/p&gt;

&lt;p&gt;Yes, this slows the workflow down.&lt;/p&gt;

&lt;p&gt;Good.&lt;/p&gt;

&lt;p&gt;Security controls that cost nothing usually protect nothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Treat issues, pull requests, and comments as untrusted input
&lt;/h3&gt;

&lt;p&gt;Every issue body, PR description, code comment, changelog, and Markdown file the agent reads should be treated as hostile until proven otherwise.&lt;/p&gt;

&lt;p&gt;That does not mean you stop using automation. It means you stop pretending user-controlled text is just data. In an agentic system, text is also control surface.&lt;/p&gt;

&lt;p&gt;This is why I still like a simpler &lt;a href="https://www.techiemike.com/self-hosted-llms-with-docker-ollama-and-open-webui/" rel="noopener noreferrer"&gt;self-hosted LLM setup with Docker, Ollama, and Open WebUI&lt;/a&gt; for a lot of personal work. A local chat interface that helps me think is a very different risk profile from a workflow agent with repository permissions and public posting rights.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Use narrower tokens and installation scopes
&lt;/h3&gt;

&lt;p&gt;Org-wide access is comfortable for setup and dangerous in production.&lt;/p&gt;

&lt;p&gt;Use repository-specific installation permissions where you can. Separate read access from write access. Separate code access from issue-management access. Separate internal automation from external-facing bots.&lt;/p&gt;

&lt;p&gt;The goal is not elegance. The goal is blast-radius reduction.&lt;/p&gt;

&lt;p&gt;When something goes wrong, you want one repo affected, not twenty.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Log the agent's tool calls like you would log an admin session
&lt;/h3&gt;

&lt;p&gt;If an agent fetches a private file because a public issue nudged it there, you want a trail.&lt;/p&gt;

&lt;p&gt;Log which trigger fired.&lt;br&gt;
Log what repositories were touched.&lt;br&gt;
Log what tools were called.&lt;br&gt;
Log what text was posted back out.&lt;/p&gt;

&lt;p&gt;Without that, your first sign of trouble is often the leaked output itself.&lt;/p&gt;

&lt;p&gt;I would also add one practical rule: run new workflows in dry-run mode against test repositories first. If a public comment can influence a private read in staging, it will do it in production too.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GitLost teaches CS students
&lt;/h2&gt;

&lt;p&gt;This is a strong teaching example because it bundles several core ideas into one incident.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication is not authorization
&lt;/h3&gt;

&lt;p&gt;The attacker did not need to break into the private repository directly. The system's own authorized agent did the reading on the attacker's behalf.&lt;/p&gt;

&lt;p&gt;That is an authorization design failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Public input can change private outcomes
&lt;/h3&gt;

&lt;p&gt;Students often think of user input as something that affects only the page or form it was typed into. Agent systems break that mental model. A sentence written in one place can trigger actions somewhere else entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security is a property of the whole system
&lt;/h3&gt;

&lt;p&gt;The model, the workflow trigger, the GitHub token, the repository permissions, and the response path all matter together. Focusing on only one piece misses the point.&lt;/p&gt;

&lt;p&gt;That is why I find incidents like this more useful than generic AI doom-posting. They are concrete. You can diagram the data flow. You can point to the trust boundary. You can explain exactly where the design went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  My bottom line on agent workflows
&lt;/h2&gt;

&lt;p&gt;I like agents. I use them. I think they are useful.&lt;/p&gt;

&lt;p&gt;I also think a lot of people are wiring them into production systems far too casually.&lt;/p&gt;

&lt;p&gt;GitLost is the warning shot. Not because AI is uniquely dangerous, but because it shows how quickly old access-control mistakes turn into leaks when you automate them.&lt;/p&gt;

&lt;p&gt;If your agent reads public text, touches private code, and posts publicly without a human checkpoint, fix that before you worry about the next model upgrade.&lt;/p&gt;

&lt;p&gt;That is the part worth taking seriously.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://noma.security/blog/gitlost-how-we-tricked-githubs-ai-agent-into-leaking-private-repos/" rel="noopener noreferrer"&gt;Noma Labs: GitLost — How We Tricked GitHub's AI Agent into Leaking Private Repos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=48827858" rel="noopener noreferrer"&gt;Hacker News discussion of GitLost&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.techiemike.com/ollama-security-self-hosted-ai/" rel="noopener noreferrer"&gt;My post on self-hosted AI security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.techiemike.com/multi-agent-debate-ai-architecture/" rel="noopener noreferrer"&gt;My post on multi-agent debate and AI architecture&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>github</category>
      <category>security</category>
      <category>devops</category>
      <category>ai</category>
    </item>
    <item>
      <title>Linux Signals Explained — SIGTERM, SIGKILL and Process Control</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Thu, 16 Jul 2026 02:15:01 +0000</pubDate>
      <link>https://dev.to/techiemike/linux-signals-explained-sigterm-sigkill-and-process-control-1nc0</link>
      <guid>https://dev.to/techiemike/linux-signals-explained-sigterm-sigkill-and-process-control-1nc0</guid>
      <description>&lt;h1&gt;
  
  
  Linux Signals Explained — SIGTERM, SIGKILL and Process Control
&lt;/h1&gt;

&lt;p&gt;Pressing &lt;code&gt;Ctrl+C&lt;/code&gt; looks simple until the day it stops working.&lt;/p&gt;

&lt;p&gt;You hit it in a terminal. One Python script exits immediately. A Docker container hangs for ten seconds before disappearing. A runaway process ignores you completely until &lt;code&gt;kill -9&lt;/code&gt; ends the argument.&lt;/p&gt;

&lt;p&gt;That difference is signals.&lt;/p&gt;

&lt;p&gt;I teach Cambridge Computer Science, and this is the gap the syllabus never bridges cleanly. Students learn hardware interrupts, interrupt priority levels, and the interrupt dispatch table. Then they sit down at a Linux terminal and meet &lt;code&gt;SIGINT&lt;/code&gt;, &lt;code&gt;SIGTERM&lt;/code&gt;, and &lt;code&gt;SIGKILL&lt;/code&gt; with no explanation of how those ideas connect. In practice, signals are the missing link. They are how the kernel talks to processes.&lt;/p&gt;

&lt;p&gt;My view is blunt: signals should be taught right after interrupt handling, not left as Unix trivia for students to trip over later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ctrl+C is SIGINT, not magic
&lt;/h2&gt;

&lt;p&gt;When you press &lt;code&gt;Ctrl+C&lt;/code&gt;, the terminal sends &lt;code&gt;SIGINT&lt;/code&gt; to the foreground process group. The default action is termination, but a program can catch it and decide what to do next.&lt;/p&gt;

&lt;p&gt;That is why a shell script can react to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;'echo "Caught SIGINT"; exit'&lt;/span&gt; INT
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;1
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"still running"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that, press &lt;code&gt;Ctrl+C&lt;/code&gt;, and the script does not just vanish. It receives a signal, runs the handler, prints the message, and exits cleanly.&lt;/p&gt;

&lt;p&gt;Python does the same thing in its own way. When you press &lt;code&gt;Ctrl+C&lt;/code&gt; during a Python program, Python translates &lt;code&gt;SIGINT&lt;/code&gt; into &lt;code&gt;KeyboardInterrupt&lt;/code&gt;. Students often think the traceback is the problem. It is actually the clue. The interpreter is telling you that the kernel interrupted the process and Python surfaced it in a readable form.&lt;/p&gt;

&lt;p&gt;This matters because it shows the first rule of signals: a signal is not always an instant kill switch. Sometimes it is a request. The process gets a chance to respond.&lt;/p&gt;

&lt;h2&gt;
  
  
  SIGTERM is the polite knock
&lt;/h2&gt;

&lt;p&gt;If you run &lt;code&gt;kill 1234&lt;/code&gt; with no extra option, Linux sends &lt;code&gt;SIGTERM&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the normal shutdown signal. It tells the process to finish what it is doing and leave properly. A well-behaved process catches &lt;code&gt;SIGTERM&lt;/code&gt;, flushes buffers, closes files, saves state, and exits with dignity.&lt;/p&gt;

&lt;p&gt;This is the distinction I care about on my own Linux boxes. When I stop services on a Docker host, I do not want a process ripped out of memory halfway through a write. I want it to shut down cleanly.&lt;/p&gt;

&lt;p&gt;Here is the simplest Python example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;running&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Received signal &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;signum&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Closing cleanly...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGTERM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;working...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Buffers flushed. Connections closed.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also explains what Docker is doing. &lt;code&gt;docker stop&lt;/code&gt; does not begin with &lt;code&gt;SIGKILL&lt;/code&gt;. It sends &lt;code&gt;SIGTERM&lt;/code&gt; first, waits for the grace period, then escalates only if the process refuses to go.&lt;/p&gt;

&lt;p&gt;That is why a bad container shutdown usually looks like a ten-second pause. Docker is giving PID 1 inside the container time to behave.&lt;/p&gt;

&lt;p&gt;If you have read my post on &lt;a href="https://www.techiemike.com/learning-docker-by-building-a-container-engine-from-scratch/" rel="noopener noreferrer"&gt;building a container engine from scratch&lt;/a&gt;, this is the practical side of the same kernel mechanics. Containers are just processes with isolation wrapped around them. They still live and die by signals.&lt;/p&gt;

&lt;p&gt;The Redis example is a good one here. In my &lt;a href="https://www.techiemike.com/self-host-redis-8-8-with-docker-compose-and-why-its-useful-for-your-homelab-3/" rel="noopener noreferrer"&gt;Redis 8.8 homelab guide&lt;/a&gt;, the container is useful only if its shutdown is clean. Databases, caches, and queue workers need time to flush work to disk. If you know Docker sends &lt;code&gt;SIGTERM&lt;/code&gt; first and &lt;code&gt;SIGKILL&lt;/code&gt; later, &lt;code&gt;stop_grace_period&lt;/code&gt; stops looking like a random YAML setting and starts looking like what it really is: extra time for the process to do the right thing.&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%2F6rzvqy0hdt011tklrsj8.webp" 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%2F6rzvqy0hdt011tklrsj8.webp" alt="Docker graceful shutdown timeline showing a container receiving SIGTERM, a 10-second grace period, and SIGKILL only if the process does not exit in time" width="800" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  SIGKILL is the last resort
&lt;/h2&gt;

&lt;p&gt;Signal 9 cannot be caught, blocked, or ignored. The kernel does not negotiate. It removes the process from the scheduler, tears down its resources, and that is the end of it.&lt;/p&gt;

&lt;p&gt;This is why &lt;code&gt;kill -9&lt;/code&gt; works when &lt;code&gt;Ctrl+C&lt;/code&gt; does not.&lt;/p&gt;

&lt;p&gt;You can prove the difference with a deliberately stubborn script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIG_IGN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it. Press &lt;code&gt;Ctrl+C&lt;/code&gt;. Nothing happens, because the process is ignoring &lt;code&gt;SIGINT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now find the PID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pgrep &lt;span class="nt"&gt;-af&lt;/span&gt; python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then send:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-9&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Students remember this one because it feels dramatic, but the real lesson is not to reach for &lt;code&gt;kill -9&lt;/code&gt; as a habit. The real lesson is the opposite. Use it only when the polite options failed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SIGKILL&lt;/code&gt; gives the process no chance to flush, save, or clean up. If you fire it at a database, log writer, or container doing disk work, you are choosing force over correctness.&lt;/p&gt;

&lt;p&gt;It matters in container land too. Docker eventually escalates to &lt;code&gt;SIGKILL&lt;/code&gt; for the same reason you do: sometimes a process is stuck and has to go. The Linux OOM killer may terminate a process using &lt;code&gt;SIGKILL&lt;/code&gt;. A container killed with &lt;code&gt;SIGKILL&lt;/code&gt; commonly exits with code 137. Running out of memory is one possible cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  The signals students meet without realising it
&lt;/h2&gt;

&lt;p&gt;Here are the ones worth knowing first. If you want the canonical reference for default actions and edge cases, the Linux &lt;code&gt;signal(7)&lt;/code&gt; page at &lt;a href="https://man7.org/linux/man-pages/man7/signal.7.html" rel="noopener noreferrer"&gt;man7.org&lt;/a&gt; is the one worth bookmarking.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Number&lt;/th&gt;
&lt;th&gt;Default action&lt;/th&gt;
&lt;th&gt;What it usually means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SIGHUP&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Terminate&lt;/td&gt;
&lt;td&gt;Terminal closed, or daemon reload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGINT&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Terminate&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Ctrl+C&lt;/code&gt; from keyboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGKILL&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Terminate&lt;/td&gt;
&lt;td&gt;Forced kill by kernel or admin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGSEGV&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Core dump&lt;/td&gt;
&lt;td&gt;Invalid memory access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGPIPE&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Terminate&lt;/td&gt;
&lt;td&gt;Wrote to a pipe with no reader&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGTERM&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Terminate&lt;/td&gt;
&lt;td&gt;Normal shutdown request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGCONT&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;Continue&lt;/td&gt;
&lt;td&gt;Resume a stopped process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGSTOP&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;Stop&lt;/td&gt;
&lt;td&gt;Forced stop that cannot be ignored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIGTSTP&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;Stop&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Ctrl+Z&lt;/code&gt; from terminal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  SIGPIPE: why &lt;code&gt;yes | head -5&lt;/code&gt; stops
&lt;/h3&gt;

&lt;p&gt;This one is all over Linux even if nobody names it.&lt;/p&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;yes&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;yes&lt;/code&gt; would happily print forever. &lt;code&gt;head&lt;/code&gt; reads five lines and closes its end of the pipe. When &lt;code&gt;yes&lt;/code&gt; tries to write again, the kernel sends &lt;code&gt;SIGPIPE&lt;/code&gt;. Default action: terminate.&lt;/p&gt;

&lt;p&gt;That is why the command ends cleanly instead of flooding your terminal forever.&lt;/p&gt;

&lt;p&gt;If you have already worked through my post on &lt;a href="https://www.techiemike.com/linux-pipes-redirection/" rel="noopener noreferrer"&gt;Linux pipes and redirection&lt;/a&gt;, this is the next piece of the picture. Pipes are not just text plumbing. They are kernel objects with rules, and one of those rules is that a writer with no readers gets told to stop.&lt;/p&gt;

&lt;h3&gt;
  
  
  SIGHUP: why a remote job dies when SSH drops
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;SIGHUP&lt;/code&gt; began as the hangup signal for a terminal line going away. The name is old, but the behaviour still matters.&lt;/p&gt;

&lt;p&gt;A student starts a long-running script over SSH, closes the laptop, comes back later, and the process is gone. That is usually not mysterious. The session closed, the shell's children got &lt;code&gt;SIGHUP&lt;/code&gt;, and the default action terminated them.&lt;/p&gt;

&lt;p&gt;The fix is not magic either. Use &lt;code&gt;nohup&lt;/code&gt;, &lt;code&gt;tmux&lt;/code&gt;, or &lt;code&gt;screen&lt;/code&gt; so the process is not tied to that one terminal session.&lt;/p&gt;

&lt;h3&gt;
  
  
  SIGSTOP and SIGCONT: pausing a process without killing it
&lt;/h3&gt;

&lt;p&gt;Start a job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Press &lt;code&gt;Ctrl+Z&lt;/code&gt; and the shell sends &lt;code&gt;SIGTSTP&lt;/code&gt;, which is the terminal stop signal. Then you can continue it with &lt;code&gt;fg&lt;/code&gt; or send &lt;code&gt;SIGCONT&lt;/code&gt; yourself.&lt;/p&gt;

&lt;p&gt;You can also force the stop with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-STOP&lt;/span&gt; 
&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-CONT&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That maps nicely onto Cambridge process states. A stopped process is not finished. It is paused until the kernel allows it to continue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Segmentation faults are signals too
&lt;/h2&gt;

&lt;p&gt;A segmentation fault is usually &lt;code&gt;SIGSEGV&lt;/code&gt;, signal 11. The process touched memory it was not allowed to touch, so the kernel terminated it. That is process protection doing exactly what it should do.&lt;/p&gt;

&lt;p&gt;This is one of the clearest places where operating system theory becomes real. The OS is enforcing memory boundaries between processes. Without that rule, one broken program could scribble over everything else in RAM.&lt;/p&gt;

&lt;p&gt;So when you see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Segmentation fault (core dumped)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that is not just an error message. It is the kernel reporting a memory protection failure and sending the signal that ends the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cambridge connection: signals and interrupt handling
&lt;/h2&gt;

&lt;p&gt;This is the part students should be taught explicitly.&lt;/p&gt;

&lt;p&gt;In Chapter 16, Cambridge students learn the interrupt model: a device raises an interrupt, the CPU saves state, the interrupt dispatch table is consulted, a service routine runs, then the previous state is restored and execution continues.&lt;/p&gt;

&lt;p&gt;Signal delivery follows the same pattern at process level.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An event occurs: keyboard interrupt, parent command, broken pipe, invalid memory access.&lt;/li&gt;
&lt;li&gt;The kernel marks a signal as pending for the process or thread.&lt;/li&gt;
&lt;li&gt;When control is returning to user mode, the kernel checks whether that signal is blocked, ignored, or caught.&lt;/li&gt;
&lt;li&gt;If a handler exists, the kernel arranges for that handler to run.&lt;/li&gt;
&lt;li&gt;After the handler finishes, &lt;code&gt;sigreturn&lt;/code&gt; restores the saved state.&lt;/li&gt;
&lt;li&gt;The process continues, exits, stops, or dumps core depending on the signal and disposition.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is not a loose analogy. It maps neatly onto the same interrupt pattern students already know, just at process level rather than hardware level.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://www.techiemike.com/youre-the-os-teaching-process-memory-and-i-o-management-through-gaming/" rel="noopener noreferrer"&gt;You're the OS&lt;/a&gt;, I teach interrupt handling as a game because students understand it faster when they have to make the decisions themselves. Signals fit that same lesson perfectly. The kernel is still the traffic controller. The only difference is that now the conversation is between the kernel and a process rather than the CPU and a hardware device.&lt;/p&gt;

&lt;p&gt;You can even inspect part of this on a live Linux system in &lt;code&gt;/proc//status&lt;/code&gt;. Try &lt;code&gt;grep -E "Sig(Pnd|Blk|Ign|Cgt)" /proc/1/status&lt;/code&gt; and you will see the signal bitmaps immediately. Fields such as &lt;code&gt;SigPnd&lt;/code&gt;, &lt;code&gt;SigBlk&lt;/code&gt;, &lt;code&gt;SigIgn&lt;/code&gt;, and &lt;code&gt;SigCgt&lt;/code&gt; show which signals are pending, blocked, ignored, or caught. That is not abstract theory. That is the kernel's process-control dashboard sitting in plain text.&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%2F0iczwcb06gjyxbzaacae.webp" 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%2F0iczwcb06gjyxbzaacae.webp" alt="Kernel-to-process signal flow diagram showing event generation, pending signal check, handler execution, and sigreturn restoring process state — the software mirror of interrupt handling" width="800" height="750"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What students should remember
&lt;/h2&gt;

&lt;p&gt;If you remember only four things, remember these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Ctrl+C&lt;/code&gt; sends &lt;code&gt;SIGINT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kill&lt;/code&gt; sends &lt;code&gt;SIGTERM&lt;/code&gt; by default.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kill -9&lt;/code&gt; sends &lt;code&gt;SIGKILL&lt;/code&gt;, and the process gets no say in the matter.&lt;/li&gt;
&lt;li&gt;A lot of weird Linux behaviour is not weird at all once you know which signal fired.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That includes broken SSH jobs, stuck containers, dead pipelines, and segfaults.&lt;/p&gt;

&lt;p&gt;Signals are not obscure Unix trivia. They are core process control. They explain how the kernel interrupts, stops, resumes, warns, and kills running programs. For CS students, they are the cleanest bridge between textbook interrupt handling and the real behaviour of a Linux system.&lt;/p&gt;

&lt;p&gt;The next time a process ignores &lt;code&gt;Ctrl+C&lt;/code&gt;, ask a better question: which signal did it get, and what is it allowed to do with it?&lt;/p&gt;

</description>
      <category>linux</category>
      <category>tutorial</category>
      <category>processes</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Proxmox VE in Production — What Running It 24/7 for Over a Year Actually Teaches You</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Wed, 15 Jul 2026 02:00:00 +0000</pubDate>
      <link>https://dev.to/techiemike/proxmox-ve-in-production-what-running-it-247-for-over-a-year-actually-teaches-you-iil</link>
      <guid>https://dev.to/techiemike/proxmox-ve-in-production-what-running-it-247-for-over-a-year-actually-teaches-you-iil</guid>
      <description>&lt;h1&gt;
  
  
  Proxmox VE in Production — What Running It 24/7 for Over a Year Actually Teaches You
&lt;/h1&gt;

&lt;p&gt;The moment an outage interrupts something you actually use every day, your homelab has stopped behaving like a toy.&lt;/p&gt;

&lt;p&gt;That is the cleanest definition of production I know.&lt;/p&gt;

&lt;p&gt;I have been running Proxmox VE 24/7 for over a year on my &lt;a href="https://www.techiemike.com/chuwi-minibook-x-vs-bmax-pro-8-which-budget-homelab-machine/" rel="noopener noreferrer"&gt;BMAX Pro 8&lt;/a&gt;. On paper, it is still a homelab box. In practice, Plex, Nextcloud, and Home Assistant all depend on it staying healthy. That changes the standard immediately. You stop caring about how quickly you can spin up a VM and start caring about how calmly you can recover when something breaks.&lt;/p&gt;

&lt;p&gt;Most Proxmox posts online are about installation, upgrade paths, or first impressions. That is useful, and I have already covered the build side in my &lt;a href="https://www.techiemike.com/proxmox-homelab-setup-on-a-mini-pc-ubuntu-vms-beyond/" rel="noopener noreferrer"&gt;Proxmox homelab setup on a mini PC guide&lt;/a&gt;. What those posts usually skip is the boring middle: the long stable stretches, the one migration that runs far too long, the backup routine you should have tightened earlier, and the network blip that changes how you think about remote access.&lt;/p&gt;

&lt;p&gt;After 400+ days, my opinion is simple: Proxmox is excellent, but it rewards boring operators and punishes optimistic ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production in a homelab still means consequences
&lt;/h2&gt;

&lt;p&gt;People get strangely hung up on the word production.&lt;/p&gt;

&lt;p&gt;If you are not billing customers, they assume it does not count. I think that misses the point. If a service is part of your normal day, if downtime immediately becomes your problem, and if recovery matters more than screenshots, you are already there.&lt;/p&gt;

&lt;p&gt;A home setup does not need a sales team behind it to deserve production habits. It only needs real consequences.&lt;/p&gt;

&lt;p&gt;When Plex buffers, someone notices. When Nextcloud disappears, the missing files matter. When Home Assistant falls over, the problem stops being an interesting virtualization issue and starts being a broken part of the day. Those are the same kinds of day-to-day services I wrote about in my &lt;a href="https://www.techiemike.com/self-hosting-toolkit-10-apps/" rel="noopener noreferrer"&gt;self-hosting toolkit&lt;/a&gt;, and they are exactly why I no longer treat uptime as a hobby metric.&lt;/p&gt;

&lt;p&gt;That is why I no longer judge infrastructure by how clever it looks. I judge it by how easy it is to understand when I am tired. Fancy is cheap. Recoverable is expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: Storage migrations are where confidence goes to die
&lt;/h2&gt;

&lt;p&gt;A fresh Proxmox install makes storage look neat and tidy. Local disks here. VM disks there. Buttons in the web interface. Progress bars. It all feels manageable.&lt;/p&gt;

&lt;p&gt;Then you do a real migration on a live box.&lt;/p&gt;

&lt;p&gt;One of the clearest lessons from this past year came from moving a VM disk between local datastores on the mini PC. On paper it was a routine reshuffle. In practice it ran for hours, and while it ran, the whole box crawled. Plex playback stuttered. Nextcloud got sluggish. The migration was saturating the machine's own disk IO, and every other service was standing in the same queue.&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%2Fm8bcxtgfchjdqng1e682.webp" 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%2Fm8bcxtgfchjdqng1e682.webp" alt="Storage migration visualization showing VM disk transfer between datastores" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is the part people underestimate on a single-node homelab. A storage move is not a copy that happens quietly in the background — it is an IO-bound operation that competes directly with everything your services need. If the guest is busy dirtying memory while it moves, the tail end drags even longer. And if your Proxmox system disk is something cheap and slow, that one weakness drags the entire host down with it. A storage migration is not housekeeping once the system is busy. It is an operational event, and it belongs in a maintenance window.&lt;/p&gt;

&lt;p&gt;This is where new homelab thinking usually goes wrong. You treat storage moves like rearranging folders on a laptop. Proxmox treats them like large, stateful operations touching the thing your services are standing on.&lt;/p&gt;

&lt;p&gt;The teacher version is simple: moving a VM disk sounds like changing seats in a classroom. It is not. It is more like moving the entire classroom while the lesson is still running.&lt;/p&gt;

&lt;p&gt;What changed for me after that long move?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I stopped doing storage work casually.&lt;/li&gt;
&lt;li&gt;I started assuming every simple move would take longer than I wanted.&lt;/li&gt;
&lt;li&gt;I became much more suspicious of doing anything big during the hours when the services were actually in use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That sounds obvious. It is obvious. The problem is that you only really believe it after one migration ruins your evening.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: Backups do not mature by themselves
&lt;/h2&gt;

&lt;p&gt;For a while, backups in a homelab often exist as a good intention.&lt;/p&gt;

&lt;p&gt;You know they matter. You mean to tighten them up. You tell yourself the current setup is fine for now.&lt;/p&gt;

&lt;p&gt;It usually is not.&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%2F4025krajeb20ybb9ahgv.webp" 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%2F4025krajeb20ybb9ahgv.webp" alt="Automated Proxmox Backup Server with retention policies" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My backup strategy evolved from manual &lt;code&gt;vzdump&lt;/code&gt; jobs to automated Proxmox Backup Server with retention policies. The real improvement was not the acronym. It was moving from "I should make a backup tonight" to scheduled, deduplicated backups that ran whether I was organised or not.&lt;/p&gt;

&lt;p&gt;Manual backups feel responsible because they produce files. Manual anything has the same weakness: it depends on you being awake, consistent, and not distracted by ten other jobs. That works for a week. It does not work for a year.&lt;/p&gt;

&lt;p&gt;What PBS changed was the shape of the problem. Instead of wondering whether I had remembered to capture a fresh state, I had a backup system with a schedule, retention, and an actual operational rhythm behind it. The policy is boring on purpose: a daily job at 2 AM, retention set to keep seven daily, four weekly, and six monthly snapshots, so I always have a recent restore point without the datastore growing forever. If you want the vendor view, the &lt;a href="https://pbs.proxmox.com/docs/" rel="noopener noreferrer"&gt;Proxmox Backup Server documentation&lt;/a&gt; is worth bookmarking because it makes the retention and datastore model much clearer than trial and error ever will.&lt;/p&gt;

&lt;p&gt;One habit matters more than the schedule itself: a weekly verify job, so PBS actually checks the backups are readable rather than just present. I will be honest about the one I do not keep — I have never sat down and tested a full restore. I have never needed to, which is either luck or a quiet risk I keep meaning to close, because a backup you have never restored is a hope, not a backup. One rule I do follow without exception: the backups do not live on the same physical disk as the VMs. If that disk dies, I do not want it taking the backups with it.&lt;/p&gt;

&lt;p&gt;Once the box started carrying important services, I stopped thinking about backups as insurance and started thinking about them as part of the service itself. If the restore path is weak, the service is weak. That is it.&lt;/p&gt;

&lt;p&gt;This is also where Proxmox teaches the same lesson every good sysadmin eventually learns: boring automation beats heroic memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: The network is the part you forget until it bites
&lt;/h2&gt;

&lt;p&gt;Every long-running setup has one moment that changes how you think about it.&lt;/p&gt;

&lt;p&gt;For me it was a brief network blip that, for a few minutes, cut me off from the box entirely. Nothing was on fire. The services kept running locally. But the reverse proxy in front of everything, the tunnel I use to reach it from outside the house, and my remote management were all suddenly unreachable at once. On a single-node homelab, that is the quiet risk nobody warns you about: the network path to your box is a single point, and when it wobbles, you lose the ability to see or fix anything precisely when you most want to.&lt;/p&gt;

&lt;p&gt;The network is not a side topic. It is the topic.&lt;/p&gt;

&lt;p&gt;I teach computer science, and this is the bit textbooks often flatten too much. On paper, a network is a tidy diagram with arrows. In practice, it is the one dependency sitting underneath every other dependency. When it drops, the dashboard that would tell you what is wrong is on the far side of the thing that is wrong.&lt;/p&gt;

&lt;p&gt;That blip made me much less casual about the phrase "just a brief network issue." Brief for whom? The link that recovers on its own in thirty seconds, or you, standing in a coffee shop unable to reach a single service until it does? It also made me plan for it: a way in that does not depend on the one path that just failed, and notes stored somewhere other than the machine I am trying to reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical rules I would write down on day one
&lt;/h2&gt;

&lt;p&gt;This is the section I wish more Proxmox posts included.&lt;/p&gt;

&lt;p&gt;Not philosophy. Operating rules.&lt;/p&gt;

&lt;p&gt;If I were setting up the same box again with the same requirement that these services need to stay dependable, I would write down this checklist on day one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A real maintenance window for any storage move, not "whenever I get time tonight."&lt;/li&gt;
&lt;li&gt;A PBS backup job on a schedule with a written retention policy — mine keeps seven daily, four weekly, and six monthly.&lt;/li&gt;
&lt;li&gt;Backups on a different physical disk from the VMs, so one dead drive cannot take both.&lt;/li&gt;
&lt;li&gt;A restore-test habit — the one I still owe myself — because successful backups do not prove successful recovery.&lt;/li&gt;
&lt;li&gt;Alerts that reach me before I stumble into a problem: disk usage crossing 80%, failed backup jobs, and offline services.&lt;/li&gt;
&lt;li&gt;A second way to reach the box that does not depend on the one network path that just failed.&lt;/li&gt;
&lt;li&gt;Clear notes stored outside the Proxmox host, so recovery steps do not disappear with the machine you are trying to recover.&lt;/li&gt;
&lt;li&gt;A power plan: UPS and a graceful shutdown, or at least a decision made on purpose instead of by accident.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that is glamorous. That is exactly why it matters.&lt;/p&gt;

&lt;p&gt;The romantic version of self-hosting is building the thing. The grown-up version is keeping it boring six months later.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 400 days actually teaches you
&lt;/h2&gt;

&lt;p&gt;Running Proxmox for a weekend tells you whether the install worked.&lt;/p&gt;

&lt;p&gt;Running it 24/7 for over a year tells you whether your habits work.&lt;/p&gt;

&lt;p&gt;Proxmox itself has been solid. The harder lessons came from operations: storage work takes longer than you think, backup maturity does not happen by accident, the network path to your box deserves more respect than the dashboard encourages, and small shortcuts become large problems once the box matters.&lt;/p&gt;

&lt;p&gt;So yes, you can run Proxmox VE in production.&lt;/p&gt;

&lt;p&gt;The better question is whether you are ready to run it like boring, dependable infrastructure. That is the standard that matters.&lt;/p&gt;

</description>
      <category>proxmox</category>
      <category>linux</category>
      <category>devops</category>
      <category>homelab</category>
    </item>
    <item>
      <title>The CISA GitHub Leak: What It Should Teach Homelab Owners</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Tue, 14 Jul 2026 02:00:01 +0000</pubDate>
      <link>https://dev.to/techiemike/the-cisa-github-leak-what-it-should-teach-homelab-owners-2p1l</link>
      <guid>https://dev.to/techiemike/the-cisa-github-leak-what-it-should-teach-homelab-owners-2p1l</guid>
      <description>&lt;h1&gt;
  
  
  The CISA GitHub Leak: Homelab Security Lessons for Self-Hosters
&lt;/h1&gt;

&lt;p&gt;In May 2026, researchers discovered a public GitHub repository maintained by a CISA contractor containing credentials, deployment material, internal documentation, and cloud infrastructure details, as detailed in &lt;a href="https://krebsonsecurity.com/2026/05/cisa-admin-leaked-aws-govcloud-keys-on-github/" rel="noopener noreferrer"&gt;Krebs on Security's report on the leak&lt;/a&gt;. CISA removed the repository and began investigating the exposure. The repository reportedly contained plaintext passwords, AWS tokens, certificates, infrastructure code, deployment files and internal documentation. Some credentials were reportedly still valid when discovered.&lt;/p&gt;

&lt;p&gt;That is not the same thing as some dramatic headline about CISA getting completely owned. It is a repository leak, and that distinction matters. It also happens to be the part self-hosters should care about most, because this is exactly the kind of mistake that turns an ordinary lab into an easy target.&lt;/p&gt;

&lt;p&gt;I think this is the more useful kind of security story anyway. A lot of homelab failures do not begin with a clever exploit. They begin with a secret in the wrong place, a service exposed longer than intended, or a trust boundary that only existed in your head.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep secrets out of repositories
&lt;/h2&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%2Fofbe4ybxw258t9vw6i3b.webp" 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%2Fofbe4ybxw258t9vw6i3b.webp" alt="Illustration of exposed credentials and commit history showing how secrets leaked from a GitHub repository can compromise a homelab" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most direct lesson from the CISA incident is not about firewalls or VLANs. It is about credential handling.&lt;/p&gt;

&lt;p&gt;Never commit passwords, API keys, private keys, access tokens, .env files, database exports, or backup archives to a repository. A repository named "private" is not a security control, and deleting a secret from the latest commit does not remove it from Git history.&lt;/p&gt;

&lt;p&gt;At minimum:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep secrets in environment files excluded by .gitignore&lt;/li&gt;
&lt;li&gt;use a password manager or secrets-management system&lt;/li&gt;
&lt;li&gt;enable repository secret scanning and push protection&lt;/li&gt;
&lt;li&gt;rotate a credential immediately if it has been committed&lt;/li&gt;
&lt;li&gt;inspect Git history, not only the current working tree&lt;/li&gt;
&lt;li&gt;give tokens only the permissions and lifetime they require&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A leaked credential should be treated as compromised even if there is no evidence that somebody used it.&lt;/p&gt;

&lt;p&gt;That last point is where people still talk themselves into delay. They tell themselves nothing bad happened, so there is nothing to rotate. I do not buy that. If a token was in a public repo, the safe assumption is that it is gone.&lt;/p&gt;

&lt;p&gt;The same principle showed up in my write-up on &lt;a href="https://www.techiemike.com/1-click-github-token-theft-via-vscode-bug-a-developer-security-wake-up-call/" rel="noopener noreferrer"&gt;GitHub token theft through a VS Code bug&lt;/a&gt;. The bug mattered, but the real damage came from what a leaked credential could unlock after it escaped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audit what is actually exposed
&lt;/h2&gt;

&lt;p&gt;A surprising number of homelabs run on assumption. The owner thinks only 80 and 443 are open because that is what they meant to expose six months ago.&lt;/p&gt;

&lt;p&gt;Check it from the outside.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nmap &lt;span class="nt"&gt;-Pn&lt;/span&gt; yourdomain.com
&lt;span class="nb"&gt;sudo &lt;/span&gt;nmap &lt;span class="nt"&gt;-Pn&lt;/span&gt; &lt;span class="nt"&gt;-p-&lt;/span&gt; your-public-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first command checks common TCP ports. The second scans all TCP ports and takes longer. UDP requires a separate scan.&lt;/p&gt;

&lt;p&gt;Run the external scan against your known public IP from a device outside the home network. Do not rely on scanning a proxied hostname, because that may test Cloudflare's edge rather than your router or server.&lt;/p&gt;

&lt;p&gt;Also, scan only systems you own or have permission to test. That should be obvious, but it still needs saying.&lt;/p&gt;

&lt;p&gt;Then check the host itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ss &lt;span class="nt"&gt;-tulpn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you the local listening sockets. The question is not just what is running. The question is what is reachable from somewhere it should not be reachable from.&lt;/p&gt;

&lt;p&gt;If you self-host AI services, this matters even more because too many people publish them first and think about access control later. A service with weak or missing auth becomes a very different problem the moment it stops being localhost-only.&lt;/p&gt;

&lt;p&gt;One blunt rule helps here: if you no longer remember why a port is open, close it until you do remember.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put authentication in front of selected services
&lt;/h2&gt;

&lt;p&gt;Weak authentication still causes more real damage than most fancy attack chains.&lt;/p&gt;

&lt;p&gt;If a service supports MFA, turn it on. If it does not, put something in front of it that does. Authelia, Authentik, and &lt;a href="https://www.techiemike.com/cloudflare-self-managed-oauth-homelab-security/" rel="noopener noreferrer"&gt;Cloudflare Access&lt;/a&gt; are all reasonable ways to stop a login page from sitting naked on the internet.&lt;/p&gt;

&lt;p&gt;This only works when users cannot bypass the authentication proxy and connect directly to the backend service. Restrict the origin with firewall rules, private networking, or binding to localhost where appropriate.&lt;/p&gt;

&lt;p&gt;MFA and proxy authentication reduce the risk of stolen login passwords, but they do not necessarily protect API tokens, session cookies, application keys, or credentials that bypass the interactive login.&lt;/p&gt;

&lt;p&gt;That is why I like layered controls more than single-product confidence. A reverse proxy is useful. A reverse proxy plus localhost binding plus tight firewall rules is better.&lt;/p&gt;

&lt;p&gt;The point is the same either way: do not confuse a clean login page with real isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Segment the network properly
&lt;/h2&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%2Fgory46itl1qkrv7n2g4t.webp" 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%2Fgory46itl1qkrv7n2g4t.webp" alt="Network segmentation diagram showing separate VLANs for main devices, homelab servers, IoT devices, and guest Wi-Fi in a homelab" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Flat networks are lazy, and they are generous to attackers.&lt;/p&gt;

&lt;p&gt;If your laptop, phone, TV, random IoT plug, and Proxmox host all sit in one easy broadcast neighbourhood, one compromise can turn into a walk across your whole lab.&lt;/p&gt;

&lt;p&gt;A simple layout is already better than the usual everything-on-one-LAN mess:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Main devices: 192.168.10.0/24&lt;/li&gt;
&lt;li&gt;Homelab servers: 192.168.20.0/24&lt;/li&gt;
&lt;li&gt;IoT devices: 192.168.30.0/24&lt;/li&gt;
&lt;li&gt;Guest Wi-Fi: 192.168.40.0/24&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then make the policy boring and strict:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Guest reaches the internet, not your servers&lt;/li&gt;
&lt;li&gt;IoT reaches only what it actually needs&lt;/li&gt;
&lt;li&gt;Admin access to Proxmox, SSH, and dashboards comes from your trusted network only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different IP ranges alone do not provide segmentation. The network equipment must place them in separate VLANs or interfaces and enforce firewall rules between them.&lt;/p&gt;

&lt;p&gt;That is the part people skip. Changing the third octet is not segmentation. Enforcing traffic boundaries is segmentation.&lt;/p&gt;

&lt;p&gt;A compact setup is easier to secure because you can still explain it to yourself six months later. My &lt;a href="https://www.techiemike.com/proxmox-homelab-setup-on-a-mini-pc-ubuntu-vms-beyond/" rel="noopener noreferrer"&gt;Proxmox homelab setup on a mini PC&lt;/a&gt; and the &lt;a href="https://www.techiemike.com/chuwi-minibook-x-vs-bmax-pro-8-which-budget-homelab-machine/" rel="noopener noreferrer"&gt;BMAX Pro 8 budget homelab comparison&lt;/a&gt; both come back to the same point: extra complexity creates blind spots fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add brute-force protection and rate limits
&lt;/h2&gt;

&lt;p&gt;Anything with a login page gets hammered eventually.&lt;/p&gt;

&lt;p&gt;If you expose SSH, a reverse proxy, a dashboard, or any admin panel, assume bots are already trying it. Fail2ban still earns its place here, but only if it is actually watching the right logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;fail2ban
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A healthy jail might produce output similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Status for the jail: sshd
|- Filter
|  |- Currently failed: 3
|  `- Total failed:     19
`- Actions
   |- Currently banned: 1
   `- Total banned:     4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fail2ban must be connected to the correct application or proxy logs. When traffic passes through Cloudflare or another proxy, configure trusted proxy handling carefully so the system reads the real client address without trusting spoofed headers.&lt;/p&gt;

&lt;p&gt;Pair that with firewall rules that make sense for the host.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw default deny incoming
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 80/tcp
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 443/tcp
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw &lt;span class="nb"&gt;enable
sudo &lt;/span&gt;ufw status numbered
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are doing this over SSH, stage the allow rules first so you do not lock yourself out.&lt;/p&gt;

&lt;p&gt;Be aware that Docker manages its own firewall rules. A container port published on 0.0.0.0 may not behave as expected under a simple UFW policy. Check published ports with docker ps and test reachability externally.&lt;/p&gt;

&lt;p&gt;Reverse-proxy rate limits help too. They will not make a weak stack strong, but they do remove easy wins from bots that rely on unlimited retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patch deliberately and on schedule
&lt;/h2&gt;

&lt;p&gt;Most self-hosters do not ignore updates because they are careless. They ignore updates because the process is vague, annoying, and easy to postpone.&lt;/p&gt;

&lt;p&gt;That is why cadence matters. Pick a rhythm and make it boring.&lt;/p&gt;

&lt;p&gt;For Docker-based services, I still prefer two separate habits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;notice that an image changed&lt;/li&gt;
&lt;li&gt;decide when to roll it out&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is why I like tools such as Diun or Dockcheck. They tell you something moved without automatically changing production. Auto-update tools have their place, but I would be careful with anything that touches your reverse proxy, auth layer, or database while you are asleep.&lt;/p&gt;

&lt;p&gt;A safer update cycle looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose pull
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it when you can watch the result.&lt;/p&gt;

&lt;p&gt;If you harden containers, test those changes one at a time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;read_only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;security_opt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;no-new-privileges:true&lt;/span&gt;
&lt;span class="na"&gt;cap_drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ALL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test these settings one at a time. A read-only container may still have writable volumes, and dropping every capability may break software that requires a specific capability. Add back only what the application demonstrably needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start where the risk drops fastest
&lt;/h2&gt;

&lt;p&gt;Do not turn this into a heroic weekend project.&lt;/p&gt;

&lt;p&gt;Start with the fix that removes the most risk in your own setup. Sometimes that is rotating an exposed token. Sometimes it is closing a port you forgot about. Sometimes it is putting one fragile service behind a proper auth layer and binding the backend to localhost.&lt;/p&gt;

&lt;p&gt;The CISA contractor leak is a useful warning because it is ordinary. There was no magic to it. Just credentials in the wrong place, followed by the usual cleanup.&lt;/p&gt;

&lt;p&gt;That is exactly why homelab owners should pay attention.&lt;/p&gt;

</description>
      <category>security</category>
      <category>linux</category>
      <category>devops</category>
      <category>homelab</category>
    </item>
    <item>
      <title>Homelab Burnout: Sustainable Self-Hosting When the Hobby Starts Feeling Like Work</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Mon, 13 Jul 2026 02:00:01 +0000</pubDate>
      <link>https://dev.to/techiemike/homelab-burnout-sustainable-self-hosting-when-the-hobby-starts-feeling-like-work-3nh7</link>
      <guid>https://dev.to/techiemike/homelab-burnout-sustainable-self-hosting-when-the-hobby-starts-feeling-like-work-3nh7</guid>
      <description>&lt;h1&gt;
  
  
  Homelab Burnout: Sustainable Self-Hosting When the Hobby Starts Feeling Like Work
&lt;/h1&gt;

&lt;p&gt;Browse &lt;a href="https://www.reddit.com/r/homelab/" rel="noopener noreferrer"&gt;r/homelab&lt;/a&gt; for long enough and you will regularly find people questioning whether their homelab has become more work than fun. People are not tired of technology itself. They are tired of turning a useful hobby into an unpaid support contract.&lt;/p&gt;

&lt;p&gt;I teach computer science full-time, run a stack of self-hosted services, write here, and record YouTube videos. My homelab is useful, but only when it stays in its lane. The moment it starts demanding daily babysitting, it stops being a hobby and starts behaving like a badly managed side business.&lt;/p&gt;

&lt;p&gt;That is my opinion on this, and I will argue it plainly: a homelab should solve problems you actually have. The second it exists mainly to generate maintenance work, the learning value starts to disappear. You are just doing chores in Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  The homelab burnout escalation trap is real
&lt;/h2&gt;

&lt;p&gt;Most homelabs do not become messy in one big leap. They grow one perfectly reasonable decision at a time.&lt;/p&gt;

&lt;p&gt;You add Plex because you want your media in one place. Then you add Uptime Kuma because monitoring feels responsible. Then a reverse proxy, then a dashboard, then a note-taking tool, then an automation tool, then a second database because one app prefers Postgres and another wants Redis. A few weeks later you have twenty moving parts and half of them are there because they looked useful on somebody else's YouTube channel.&lt;/p&gt;

&lt;p&gt;That is how hobby scope creep works. Nobody wakes up and decides to build an unreliable mini data centre in the spare room. You just keep saying yes to one more service.&lt;/p&gt;

&lt;p&gt;I already trimmed my own stack once. In &lt;a href="https://www.techiemike.com/self-hosting-toolkit-10-apps/" rel="noopener noreferrer"&gt;Self-Hosting Toolkit: 10 Apps I Actually Use Every Day&lt;/a&gt;, the key phrase is "actually use." That was not branding. It was a survival rule. If a service does not save time, make money, teach me something worth keeping, or solve a regular problem in my week, it does not deserve a permanent slot.&lt;/p&gt;

&lt;p&gt;A lot of burnout comes from running software with no job description.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automation should remove toil, not create clever new failure modes
&lt;/h2&gt;

&lt;p&gt;Automation is the part everyone talks about, but many people automate the wrong things. They build a complicated chain of scripts, webhooks, and notifications, then spend Saturday debugging the automation instead of the original problem.&lt;/p&gt;

&lt;p&gt;Good homelab automation is boring. It cuts repeat work. It makes failure obvious. It does not need a diagram just to explain why your container restarted.&lt;/p&gt;

&lt;p&gt;A simple Docker health check earns its place because it answers a basic question: is this service alive or not?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&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="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app:1.4.2&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000/health"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This assumes the image includes curl and the application exposes a /health endpoint. Otherwise, use a command already available inside the container or add an appropriate health-check method.&lt;/p&gt;

&lt;p&gt;That is useful. It replaces guesswork with one small signal. Pinning a known version also makes updates more deliberate and rollbacks easier.&lt;/p&gt;

&lt;p&gt;The same applies to update routines. I do not want to remember which container needs a manual pull, which one changed its environment variables, and which one quietly stopped two weeks ago. If you are relying on memory for routine maintenance, you already have a fragile system.&lt;/p&gt;

&lt;p&gt;This is also why I still like small, direct tooling. A maintenance script that checks container status, disk usage, and backup age is worth more than a beautiful dashboard I only open after something breaks. Monitoring should tell you what needs attention. It should not become another thing you maintain for its own sake. My &lt;a href="https://www.techiemike.com/hermes-agent-mini-pc-setup/" rel="noopener noreferrer"&gt;Hermes Agent mini PC setup guide&lt;/a&gt; comes from exactly that mindset: use automation to remove repeated admin work, not to build a second system you have to babysit.&lt;/p&gt;

&lt;p&gt;If you want the short version, build the minimum system that answers three questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the service up?&lt;/li&gt;
&lt;li&gt;Is the data backed up?&lt;/li&gt;
&lt;li&gt;Will I notice when something fails?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything beyond that needs to justify itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boundaries matter more than enthusiasm
&lt;/h2&gt;

&lt;p&gt;This is the part many homelab guides skip because it is less fun than listing tools.&lt;/p&gt;

&lt;p&gt;You need boundaries.&lt;/p&gt;

&lt;p&gt;Not every service should be self-hosted. Some are worth the effort because they protect privacy, centralise your workflow, or save ongoing subscription costs. Others are cheap in dollars and expensive in attention.&lt;/p&gt;

&lt;p&gt;The best question I know is this: what happens if this service breaks for 48 hours?&lt;/p&gt;

&lt;p&gt;If the honest answer is "nothing important," it probably should not sit in the centre of your setup. It might still be a good weekend experiment. It just should not become permanent infrastructure.&lt;/p&gt;

&lt;p&gt;That is the difference between a lab and a dependency. Labs are where you test ideas. Dependencies are the things that quietly ruin your week when they go down.&lt;/p&gt;

&lt;p&gt;For me, the keepers are the services that support real work: file storage, monitoring, automation, remote access, and a few tools I touch constantly. A smaller stack is not less serious. It is usually better engineered because you can still understand it on a tired Tuesday night. The same bias toward manageable infrastructure is why I built my &lt;a href="https://www.techiemike.com/proxmox-homelab-setup-on-a-mini-pc-ubuntu-vms-beyond/" rel="noopener noreferrer"&gt;Proxmox homelab setup on a mini PC&lt;/a&gt; around hardware I can understand, recover, and afford to leave running.&lt;/p&gt;

&lt;p&gt;A lot of people also underestimate security overhead here. Every exposed dashboard, admin panel, and reverse proxy route increases the attack surface. Limiting permanent services is therefore not only a maintenance decision; it is also a security discipline. That is why I treat access control and certificates as infrastructure, not decoration. If you use Cloudflare and want an additional authentication layer in front of selected services, &lt;a href="https://www.techiemike.com/cloudflare-self-managed-oauth-homelab-security/" rel="noopener noreferrer"&gt;Cloudflare Self-Managed OAuth&lt;/a&gt; is worth understanding. It complements rather than replaces secure configuration, updates, certificates, and network controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation is what saves you at 2 AM
&lt;/h2&gt;

&lt;p&gt;The most underrated homelab tool is not Docker, Proxmox, or a reverse proxy. It is documentation.&lt;/p&gt;

&lt;p&gt;I mean boring documentation. The kind that tells you which compose file matters, which ports are already in use, which volume paths hold real data, which containers depend on each other, and what to restore first after a bad update.&lt;/p&gt;

&lt;p&gt;Without that, every outage becomes archaeology.&lt;/p&gt;

&lt;p&gt;This is where my Obsidian vault earns its keep. I keep service notes, recovery steps, container locations, backup paths, and configuration decisions in one place. Not because it is elegant, but because I do not trust the tired version of myself to remember why something was set up a certain way six months ago.&lt;/p&gt;

&lt;p&gt;A useful service note can be brutally simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Service: Uptime Kuma
Compose file: /opt/monitoring/docker-compose.yml
Data path: /opt/docker/uptime-kuma
Depends on: reverse proxy + local DNS
Backup: nightly tar to external drive
Restore order: DNS -&amp;gt; proxy -&amp;gt; Kuma
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That note has saved more time than any fancy dashboard ever has.&lt;/p&gt;

&lt;p&gt;The same habit applies to anyone learning to build real systems. Sustainable engineering is not just about writing code that works today. It is about leaving enough context behind that the next person, or future you, can support it without panic. Real systems fail. Good documentation turns failure from chaos into a checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  The economics are not just about money
&lt;/h2&gt;

&lt;p&gt;Self-hosters love talking about savings, and sometimes the savings are real. A small mini PC, a couple of drives, and a handful of containers can absolutely replace several monthly subscriptions.&lt;/p&gt;

&lt;p&gt;But cost is not just hardware plus electricity.&lt;/p&gt;

&lt;p&gt;Time is part of the bill.&lt;/p&gt;

&lt;p&gt;If a service saves you $4 a month but costs two hours of maintenance every other weekend, that is not frugal. That is expensive in a different currency. The maths gets worse if the service is something you barely use.&lt;/p&gt;

&lt;p&gt;This is why I think the healthiest homelab mindset is selective self-hosting. Keep the things that give you one of these returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;control over important data&lt;/li&gt;
&lt;li&gt;a workflow improvement you genuinely notice&lt;/li&gt;
&lt;li&gt;a solid learning payoff&lt;/li&gt;
&lt;li&gt;a clear cost saving without constant babysitting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If it gives you none of those, it is probably clutter.&lt;/p&gt;

&lt;p&gt;I also think there is a phase many homelabbers need to go through once: the purge. Not a dramatic teardown, just an honest audit. Open the compose files. List the services. Ask what each one is doing for you now, not what you hoped it would do when you installed it.&lt;/p&gt;

&lt;p&gt;Delete the tourists: services that arrived as experiments, contributed nothing, and never left.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick homelab sustainability audit
&lt;/h2&gt;

&lt;p&gt;For each permanent service, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did I use it during the last 30 days?&lt;/li&gt;
&lt;li&gt;Does it hold data that matters?&lt;/li&gt;
&lt;li&gt;Is it backed up and tested?&lt;/li&gt;
&lt;li&gt;Do I know how to restore it?&lt;/li&gt;
&lt;li&gt;Would replacing it with a hosted service reduce meaningful work?&lt;/li&gt;
&lt;li&gt;Is its learning value still greater than its maintenance cost?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A service that fails most of those questions probably belongs in the lab, not in permanent production.&lt;/p&gt;

&lt;h2&gt;
  
  
  My rule now
&lt;/h2&gt;

&lt;p&gt;I still enjoy self-hosting. I am not interested in a purity test where everything must live on somebody else's cloud. But I am also done pretending that more containers automatically mean more skill.&lt;/p&gt;

&lt;p&gt;My rule now is simple: every service must earn the right to keep running.&lt;/p&gt;

&lt;p&gt;If it saves time, protects data I care about, supports teaching or content work, or teaches me something I will reuse, it stays. If it mostly produces updates, alerts, and extra surface area, it goes.&lt;/p&gt;

&lt;p&gt;That shift changed the feel of my homelab completely. It is calmer. Easier to recover. Easier to explain. Easier to leave alone for a few days without wondering what is quietly breaking.&lt;/p&gt;

&lt;p&gt;That is what sustainable self-hosting looks like. Not the biggest stack. The one you can still live with.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>devops</category>
      <category>docker</category>
      <category>selfhosting</category>
    </item>
    <item>
      <title>Six arguing AI agents: what multi-agent debate teaches CS students about AI architecture</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Sun, 12 Jul 2026 14:58:14 +0000</pubDate>
      <link>https://dev.to/techiemike/six-arguing-ai-agents-what-multi-agent-debate-teaches-cs-students-about-ai-architecture-5614</link>
      <guid>https://dev.to/techiemike/six-arguing-ai-agents-what-multi-agent-debate-teaches-cs-students-about-ai-architecture-5614</guid>
      <description>&lt;h1&gt;
  
  
  Six arguing AI agents: what multi-agent debate teaches CS students about AI architecture
&lt;/h1&gt;

&lt;p&gt;Most students meet AI through prompts. Type a question, get a paragraph back, move on.&lt;/p&gt;

&lt;p&gt;That framing is useful for five minutes and then it gets in the way.&lt;/p&gt;

&lt;p&gt;The more interesting lesson is what happens when you stop treating a model like a single all-knowing assistant and start treating it like one noisy component in a bigger system. Give several model instances the same problem. Ask each one for an answer. Make them criticise each other. Force another round. Then have a judge combine the best points.&lt;/p&gt;

&lt;p&gt;Suddenly the discussion stops being about prompt wording and starts being about architecture.&lt;/p&gt;

&lt;p&gt;That is why multi-agent debate matters. Not because six chat windows arguing with each other are cute, and not because debate turns an LLM into a reliable thinker. It matters because the pattern exposes what these systems are actually good at, where they fail, and what good computer science students should notice before they get hypnotised by demo videos.&lt;/p&gt;

&lt;p&gt;My take is blunt: prompt engineering is the least interesting part of this story. System design is the lesson.&lt;/p&gt;

&lt;h2&gt;
  
  
  What multi-agent debate actually is
&lt;/h2&gt;

&lt;p&gt;The 2023 paper &lt;a href="https://arxiv.org/abs/2305.14325" rel="noopener noreferrer"&gt;"Improving Factuality and Reasoning in Language Models through Multiagent Debate"&lt;/a&gt; describes a simple setup: multiple language model instances propose answers, debate them over several rounds, and then converge on a final answer. The authors report improvements on mathematical and strategic reasoning tasks, along with reductions in fallacious and hallucinatory responses compared with single-agent baselines.&lt;/p&gt;

&lt;p&gt;The idea is not mystical. You are building a feedback loop.&lt;/p&gt;

&lt;p&gt;A basic debate pipeline usually looks like this:&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%2F85b2ejvdslah3ur14911.webp" 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%2F85b2ejvdslah3ur14911.webp" alt="Multi-agent debate pipeline diagram showing several agents answering the same prompt, critiquing each other, revising, and feeding a final judge" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Agent A, B, and C each answer the same prompt independently.&lt;/li&gt;
&lt;li&gt;Each agent reads the others' answers and points out weak logic, missing steps, or contradictions.&lt;/li&gt;
&lt;li&gt;Each agent revises its own answer after seeing criticism.&lt;/li&gt;
&lt;li&gt;A final judge, or one more synthesis pass, picks the strongest parts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That workflow borrows from something humans already do in programming: code review. One developer writes the first pass. Another person spots the broken assumption. Someone else notices the edge case. The final version is better, not because one brain became smarter, but because the process exposed mistakes before they shipped.&lt;/p&gt;

&lt;p&gt;If you have read my piece on &lt;a href="https://www.techiemike.com/the-chat-graveyard-how-to-export-search-and-learn-from-your-ai-conversations/" rel="noopener noreferrer"&gt;searching old AI conversations&lt;/a&gt;, the same pattern shows up there too. The value is often in the review loop, not the first output.&lt;/p&gt;

&lt;h2&gt;
  
  
  What debate reveals about LLM architecture
&lt;/h2&gt;

&lt;p&gt;This is the part students should care about.&lt;/p&gt;

&lt;p&gt;A large language model is still an autoregressive system that predicts the next token from context. The &lt;a href="https://arxiv.org/abs/2005.14165" rel="noopener noreferrer"&gt;GPT-3 paper&lt;/a&gt; describes it as an autoregressive language model, and the 2022 paper &lt;a href="https://arxiv.org/abs/2201.11903" rel="noopener noreferrer"&gt;"Chain-of-Thought Prompting Elicits Reasoning in Large Language Models"&lt;/a&gt; showed that giving models room to produce intermediate reasoning steps can improve results on arithmetic, commonsense, and symbolic tasks. Multi-agent debate pushes that same idea one step further: instead of one long reasoning trace, you create several traces and let them attack each other.&lt;/p&gt;

&lt;p&gt;That tells you something important.&lt;/p&gt;

&lt;p&gt;A lot of what people call "reasoning" in practice is not a hidden mode that switches on inside the model. It is often the result of structure wrapped around the model: extra steps, extra context, criticism, retries, and selection. In other words, the intelligence you see is partly in the orchestration.&lt;/p&gt;

&lt;p&gt;I think students need to hear that early, because the internet keeps selling the opposite story. One flashy demo appears, people act as if the model has crossed some mysterious line, and the system design vanishes from the conversation.&lt;/p&gt;

&lt;p&gt;But look at what debate is doing under the hood:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;redundancy, because several agents attempt the task&lt;/li&gt;
&lt;li&gt;error detection, because agents inspect each other's reasoning&lt;/li&gt;
&lt;li&gt;iterative refinement, because answers are revised instead of accepted once&lt;/li&gt;
&lt;li&gt;aggregation, because a judge or synthesis pass chooses among alternatives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are architecture decisions. They are not prompt tricks.&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%2Fr9mc9ohpw7cl72z1vwj9.webp" 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%2Fr9mc9ohpw7cl72z1vwj9.webp" alt="LLM architecture feedback loop illustration showing redundancy, critique, iterative refinement, and aggregation in a multi-agent system" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is also why multi-agent systems look a lot like classic CS ideas wearing new clothes. Distributed systems use redundancy because one node can fail. Compilers use multiple passes because one pass is not always enough. Software teams use tests and review because the first version is rarely the final one. Debate is the same instinct applied to a probabilistic text engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The useful lesson is not "AI thinks better in groups"
&lt;/h2&gt;

&lt;p&gt;Be careful with the headline version of this idea.&lt;/p&gt;

&lt;p&gt;It is tempting to say that a group of agents "thinks" better than one agent. I would phrase it more carefully. A structured group process can produce better outputs than a single pass because it creates more chances to catch obvious mistakes.&lt;/p&gt;

&lt;p&gt;That difference matters.&lt;/p&gt;

&lt;p&gt;If three weak answers all share the same bad assumption, debate does not magically rescue you. It can even make the wrong answer sound more convincing, because now it arrives with consensus theatre attached. Agreement is not proof. A polished synthesis is not proof either.&lt;/p&gt;

&lt;p&gt;That is one reason I still prefer grounded workflows over pure discussion workflows. The 2022 paper &lt;a href="https://arxiv.org/abs/2210.03629" rel="noopener noreferrer"&gt;"ReAct: Synergizing Reasoning and Acting in Language Models"&lt;/a&gt; is useful here because it combines reasoning traces with actions that fetch external information. For factual tasks, a debating panel with no access to evidence is still trapped inside its own guesses.&lt;/p&gt;

&lt;p&gt;So yes, debate can help. No, it does not turn a language model into a truth machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why CS students should care
&lt;/h2&gt;

&lt;p&gt;If I were teaching this to a class, I would not present multi-agent debate as an AI party trick. I would use it to teach systems thinking.&lt;/p&gt;

&lt;p&gt;Students usually start with the surface layer: the prompt. That is understandable. It is the part they can see.&lt;/p&gt;

&lt;p&gt;But the deeper question is this: what kind of system are you building around an unreliable component?&lt;/p&gt;

&lt;p&gt;That question is pure computer science.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. It teaches decomposition
&lt;/h3&gt;

&lt;p&gt;A decent multi-agent workflow splits one job into smaller responsibilities: proposer, critic, judge, verifier. That is exactly how good software systems are designed. Different parts do different jobs. Nobody asks the same component to generate, verify, rank, and explain everything perfectly in one pass.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. It teaches evaluation
&lt;/h3&gt;

&lt;p&gt;Students often stop at "the AI answered." Debate forces the next question: answered according to what standard?&lt;/p&gt;

&lt;p&gt;Was the logic complete? Did it use evidence? Did two agents agree for the same reason, or did one just copy the other? What would count as a failure case?&lt;/p&gt;

&lt;p&gt;Those are evaluation questions, and they matter more than the prompt itself. I made a similar point in my guide on &lt;a href="https://www.techiemike.com/how-to-set-up-a-test-pipeline-that-actually-catches-bugs/" rel="noopener noreferrer"&gt;how to set up a test pipeline that actually catches bugs&lt;/a&gt;, because good systems need clear checks, not vibes.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. It teaches observability
&lt;/h3&gt;

&lt;p&gt;One reason single-shot AI tools are slippery is that you only see the final sentence. Debate systems expose more of the path. You can inspect the initial answers, the objections, the revisions, and the final merge. That makes the system easier to debug.&lt;/p&gt;

&lt;p&gt;It is the same reason tracebacks matter in programming. If you only saw "program failed," you would learn very little. If you can inspect the chain of events, you can actually reason about the bug. That is why I keep pushing students toward explicit debugging habits instead of one-shot guesswork.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. It teaches trade-offs
&lt;/h3&gt;

&lt;p&gt;Debate sounds clever until you pay for it.&lt;/p&gt;

&lt;p&gt;Three rounds with four agents is already twelve model calls before the judge speaks. Latency goes up. Cost goes up. Failure modes multiply. Prompt leakage between agents becomes a design issue. Logging becomes a design issue. Rate limits become a design issue.&lt;/p&gt;

&lt;p&gt;That is good news for a CS classroom, because now the conversation is finally real. Students are not just asking, "Can AI do it?" They are asking, "What did this architecture cost, and was the gain worth it?"&lt;/p&gt;

&lt;p&gt;That is a much better question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three debate frameworks students can try
&lt;/h2&gt;

&lt;p&gt;You do not need a research lab to experiment with this. You just need to keep the tasks small and the scoring clear.&lt;/p&gt;

&lt;h3&gt;
  
  
  The two-agent critic loop
&lt;/h3&gt;

&lt;p&gt;This is the simplest version.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent 1 writes an answer.&lt;/li&gt;
&lt;li&gt;Agent 2 critiques it line by line.&lt;/li&gt;
&lt;li&gt;Agent 1 revises once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use it for short explanations, code comments, or a paragraph answering a theory question. Students learn quickly that the second pass is often less about brilliance and more about catching omissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The three-agent committee
&lt;/h3&gt;

&lt;p&gt;Give the same question to three isolated agents. Do not let them see each other at first. Then ask a judge to compare them against a rubric.&lt;/p&gt;

&lt;p&gt;This works well for structured tasks: design choices, algorithm explanations, short essays, and worked examples. The point is not to vote blindly. The point is to inspect disagreement. When two answers agree and one dissents, students should ask why.&lt;/p&gt;

&lt;h3&gt;
  
  
  The evidence-first debate
&lt;/h3&gt;

&lt;p&gt;This is the one I would push hardest.&lt;/p&gt;

&lt;p&gt;Before any agent can argue, each one has to quote or retrieve evidence from an approved source set: lecture notes, a paper extract, a dataset, or a fixed documentation page. Only then can the critique round begin.&lt;/p&gt;

&lt;p&gt;That rule stops the exercise from turning into a confidence contest. It also teaches a habit students badly need in the AI era: separate fluent output from justified output.&lt;/p&gt;

&lt;p&gt;If students want to run these experiments locally, they do not need an expensive machine. Smaller local setups are enough for classroom-scale tests, especially if the tasks are short and the models are modest. I covered that trade-off in my guide on &lt;a href="https://www.techiemike.com/running-ai-models-on-old-hardware-a-10-year-old-xeon-is-all-you-need-2/" rel="noopener noreferrer"&gt;running AI models on old hardware&lt;/a&gt;. For experimentation, architecture matters more than brute force.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the hype gets ahead of reality
&lt;/h2&gt;

&lt;p&gt;This is where I part company with the more breathless AI commentary.&lt;/p&gt;

&lt;p&gt;When people show a multi-agent demo, they often imply that the magic comes from "more agents." I do not think that is the right takeaway. Past a certain point, adding more agents can just add more noise, more cost, and more nicely formatted nonsense.&lt;/p&gt;

&lt;p&gt;The better question is whether each added step has a job.&lt;/p&gt;

&lt;p&gt;Does one agent bring independent evidence?&lt;br&gt;
Does one agent act as a strict verifier?&lt;br&gt;
Does one round of critique catch a failure mode you have actually observed?&lt;/p&gt;

&lt;p&gt;If the answer is no, you are probably building theatre.&lt;/p&gt;

&lt;p&gt;I see the same issue in some local AI tooling conversations too. People pile on frameworks because the stack looks impressive, not because each layer solves a real problem. That is why I keep coming back to practical architecture choices in posts like &lt;a href="https://www.techiemike.com/learning-docker-by-building-a-container-engine-from-scratch/" rel="noopener noreferrer"&gt;Learning Docker by Building a Container Engine from Scratch&lt;/a&gt;. More moving parts only help when each part earns its place.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would want students to remember
&lt;/h2&gt;

&lt;p&gt;If this topic lands anywhere useful in a CS classroom, it should land here.&lt;/p&gt;

&lt;p&gt;A language model is not the whole system.&lt;/p&gt;

&lt;p&gt;The prompt is not the whole system.&lt;/p&gt;

&lt;p&gt;The benchmark screenshot is definitely not the whole system.&lt;/p&gt;

&lt;p&gt;What matters is how you handle uncertainty, error checking, evidence, revision, and cost around a component that is fluent but unreliable. Multi-agent debate is worth studying because it makes those design choices visible.&lt;/p&gt;

&lt;p&gt;That is the lesson I would argue for: AI architecture is a systems problem first, and a wording problem second.&lt;/p&gt;

&lt;p&gt;A simple classroom version is to give three agents the same algorithm explanation, ask each one to identify the weakest assumption in the others' answers, then have students compare the final synthesis against a rubric. That keeps the lesson concrete. Students can see whether the extra critique round exposed a real weakness or just produced more polished noise.&lt;/p&gt;

&lt;p&gt;Students who understand that will build better tools, ask better questions, and fall for fewer demos.&lt;/p&gt;

&lt;p&gt;That seems more useful than teaching them how to write one more clever prompt.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>education</category>
      <category>architecture</category>
    </item>
    <item>
      <title>TLS Certificates for Internal Services: HTTPS Inside Your Homelab</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Sat, 11 Jul 2026 04:35:52 +0000</pubDate>
      <link>https://dev.to/techiemike/tls-certificates-for-internal-services-https-inside-your-homelab-12cj</link>
      <guid>https://dev.to/techiemike/tls-certificates-for-internal-services-https-inside-your-homelab-12cj</guid>
      <description>&lt;h1&gt;
  
  
  TLS Certificates for Internal Services: HTTPS Inside Your Homelab
&lt;/h1&gt;

&lt;p&gt;Most homelab TLS setups stop at the front door.&lt;/p&gt;

&lt;p&gt;You put Caddy, Nginx, or Traefik in front, get a valid certificate for &lt;code&gt;app.example.com&lt;/code&gt;, and call it done. From the browser to the reverse proxy, everything is encrypted. From the reverse proxy to the actual container, it is often plain HTTP on port 80 or 8080.&lt;/p&gt;

&lt;p&gt;That is not automatically wrong. On a single mini PC with a couple of containers, internal TLS is rarely the first problem I would fix. Weak passwords, overexposed dashboards, and lazy network rules usually matter more.&lt;/p&gt;

&lt;p&gt;But once a homelab grows beyond "one box, three containers," plain HTTP starts spreading everywhere. A reverse proxy talks to one VM over HTTP. A helper container calls another API with no encryption. A dashboard sends session cookies across the LAN because "it's only internal." That is the point where internal TLS stops being cosmetic and starts being decent housekeeping.&lt;/p&gt;

&lt;p&gt;My view is simple: if a service is worth exposing through a nice hostname, it is probably worth deciding how you want to trust it internally too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where plain HTTP sneaks back in
&lt;/h2&gt;

&lt;p&gt;The usual pattern looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browser to reverse proxy: HTTPS&lt;/li&gt;
&lt;li&gt;Reverse proxy to upstream app: HTTP&lt;/li&gt;
&lt;li&gt;App to database: whatever the default was&lt;/li&gt;
&lt;li&gt;Admin tool to API endpoint: plain HTTP because it lives on the same subnet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That can be acceptable in a tiny setup where everything runs on one host and nothing else can reach the traffic path. I still would not call it a long-term design. The moment you add another VM, another Docker host, or a tunnel back into the network, your "internal only" assumption gets weaker.&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%2F8704xo9ud9u5mxfwlv5c.webp" 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%2F8704xo9ud9u5mxfwlv5c.webp" alt="Diagram showing browser-to-proxy HTTPS, proxy-to-app HTTP, and internal service traffic paths inside a homelab" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This came up for me while thinking about the same problem I discussed in my &lt;a href="https://www.techiemike.com/ollama-security-self-hosted-ai/" rel="noopener noreferrer"&gt;self-hosted AI security guide&lt;/a&gt;. Once you start putting tools behind reverse proxies and opening remote admin paths, the old habit of "just use HTTP inside" sticks around long after the setup stopped being small.&lt;/p&gt;

&lt;p&gt;Internal TLS does not make a homelab secure. It does do three practical things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It reduces casual packet snooping on segments you do not fully trust.&lt;/li&gt;
&lt;li&gt;It lets you test the same HTTPS assumptions internally that you expect externally.&lt;/li&gt;
&lt;li&gt;It stops you from building workflows that silently depend on insecure defaults.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That third one matters more than people admit. If your automation only works when certificate checks are disabled, you have already taught your stack a bad habit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three approaches that actually make sense
&lt;/h2&gt;

&lt;p&gt;I would ignore the endless certificate-tool rabbit hole and stick to three realistic options.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Local CA for quick wins
&lt;/h3&gt;

&lt;p&gt;If your goal is "I want valid-looking HTTPS inside the lab by tonight," a local CA is the low-friction option.&lt;/p&gt;

&lt;p&gt;Caddy has a &lt;code&gt;local_certs&lt;/code&gt; option that tells it to issue certificates internally instead of using a public ACME CA. That makes it useful for development environments and small private networks where you control the clients. The catch is the same as every private CA setup: the clients have to trust your root certificate first.&lt;/p&gt;

&lt;p&gt;This is the best fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you mostly browse services from your own machines&lt;/li&gt;
&lt;li&gt;you control the phones, laptops, or tablets that need trust installed&lt;/li&gt;
&lt;li&gt;you want HTTPS warnings gone without involving public DNS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the worst fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;guests or unmanaged devices need access&lt;/li&gt;
&lt;li&gt;you do not want to distribute a root CA to every client&lt;/li&gt;
&lt;li&gt;you expect native apps to behave nicely with an untrusted chain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, local CA is tidy for a personal lab, messy for a shared one.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Let's Encrypt with DNS-01 for services under your real domain
&lt;/h3&gt;

&lt;p&gt;If your internal services already use names under a public domain you control, DNS-01 is usually the cleanest answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://letsencrypt.org/docs/challenge-types/#dns-01-challenge" rel="noopener noreferrer"&gt;Let's Encrypt's DNS-01 challenge docs&lt;/a&gt; explain that your ACME client places a TXT record under &lt;code&gt;_acme-challenge&lt;/code&gt; for the domain. Those docs also note two details that matter here: DNS-01 can issue wildcard certificates, and it only makes sense when your DNS provider has an API you can automate.&lt;/p&gt;

&lt;p&gt;That means you can issue certificates for names under your domain without exposing the service itself on the public internet, as long as you can update DNS records for the validation step.&lt;/p&gt;

&lt;p&gt;This is the setup I would reach for if you already use a real domain for your homelab and want browser-trusted certificates without teaching every client to trust a private root.&lt;/p&gt;

&lt;p&gt;It works especially well with reverse proxies that already understand ACME. Traefik's ACME docs explicitly support &lt;code&gt;dnsChallenge&lt;/code&gt;, and the same docs note that ACME v2 wildcard certificates require a DNS-01 challenge.&lt;/p&gt;

&lt;p&gt;The downside is operational, not conceptual. You now have DNS API credentials in the mix. That is manageable, but it deserves the same care I talked about in the &lt;a href="https://www.techiemike.com/cloudflare-self-managed-oauth-homelab-security/" rel="noopener noreferrer"&gt;Cloudflare self-managed OAuth post&lt;/a&gt;: narrowly scoped credentials beat one giant token that can edit everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Private ACME with step-ca when the lab stops being small
&lt;/h3&gt;

&lt;p&gt;If you have multiple machines, multiple services, and want certificates to renew automatically without public DNS hacks, this is where &lt;code&gt;step-ca&lt;/code&gt; starts making sense.&lt;/p&gt;

&lt;p&gt;The useful part is not just "run your own CA." The &lt;a href="https://smallstep.com/docs/step-ca/acme-basics/" rel="noopener noreferrer"&gt;step-ca ACME basics docs&lt;/a&gt; show that &lt;code&gt;step-ca&lt;/code&gt; supports ACME, so standard ACME clients can request certificates from your own certificate authority instead of from a public provider. That means you can keep the familiar issuance flow while staying fully inside your own trust model.&lt;/p&gt;

&lt;p&gt;This is the option I would choose when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you have several hosts, not just several containers&lt;/li&gt;
&lt;li&gt;internal service names are not part of public DNS&lt;/li&gt;
&lt;li&gt;you want one internal CA instead of one-off self-signed certs everywhere&lt;/li&gt;
&lt;li&gt;you are prepared to manage trust distribution properly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is also the point where you need to be honest with yourself. A private CA is not hard because of certificate issuance. It is hard because every client needs the root certificate installed correctly, and you need a plan for renewal, revocation, and what happens when you rebuild devices.&lt;/p&gt;

&lt;p&gt;If that sounds annoying, that is because PKI is annoying. The tooling got better. The responsibility did not.&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%2Fd8841599fm40cnu6i61w.webp" 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%2Fd8841599fm40cnu6i61w.webp" alt="Comparison chart showing three internal TLS options for a homelab: local CA, Let's Encrypt DNS-01, and private ACME with step-ca" width="800" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My rule of thumb
&lt;/h2&gt;

&lt;p&gt;Here is the version I would give to anyone building a real homelab instead of a PKI hobby project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One box, mostly personal devices, low stakes: local CA is fine.&lt;/li&gt;
&lt;li&gt;Public domain already in use, want normal browser trust: Let's Encrypt with DNS-01.&lt;/li&gt;
&lt;li&gt;Several hosts, several internal names, long-term setup: private ACME with step-ca.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I would not do is collect five half-solutions at once.&lt;/p&gt;

&lt;p&gt;I have seen a lot of homelabs end up with one self-signed cert for Grafana, a reverse proxy wildcard from Let's Encrypt, one app running plain HTTP forever, and a note to "fix later" on the service that matters most. That is not a design. That is archaeology.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to test before you call it done
&lt;/h2&gt;

&lt;p&gt;After you flip a service to HTTPS, test the boring parts.&lt;/p&gt;

&lt;p&gt;These examples are worth keeping around:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
curl -vk https://service.example.internal/
openssl s_client -connect service.example.internal:443 -servername service.example.internal 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>security</category>
      <category>linux</category>
      <category>devops</category>
      <category>selfhosting</category>
    </item>
    <item>
      <title>Beyond Ollama: 3 Homelab AI Tools Worth Trying Next</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Fri, 10 Jul 2026 06:05:29 +0000</pubDate>
      <link>https://dev.to/techiemike/beyond-ollama-3-homelab-ai-tools-worth-trying-next-4ai9</link>
      <guid>https://dev.to/techiemike/beyond-ollama-3-homelab-ai-tools-worth-trying-next-4ai9</guid>
      <description>&lt;h1&gt;
  
  
  Beyond Ollama: 3 Homelab AI Tools Worth Trying Next
&lt;/h1&gt;

&lt;p&gt;Ollama is the tool I recommend when someone wants their first local model running tonight. It is simple, fast to understand, and good enough for a lot of homelab jobs. Pull a model, hit an API, wire up Open WebUI, done. I already covered that full path in my &lt;a href="https://www.techiemike.com/self-hosted-llms-with-docker-ollama-and-open-webui/" rel="noopener noreferrer"&gt;Docker + Ollama + Open WebUI guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But Ollama also nudges you toward one shape of local AI: text models behind a neat API. That is fine until you want more control over image workflows, a cleaner OpenAI-compatible service layer, or one local runtime that can cover chat, speech, embeddings, and image generation from the same box.&lt;/p&gt;

&lt;p&gt;That is where the next wave gets interesting.&lt;/p&gt;

&lt;p&gt;The three projects I would watch right now are ComfyUI, LocalAI, and Lemonade. They are not direct replacements for Ollama in the sense of "uninstall Ollama and forget it exists." They solve different problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ComfyUI is for visual AI workflows where you want to control every step&lt;/li&gt;
&lt;li&gt;LocalAI is for homelabbers who want a broader local API surface with OpenAI-compatible apps&lt;/li&gt;
&lt;li&gt;Lemonade is for people who want to experiment with a broader local AI runtime with chat, image, speech, and standard APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My view is simple: Ollama is still the easiest first stop, but it is no longer the whole conversation. If your homelab AI stack stops at Ollama, you are missing some genuinely useful tools.&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%2Fy3xlkkzfnbrqor83vh6l.webp" 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%2Fy3xlkkzfnbrqor83vh6l.webp" alt="Comparison graphic showing Ollama as the starting point, then branching into ComfyUI for visual workflows, LocalAI for API compatibility, and Lemonade for multimodal local services" width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why look past Ollama at all?
&lt;/h2&gt;

&lt;p&gt;Ollama does one thing very well: it makes local LLM inference approachable. That matters. A lot of local AI users never move past the "I want a model on my machine" stage, and they do not need to.&lt;/p&gt;

&lt;p&gt;Still, three limits show up pretty quickly.&lt;/p&gt;

&lt;p&gt;First, Ollama is mostly about model serving, not workflow design. If you want to build a repeatable image pipeline with prompt branches, upscalers, masks, and post-processing, Ollama is the wrong tool.&lt;/p&gt;

&lt;p&gt;Second, plenty of apps expect an OpenAI-style API and want more than text generation. LocalAI leans hard into that compatibility layer, and that can save time when you are wiring tools together.&lt;/p&gt;

&lt;p&gt;Third, local AI is getting more multimodal. Text alone is no longer the interesting part. Speech to text, text to speech, embeddings, image generation, and agent tooling are all creeping into the same workflows. Lemonade is appealing because it is trying to package that broader stack into one local service.&lt;/p&gt;

&lt;p&gt;That does not mean these projects are automatically better. It means they cover ground Ollama does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. ComfyUI: the visual workflow engine
&lt;/h2&gt;

&lt;p&gt;If your local AI curiosity has moved from "run a model" to "build a pipeline," ComfyUI is the one to try next.&lt;/p&gt;

&lt;p&gt;ComfyUI's &lt;a href="https://docs.comfy.org/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; describes it as an AI creation engine for visual professionals who want control over every model, every parameter, and every output. The docs also push two ideas that matter for homelab users: you can run ComfyUI on your own hardware through Comfy Desktop, and the whole product is built around workflows you can load, remix, and iterate on.&lt;/p&gt;

&lt;p&gt;That workflow-first approach is the real difference.&lt;/p&gt;

&lt;p&gt;Instead of typing one prompt into a chat box and hoping for the best, you build a graph. One node loads a checkpoint. Another encodes the prompt. Another sets sampler settings. Another upscales. Another handles masks or ControlNet-style conditioning. It feels more like wiring a media pipeline than chatting with a bot.&lt;/p&gt;

&lt;p&gt;That is why ComfyUI matters beyond image hobbyists. It teaches a better mental model for local AI. You stop thinking in one-shot prompts and start thinking in reusable systems.&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%2F5o3ghmcsc1bkcirjowqz.webp" 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%2F5o3ghmcsc1bkcirjowqz.webp" alt="Workflow diagram showing a ComfyUI node graph for local image generation with checkpoint loading, prompt encoding, upscaling, and output stages" width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What ComfyUI is good at
&lt;/h3&gt;

&lt;p&gt;For a homelab, I think ComfyUI makes sense in four cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You want stable, repeatable image generation workflows instead of random prompting.&lt;/li&gt;
&lt;li&gt;You are generating assets for a blog, course, or small business and you need consistency.&lt;/li&gt;
&lt;li&gt;You want to understand how visual AI pipelines actually fit together.&lt;/li&gt;
&lt;li&gt;You want to save and reuse workflows instead of rebuilding the same prompt stack every time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The community workflow library is a big part of the appeal. You can start from a template, inspect how it works, then trim it down to fit your own hardware.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where ComfyUI gets awkward
&lt;/h3&gt;

&lt;p&gt;The downside is also obvious: ComfyUI asks more from you.&lt;/p&gt;

&lt;p&gt;If Ollama feels like &lt;code&gt;docker run&lt;/code&gt; plus a chat window, ComfyUI feels like learning a node editor. That is not a flaw. It is the point. But it means the learning curve is real.&lt;/p&gt;

&lt;p&gt;It is also the least forgiving option here if your hardware is modest and your expectations are not. Text inference on CPU-only hardware can still be useful, as I found in my &lt;a href="https://www.techiemike.com/running-ai-models-on-old-hardware-a-10-year-old-xeon-is-all-you-need-2/" rel="noopener noreferrer"&gt;old hardware local AI write-up&lt;/a&gt;. Image workflows are different. You can run them locally, but speed and model choice matter a lot more.&lt;/p&gt;

&lt;p&gt;So my practical advice is this: do not start ComfyUI by chasing the flashiest workflow on social media. Start with a basic community workflow, a smaller model, and one job you actually have. Blog art. Thumbnail variations. Product mockups. Something concrete.&lt;/p&gt;

&lt;h3&gt;
  
  
  My homelab take on ComfyUI
&lt;/h3&gt;

&lt;p&gt;ComfyUI is the tool in this list that most rewards a dedicated box or VM. If your homelab has a machine with a decent GPU, this is where it earns its keep. If your homelab is just a mini PC with integrated graphics and limited RAM, ComfyUI can still be educational, but I would not make it your first "serious production" local AI service.&lt;/p&gt;

&lt;p&gt;That is also why I do not see ComfyUI as an Ollama replacement. I see it as the moment local AI stops being only about chat.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. LocalAI: the compatibility play
&lt;/h2&gt;

&lt;p&gt;LocalAI is interesting for a completely different reason.&lt;/p&gt;

&lt;p&gt;Its homepage makes the pitch very clearly: it is a free OpenAI and Anthropic alternative, built as a small composable AI stack. The &lt;a href="https://localai.io/basics/getting_started/index.html" rel="noopener noreferrer"&gt;LocalAI quickstart&lt;/a&gt; also states that it is a drop-in replacement for the OpenAI API, supports multiple model families, and can run language models, images, audio, and more locally. Docker is the recommended installation method, and the quick start is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nt"&gt;--name&lt;/span&gt; local-ai &lt;span class="nt"&gt;-ti&lt;/span&gt; localai/localai:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single detail tells you who LocalAI is for.&lt;/p&gt;

&lt;p&gt;It is for the homelabber who already has a pile of tools that expect a familiar API shape and does not want to keep adapting everything around one runtime. If you have scripts, agent tools, or self-hosted apps that speak OpenAI-style endpoints, LocalAI can be easier to slot into place than a more opinionated stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  What LocalAI is good at
&lt;/h3&gt;

&lt;p&gt;The best part of LocalAI is not that it tries to do everything. It is that it tries to expose local models through interfaces a lot of existing software already understands.&lt;/p&gt;

&lt;p&gt;That gives it three obvious uses in a homelab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;replacing cloud API calls in tools that already support OpenAI-compatible backends&lt;/li&gt;
&lt;li&gt;centralising local inference behind one service instead of a stack of ad hoc wrappers&lt;/li&gt;
&lt;li&gt;experimenting with broader local AI features without locking yourself into a single app UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also like the way the project frames itself as composable. That usually matters more in a homelab than polished marketing. You want pieces you can fit into your own stack, not a black box that wants to own the whole machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  The part people gloss over
&lt;/h3&gt;

&lt;p&gt;OpenAI-compatible does not mean identical behaviour across every app.&lt;/p&gt;

&lt;p&gt;This is where a lot of local AI blog posts get sloppy. A compatible API gets you much closer to plug-and-play, but model capabilities, latency, backend support, and edge-case behaviour still vary. The docs promise the API shape. They do not promise every upstream app will behave exactly as if it were talking to OpenAI.&lt;/p&gt;

&lt;p&gt;That is not a criticism of LocalAI. It is just the honest version.&lt;/p&gt;

&lt;p&gt;If you are the kind of homelabber who likes clean interfaces and clear boundaries, LocalAI is a strong fit. If you want a dead-simple chat setup in ten minutes, Ollama is still easier. If you want a broad local API target for apps and experiments, LocalAI starts to look better.&lt;/p&gt;

&lt;h3&gt;
  
  
  My homelab take on LocalAI
&lt;/h3&gt;

&lt;p&gt;This is the one I would try first if I were moving from "local toy" to "local platform."&lt;/p&gt;

&lt;p&gt;Why? Because a homelab gets messy fast. One service wants text generation. Another wants embeddings. Another expects an OpenAI-style endpoint. Another needs audio later. A compatibility layer matters more than people think.&lt;/p&gt;

&lt;p&gt;I would still keep the deployment conservative. Bind it locally first. Put a reverse proxy and auth in front of it if you need remote access. The same local AI security rules I wrote about for Ollama still apply here: do not confuse "self-hosted" with "safe by default."&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Lemonade: the all-in-one runtime I would keep an eye on
&lt;/h2&gt;

&lt;p&gt;Lemonade is the newest-feeling project in this list, and it is the one I find easiest to imagine on a modern homelab.&lt;/p&gt;

&lt;p&gt;Its site pitches Lemonade as local AI for text, images, and speech. The installation flow says it equips your machine with an AI runtime, GUI, CLI, and API endpoints. The &lt;a href="https://github.com/lemonade-sdk/lemonade" rel="noopener noreferrer"&gt;Lemonade GitHub project&lt;/a&gt; pushes the same idea harder: Lemonade Server exposes standard OpenAI, Anthropic, and Ollama APIs, while the broader platform covers chat, coding, speech, image generation, transcription, and embeddings.&lt;/p&gt;

&lt;p&gt;For Docker users, the site shows this quick start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; lemonade-server &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 13305:13305 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; lemonade-cache:/root/.cache/huggingface &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; lemonade-llama:/opt/lemonade/llama &lt;span class="se"&gt;\&lt;/span&gt;
  ghcr.io/lemonade-sdk/lemonade-server:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a serious homelab signal. It tells me the project understands how people like us test things: one container, mapped port, persistent volumes, then decide whether it deserves a permanent home.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Lemonade is good at
&lt;/h3&gt;

&lt;p&gt;What I like here is the ambition.&lt;/p&gt;

&lt;p&gt;Lemonade is not saying "here is one local model runner." It is saying "here is a local-first AI runtime that can serve apps and agents, speak familiar APIs, and cover multiple modalities." The README even exposes CLI flows like &lt;code&gt;lemonade run&lt;/code&gt;, &lt;code&gt;lemonade pull&lt;/code&gt;, and &lt;code&gt;lemonade list&lt;/code&gt;, plus a built-in model manager.&lt;/p&gt;

&lt;p&gt;That makes Lemonade appealing for a homelab in three situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you want one local runtime for more than chat&lt;/li&gt;
&lt;li&gt;you want a local service that can talk to existing apps over standard APIs&lt;/li&gt;
&lt;li&gt;you want multimodal experiments without stitching five unrelated projects together yourself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also a quiet but important detail in the README: Lemonade documents support across CPU, GPU, and in some cases NPU-focused configurations depending on backend and platform. I would not promise all of that on every random mini PC. But the direction is interesting. It feels closer to where local AI is heading.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I would still be cautious
&lt;/h3&gt;

&lt;p&gt;The more all-in-one a project becomes, the more you need to be realistic about scope.&lt;/p&gt;

&lt;p&gt;Broad capability claims are easy to love in a README and harder to operate in a homelab. Multimodal stacks usually mean more storage, more dependency churn, more model management, and more opportunities to end up debugging the runtime instead of using it.&lt;/p&gt;

&lt;p&gt;So I would treat Lemonade as a platform experiment before I treated it as core infrastructure.&lt;/p&gt;

&lt;p&gt;That is not an insult. Plenty of the best homelab tools start as platform experiments.&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%2Fl2v2xws9bfav09pco99g.webp" 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%2Fl2v2xws9bfav09pco99g.webp" alt="Dashboard-style illustration comparing LocalAI and Lemonade as local API and multimodal runtime layers on homelab hardware" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  My homelab take on Lemonade
&lt;/h3&gt;

&lt;p&gt;If I wanted one box in the rack to become "the local AI services machine," Lemonade is the project here that best matches that idea.&lt;/p&gt;

&lt;p&gt;Not because it is automatically the best at every individual task. It probably is not. But because it is trying to unify text, image, speech, and app-facing APIs into one local runtime. That is a more useful direction than yet another single-purpose model wrapper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one should you choose?
&lt;/h2&gt;

&lt;p&gt;Short version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pick ComfyUI if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;you care about image generation or visual workflows more than chat&lt;/li&gt;
&lt;li&gt;you want to build reusable pipelines, not just send prompts&lt;/li&gt;
&lt;li&gt;you have the patience to learn a node-based tool properly&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pick LocalAI if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;you want a local OpenAI-compatible service for apps and scripts&lt;/li&gt;
&lt;li&gt;you are building a broader self-hosted AI platform, not just a chat box&lt;/li&gt;
&lt;li&gt;you prefer composable infrastructure over an all-in-one user experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pick Lemonade if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;you want one local runtime that reaches beyond text&lt;/li&gt;
&lt;li&gt;you care about chat, speech, embeddings, and image generation in the same stack&lt;/li&gt;
&lt;li&gt;you like the idea of standard APIs plus local-first multimodal tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stick with Ollama if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;your real need is still just local text inference&lt;/li&gt;
&lt;li&gt;you want the smallest setup burden&lt;/li&gt;
&lt;li&gt;you value simplicity more than feature breadth right now&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one matters. There is no prize for making your homelab more complicated than it needs to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical setup advice before you install any of them
&lt;/h2&gt;

&lt;p&gt;This is the part that matters more than choosing the "right" brand.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Separate experiments from core services
&lt;/h3&gt;

&lt;p&gt;Do not dump every AI runtime onto the same VM that already runs your password manager, DNS, and media stack. Give local AI tools their own box, LXC, or VM where possible. They pull big models, eat disk, and change quickly.&lt;/p&gt;

&lt;p&gt;If you are planning that separation now, my &lt;a href="https://www.techiemike.com/homelab-network-design-building-your-own-miniature-data-centre-at-home/" rel="noopener noreferrer"&gt;homelab network design guide&lt;/a&gt; is the post I would read before carving out another AI host.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Start with one use case, not three
&lt;/h3&gt;

&lt;p&gt;The fastest way to waste a weekend is to install ComfyUI, LocalAI, and Lemonade in one sitting and learn none of them properly. Pick one actual job first.&lt;/p&gt;

&lt;p&gt;If the job is image workflows, use ComfyUI.&lt;br&gt;
If the job is API compatibility, use LocalAI.&lt;br&gt;
If the job is "I want a broader local AI service layer," try Lemonade.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Budget storage before you budget hype
&lt;/h3&gt;

&lt;p&gt;Model storage gets out of hand fast. Between checkpoints, embeddings, speech models, caches, and image assets, local AI can eat SSD space long before it maxes out CPU.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Keep security boring
&lt;/h3&gt;

&lt;p&gt;Local AI projects are still software stacks with ports, APIs, and update cycles. Keep them off the open internet unless you have a very good reason. Bind locally first. Add auth deliberately. Reuse the same caution you would use for any self-hosted dashboard or API. My &lt;a href="https://www.techiemike.com/ollama-security-self-hosted-ai/" rel="noopener noreferrer"&gt;Ollama security guide&lt;/a&gt; exists for a reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  My actual opinion
&lt;/h2&gt;

&lt;p&gt;I do not think the next step after Ollama is "the one true replacement." I think the better question is what kind of homelab AI user you are becoming.&lt;/p&gt;

&lt;p&gt;If you are becoming a workflow builder, ComfyUI makes sense.&lt;br&gt;
If you are becoming an infrastructure person, LocalAI makes sense.&lt;br&gt;
If you want a local-first service layer that feels closer to a real AI platform, Lemonade is the one I would watch.&lt;/p&gt;

&lt;p&gt;That is why this space is more interesting now than it was six months ago. Local AI is finally splitting into real categories instead of pretending every problem is solved by one model runner and a chat window.&lt;/p&gt;

&lt;p&gt;Ollama got a lot of people through the front door. Good. It needed to happen.&lt;/p&gt;

&lt;p&gt;But the room is bigger now.&lt;/p&gt;

&lt;p&gt;If you want the philosophy behind local-first AI, read &lt;a href="https://www.techiemike.com/right-to-local-intelligence/" rel="noopener noreferrer"&gt;Right to Local Intelligence&lt;/a&gt;. If you want the easy starting point, use Ollama. If you want the next layer in a homelab that is starting to do serious work, these three are where I would look next.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>devops</category>
      <category>selfhosted</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
