<?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: Nejdet Kadir Bektaş</title>
    <description>The latest articles on DEV Community by Nejdet Kadir Bektaş (@nejdetkadir).</description>
    <link>https://dev.to/nejdetkadir</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%2F801665%2Fb8d79f8c-bccd-4ef0-bc5c-416a21f79dc5.jpeg</url>
      <title>DEV Community: Nejdet Kadir Bektaş</title>
      <link>https://dev.to/nejdetkadir</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nejdetkadir"/>
    <language>en</language>
    <item>
      <title>How I Built My Own Home-Lab: A Single-Node Kubernetes Platform</title>
      <dc:creator>Nejdet Kadir Bektaş</dc:creator>
      <pubDate>Tue, 21 Jul 2026 06:12:35 +0000</pubDate>
      <link>https://dev.to/nejdetkadir/how-i-built-my-own-home-lab-a-single-node-kubernetes-platform-4acp</link>
      <guid>https://dev.to/nejdetkadir/how-i-built-my-own-home-lab-a-single-node-kubernetes-platform-4acp</guid>
      <description>&lt;p&gt;I turned a second-hand mini-PC into a proper, internet-facing application platform: one Kubernetes node running my own apps, deployed automatically on every &lt;code&gt;git push&lt;/code&gt;, reachable both on my LAN and (selectively) from the internet — &lt;strong&gt;without opening a single port on my router&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post is the high-level tour. It's not a copy-paste tutorial; it's the map I wish I'd had, with the decisions that mattered, the traps I hit, and links to the official docs for the details.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;My hardware:&lt;/strong&gt; an HP EliteDesk 800 G3 Mini (i5-6500T, 8 GB RAM, 128 GB SSD). Used, cheap, silent, sips power. You do &lt;strong&gt;not&lt;/strong&gt; need a rack.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The one idea that makes it all work: Git is the source of truth
&lt;/h2&gt;

&lt;p&gt;Before any tool, decide this: &lt;strong&gt;the cluster should be describable entirely by Git repositories.&lt;/strong&gt; Every app, every config, every secret (encrypted) lives in version control. The cluster is disposable — if it burns down, you re-apply Git and you're back.&lt;/p&gt;

&lt;p&gt;This principle (called &lt;strong&gt;GitOps&lt;/strong&gt;) is what turns a pile of &lt;code&gt;kubectl apply&lt;/code&gt; commands into something you can reason about, review, and rebuild.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: git push ──&amp;gt; CI: test + build image ──&amp;gt; Container Registry
                        │
                        └──&amp;gt; Git: write new image tag ──&amp;gt; ArgoCD watches Git ──&amp;gt; Kubernetes applies it ──&amp;gt; App is live
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The golden rule: &lt;strong&gt;CI never talks to the cluster.&lt;/strong&gt; It builds an image and writes one line to Git. A separate agent inside the cluster (ArgoCD) notices the change and applies it. Nothing outside holds cluster credentials.&lt;/p&gt;




&lt;h2&gt;
  
  
  The stack, and why
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.debian.org/" rel="noopener noreferrer"&gt;Debian 12&lt;/a&gt; minimal&lt;/td&gt;
&lt;td&gt;rock-solid, tiny, no surprises&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;td&gt;&lt;a href="https://k3s.io/" rel="noopener noreferrer"&gt;k3s&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;full k8s in ~1 GB RAM; batteries included (ingress, load-balancer, storage)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitOps&lt;/td&gt;
&lt;td&gt;&lt;a href="https://argo-cd.readthedocs.io/" rel="noopener noreferrer"&gt;Argo CD&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;the cluster pulls from Git; you never push to it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ingress&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://traefik.io/" rel="noopener noreferrer"&gt;Traefik&lt;/a&gt; (bundled with k3s)&lt;/td&gt;
&lt;td&gt;one front door for every app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Certificates&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://cert-manager.io/" rel="noopener noreferrer"&gt;cert-manager&lt;/a&gt; + a private CA&lt;/td&gt;
&lt;td&gt;HTTPS on the LAN with your own trusted root&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DNS + ad-blocking&lt;/td&gt;
&lt;td&gt;&lt;a href="https://adguard.com/en/adguard-home/overview.html" rel="noopener noreferrer"&gt;AdGuard Home&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;serves &lt;code&gt;*.home.lab&lt;/code&gt; to the whole house, blocks ads as a bonus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cloudnative-pg.io/" rel="noopener noreferrer"&gt;CloudNativePG&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Postgres that heals itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public edge&lt;/td&gt;
&lt;td&gt;&lt;a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/" rel="noopener noreferrer"&gt;Cloudflare Tunnel&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;expose apps with zero open ports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remote access&lt;/td&gt;
&lt;td&gt;&lt;a href="https://tailscale.com/" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;reach the whole lab from anywhere, securely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/getsops/sops" rel="noopener noreferrer"&gt;SOPS&lt;/a&gt; + &lt;a href="https://github.com/FiloSottile/age" rel="noopener noreferrer"&gt;age&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;encrypted secrets safe to commit to Git&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; k3s ships Traefik, a load-balancer (ServiceLB), local storage, and metrics-server out of the box. Don't install a heavier distro and then bolt those on — you'll fight the defaults.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The build, stage by stage (high level)
&lt;/h2&gt;

&lt;p&gt;The whole thing is one ordered runbook. Each stage only depends on the ones above it, so a full rebuild is "run this list top to bottom."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0. Accounts, domains, keys
1. Install Debian, harden SSH
2. Install k3s
3. cert-manager + private CA
4. AdGuard: LAN DNS
5. Container registry
6. Postgres + Redis
7. Argo CD
8. Deploy your first app
9. CI: auto-deploy on push
10. Cloudflare Tunnel: go public
11. Tailscale: remote access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  0–2 — Foundation
&lt;/h3&gt;

&lt;p&gt;Reserve a fixed IP for the box in your &lt;strong&gt;router's DHCP settings&lt;/strong&gt; (bind it to the machine's MAC address — don't set a static IP on the host). Install Debian minimal: &lt;strong&gt;no desktop, SSH server yes&lt;/strong&gt;. Then harden SSH to key-only. Install k3s with a one-liner from &lt;a href="https://get.k3s.io" rel="noopener noreferrer"&gt;get.k3s.io&lt;/a&gt;. Copy the kubeconfig to your laptop and you're driving the cluster from your desk.&lt;/p&gt;

&lt;h3&gt;
  
  
  3–5 — The platform
&lt;/h3&gt;

&lt;p&gt;cert-manager mints a private Certificate Authority so every &lt;code&gt;*.home.lab&lt;/code&gt; site gets real HTTPS. AdGuard becomes your LAN's DNS server (point your router's DNS at it), so &lt;code&gt;argocd.home.lab&lt;/code&gt; resolves everywhere in the house. A small container registry holds your app images.&lt;/p&gt;

&lt;h3&gt;
  
  
  6–8 — Data and GitOps
&lt;/h3&gt;

&lt;p&gt;Postgres and Redis come up as shared backing services. Then Argo CD — the heart of the whole thing. You hand-apply exactly &lt;strong&gt;one&lt;/strong&gt; Argo CD "app-of-apps," and from then on, new applications appear simply by committing a small file to a Git repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  9 — The magic moment: automatic deploys
&lt;/h3&gt;

&lt;p&gt;Wire up CI so that &lt;strong&gt;every push to &lt;code&gt;main&lt;/code&gt;&lt;/strong&gt; runs your tests, builds an image, pushes it, and writes the new tag back to Git. Argo CD sees the commit and rolls out the new version. You never touch the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  10–11 — Public and remote
&lt;/h3&gt;

&lt;p&gt;Cloudflare Tunnel publishes chosen apps to the internet on your own domain — outbound-only, so &lt;strong&gt;no router ports open and your home IP stays hidden&lt;/strong&gt;. Tailscale lets &lt;em&gt;you&lt;/em&gt; reach the entire lab (SSH, dashboards, everything) from anywhere as if you were home.&lt;/p&gt;




&lt;h2&gt;
  
  
  The happy path (what a normal day looks like)
&lt;/h2&gt;

&lt;p&gt;Once it's built, shipping a change is astonishingly boring — which is the point:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I edit code, &lt;code&gt;git push&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;CI tests, builds, and commits a one-line tag change.&lt;/li&gt;
&lt;li&gt;Argo CD notices and deploys it.&lt;/li&gt;
&lt;li&gt;A couple of minutes later, the new version is serving traffic — on my LAN &lt;em&gt;and&lt;/em&gt; on the public URL.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No SSH. No &lt;code&gt;kubectl&lt;/code&gt;. No manual steps. That loop is the entire reason to build this.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tips I earned the hard way
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Budget your RAM before you install anything.&lt;/strong&gt; On 8 GB, every component needs a line in a budget table. My platform idles around &lt;strong&gt;3.5 GB&lt;/strong&gt;, leaving ~4 GB for apps. The biggest single consumer (Prometheus + Grafana monitoring, ~1.3 GB) I made &lt;strong&gt;optional&lt;/strong&gt; — installed only when I actually need it. Don't run monitoring you're not looking at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The node must not depend on the cluster to boot.&lt;/strong&gt; Point the &lt;em&gt;host's&lt;/em&gt; DNS at a public resolver (&lt;code&gt;1.1.1.1&lt;/code&gt;), never at the AdGuard pod — otherwise a reboot deadlocks (the node needs DNS to start the pod that provides DNS).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal Debian has no &lt;code&gt;curl&lt;/code&gt;.&lt;/strong&gt; Tiny thing, cost me a confused minute. &lt;code&gt;apt install curl&lt;/code&gt; before the k3s script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;containerd doesn't read the OS trust store.&lt;/strong&gt; For a private-registry CA, you must put the cert in k3s's &lt;code&gt;registries.yaml&lt;/code&gt; and restart k3s — trusting it at the OS level isn't enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;macOS treats DNS1/DNS2 as a pool, not a fallback.&lt;/strong&gt; Set a secondary public DNS and some &lt;code&gt;*.home.lab&lt;/code&gt; lookups randomly fail (and get cached as "doesn't exist"). Keeping Tailscale connected fixes it permanently via split-DNS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encrypt secrets, commit them anyway.&lt;/strong&gt; With SOPS + age, encrypted secrets live safely in Git. Keep the private key in your password manager. This is what makes "rebuild from Git" actually true.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set your BIOS to power on after outage.&lt;/strong&gt; A shelf server nobody power-cycles must come back by itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buy used RAM.&lt;/strong&gt; The single best upgrade. DDR4 SODIMMs are cheap and dissolve almost every "deferred until later" decision.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The security posture (don't skip this)
&lt;/h2&gt;

&lt;p&gt;The infrastructure perimeter can be genuinely strong: no open ports, one fail-closed public hostname behind Cloudflare's WAF, admin dashboards restricted to LAN/Tailscale only, all secrets encrypted.&lt;/p&gt;

&lt;p&gt;But the weakest point is usually &lt;strong&gt;the app you put behind it&lt;/strong&gt;, not the perimeter. My hard-won lesson: the moment you expose an app publicly, audit &lt;em&gt;it&lt;/em&gt; — default seeded admin accounts, hardcoded credentials, migrations that run in production. A locked front door doesn't help if the app ships with a spare key taped under the mat.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; exposing an app to the internet is a one-line change — and so is &lt;em&gt;un&lt;/em&gt;-exposing it. Treat "public" as a deliberate, reviewable decision, per app.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Was it worth it?
&lt;/h2&gt;

&lt;p&gt;Completely. For the price of a used mini-PC and a weekend, I have a platform that deploys my side-projects automatically, serves them with real HTTPS on my own domain, and I can rebuild from Git if the SSD dies. More importantly, everything I practice here — GitOps, Kubernetes, secrets management, zero-trust networking — is the same shape as what runs in production at work, just sized for a shelf.&lt;/p&gt;

&lt;p&gt;Start smaller than this if you want. Even "k3s + one app + Tailscale" is a fantastic first weekend. Then let it grow — the discipline of budgeting and Git-as-truth scales with you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.k3s.io/" rel="noopener noreferrer"&gt;k3s docs&lt;/a&gt; — the distribution that makes single-node k8s pleasant&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://argo-cd.readthedocs.io/en/stable/getting_started/" rel="noopener noreferrer"&gt;Argo CD getting started&lt;/a&gt; — the GitOps engine&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/" rel="noopener noreferrer"&gt;Cloudflare Tunnel&lt;/a&gt; — public exposure without open ports&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tailscale.com/kb/" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt; — the mesh VPN for remote access&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/getsops/sops" rel="noopener noreferrer"&gt;SOPS&lt;/a&gt; — encrypt secrets you can safely commit&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cert-manager.io/docs/" rel="noopener noreferrer"&gt;cert-manager&lt;/a&gt; — automated certificates, including a private CA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Happy self-hosting. Your home-lab is the best classroom you'll ever own.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>homelab</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>selfhosting</category>
    </item>
  </channel>
</rss>
