<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Platform Engineering |</title><link>https://example.com/tags/platform-engineering/</link><atom:link href="https://example.com/tags/platform-engineering/index.xml" rel="self" type="application/rss+xml"/><description>Platform Engineering</description><generator>HugoBlox Kit (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Thu, 14 May 2026 00:00:00 +0000</lastBuildDate><image><url>https://example.com/media/icon_hu_da05098ef60dc2e7.png</url><title>Platform Engineering</title><link>https://example.com/tags/platform-engineering/</link></image><item><title>Stage I: Architecting a Bare-Metal KVM &amp; K3s Foundation</title><link>https://example.com/blog/stage-1-compute-foundation/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://example.com/blog/stage-1-compute-foundation/</guid><description>&lt;p&gt;Building Kubernetes in the cloud is easy—cloud providers abstract all the hard networking and compute layers away from you. To truly understand Cloud Native architecture, I decided to build a zero-trust environment from the ground up.&lt;/p&gt;
&lt;p&gt;This post covers &lt;strong&gt;Stage I&lt;/strong&gt; of my 10-Phase Engineering Roadmap: establishing the virtualized compute layer, deploying a zero-trust mesh VPN, bootstrapping the K3s cluster, and configuring edge routing.&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="base-compute"&gt;1. The Base Compute Environment &amp;amp; Zero-Trust Access&lt;/h2&gt;
&lt;p&gt;To simulate a true enterprise environment, I am running a bare-metal &lt;strong&gt;Kali Linux&lt;/strong&gt; host. However, I am not running the cluster directly on the host OS. Instead, I am using &lt;strong&gt;KVM (Kernel-based Virtual Machine)&lt;/strong&gt; to spin up an isolated Ubuntu server guest.&lt;/p&gt;
&lt;p&gt;Furthermore, to enforce zero-trust from day one, SSH port 22 is completely blocked from the public internet. Access to the Ubuntu KVM is handled exclusively via &lt;strong&gt;Tailscale&lt;/strong&gt;, creating an encrypted wireguard mesh tunnel that allows me to SSH into the root environment securely from anywhere.&lt;/p&gt;
&lt;p&gt;To confirm the virtualized compute layer was running smoothly, here is the output verifying the KVM guest status on the Kali host:&lt;/p&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;&lt;img src="virsh-output.png" alt="KVM Guest Status" loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="opentofu"&gt;2. Declarative Infrastructure with OpenTofu&lt;/h2&gt;
&lt;p&gt;To eliminate manual configuration drift right from the start, I used &lt;strong&gt;OpenTofu&lt;/strong&gt; with the &lt;code&gt;libvirt&lt;/code&gt; provider to manage the underlying state of the Ubuntu virtual machine.&lt;/p&gt;
&lt;p&gt;Here is a snippet of how I structured the infrastructure provisioning:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;required_providers&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; libvirt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dmacvicar/libvirt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.7.6&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;libvirt&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;qemu:///system&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Download the official Ubuntu Cloud Image
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 1. The Base Image (Downloads and stores the raw Ubuntu image)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;libvirt_volume&amp;#34; &amp;#34;ubuntu_base&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ubuntu-base.qcow2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;${path.module}/noble-server-cloudimg-amd64.img&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;qcow2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 2. The Actual VM Drive (Clones the base and stretches it to 30GB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;libvirt_volume&amp;#34; &amp;#34;ubuntu_image&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ubuntu-24.04.qcow2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; base_volume_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;libvirt_volume&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ubuntu_base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;32212254720&lt;/span&gt;&lt;span class="c1"&gt; # 30GB - SSD
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;qcow2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Inject the cloud-init file (which now contains Tailscale)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;libvirt_cloudinit_disk&amp;#34; &amp;#34;commoninit&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;commoninit.iso&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; user_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;${path.module}/cloud_init.cfg&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Define the Virtual Machine
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;libvirt_domain&amp;#34; &amp;#34;k3s_node&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;k3s-server&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;6144&amp;#34;&lt;/span&gt;&lt;span class="c1"&gt; # 6GB RAM
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; vcpu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="c1"&gt; # 2 CPU Cores
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cloudinit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;libvirt_cloudinit_disk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;commoninit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;network_interface&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; network_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; wait_for_lease&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;disk&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; volume_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;libvirt_volume&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ubuntu_image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;console&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pty&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; target_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; target_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;serial&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;The Challenge:&lt;/strong&gt; The most frustrating part of this setup was dealing with libvirt socket permissions. By default, the OpenTofu provider running on the Kali host was getting &lt;code&gt;permission denied&lt;/code&gt; when trying to connect to the &lt;code&gt;qemu:///system&lt;/code&gt; socket. I had to properly configure the &lt;code&gt;libvirt&lt;/code&gt; group policies and Polkit rules on the host to allow the declarative pipeline to automatically provision the VM without requiring interactive root prompts.&lt;/p&gt;
&lt;h2 id="k3s-setup"&gt;3. Bootstrapping Bare-Metal K3s&lt;/h2&gt;
&lt;p&gt;For container orchestration, I chose &lt;strong&gt;K3s&lt;/strong&gt;. It is lightweight enough to run highly efficiently on the Ubuntu guest, but fully conformant for enterprise-grade deployments.&lt;/p&gt;
&lt;p&gt;To install and bootstrap the cluster securely, I explicitly passed arguments to bind to my Tailscale interface and disabled the default local storage path to maintain strict control:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sfL &lt;span class="o"&gt;[&lt;/span&gt;https://get.k3s.io&lt;span class="o"&gt;](&lt;/span&gt;https://get.k3s.io&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;INSTALL_K3S_EXEC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--node-ip &amp;lt;TAILSCALE_IP_ADDRESS&amp;gt; --bind-address &amp;lt;TAILSCALE_IP_ADDRESS&amp;gt; --tls-san &amp;lt;TAILSCALE_IP_ADDRESS&amp;gt;&amp;#34;&lt;/span&gt; sh -
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once the script executed, the node successfully registered itself using the secure Tailscale interface, as shown in the cluster node status:&lt;/p&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="K3s Node Status showing Tailscale IP"
srcset="https://example.com/blog/stage-1-compute-foundation/k3s-proof_hu_c98d0581d4185be8.webp 320w, https://example.com/blog/stage-1-compute-foundation/k3s-proof_hu_c3cfcf5f42b053dc.webp 480w, https://example.com/blog/stage-1-compute-foundation/k3s-proof_hu_4c79557933bcff55.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://example.com/blog/stage-1-compute-foundation/k3s-proof_hu_c98d0581d4185be8.webp"
width="760"
height="166"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="traefik-ingress"&gt;4. Edge Routing &amp;amp; Traefik Challenges&lt;/h2&gt;
&lt;p&gt;Networking on local KVM Kubernetes is drastically different than the cloud. You don&amp;rsquo;t have AWS or GCP to automatically provision an Elastic Load Balancer (ELB) for you.&lt;/p&gt;
&lt;p&gt;I utilized &lt;strong&gt;Traefik&lt;/strong&gt; as the primary Ingress Controller, deploying it via Helm to manage routing for my internal services.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;networking.k8s.io/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Ingress&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;n8n-ingress&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;whatsapp-bot &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ingressClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;traefik&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;&amp;lt;Magic-DNS-FROM-TAILSCALE&amp;gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# MagicDNS name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pathType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Prefix&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;n8n-service &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# n8n service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5678&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 5678 default n8n port&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Originally, exposing services from an isolated KVM guest required complex iptables rules, sysctl IP forwarding, and dealing with port 80/443 conflicts on the Kali host. To completely bypass this networking headache, I utilized Tailscale MagicDNS. By binding K3s and Traefik directly to the Tailscale interface inside the VM, I created a secure overlay network. Now, Traefik routes traffic seamlessly via MagicDNS, completely bypassing the Kali host&amp;rsquo;s physical network bridge and eliminating the need for complex port-forwarding.&lt;/p&gt;
&lt;p&gt;With the local DNS override in place and Traefik configured, I could securely access the internal dashboard. Here is the active HTTP routing rule proving the configuration works:&lt;/p&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Traefik Dashboard HTTP Routers"
srcset="https://example.com/blog/stage-1-compute-foundation/traefik-ui_hu_3cd9aa6f9e1fbb34.webp 320w, https://example.com/blog/stage-1-compute-foundation/traefik-ui_hu_72133731afa3471f.webp 480w, https://example.com/blog/stage-1-compute-foundation/traefik-ui_hu_70a345757f62f245.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://example.com/blog/stage-1-compute-foundation/traefik-ui_hu_3cd9aa6f9e1fbb34.webp"
width="760"
height="265"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="outcomes"&gt;5. Stage I Outcomes&lt;/h2&gt;
&lt;p&gt;By the end of Phase 4, the foundation was set:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure is codified:&lt;/strong&gt; The Ubuntu VM environment can be reliably rebuilt using OpenTofu.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Orchestration is live:&lt;/strong&gt; K3s is running natively and efficiently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Access is secured:&lt;/strong&gt; The host and guest are protected behind a Tailscale zero-trust perimeter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With the base compute and networking established, the cluster was ready for application workloads.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next up:&lt;/strong&gt; In
, I dive into how I layered &lt;strong&gt;GitHub Actions&lt;/strong&gt; and &lt;strong&gt;ArgoCD&lt;/strong&gt; on top of this foundation to enforce GitOps compliance, and how I secured the cluster credentials using &lt;strong&gt;HashiCorp Vault&lt;/strong&gt;.&lt;/p&gt;</description></item><item><title>Cloud Native Architecture: Zero-Trust Bare Metal Kubernetes</title><link>https://example.com/projects/whatsapp-chatbot/</link><pubDate>Wed, 28 Jan 2026 00:00:00 +0000</pubDate><guid>https://example.com/projects/whatsapp-chatbot/</guid><description>&lt;p&gt;An ongoing, 10-phase infrastructure build demonstrating modern DevSecOps principles. This active laboratory project is transforming bare-metal hardware into a highly available, self-healing, and secure Kubernetes environment using GitOps methodologies.&lt;/p&gt;
&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;I wanted to move beyond simple cloud provider tutorials and understand how the underlying compute layers actually work. I engineered this cluster to enforce zero-trust security and eliminate manual configuration drift, proving that enterprise-grade automation can be built from bare metal using virtualization, secure tunneling, and GitOps.&lt;/p&gt;
&lt;h2 id="infrastructure-capabilities"&gt;Infrastructure Capabilities&lt;/h2&gt;
&lt;h3 id="1-virtualized-compute--zero-trust-access"&gt;1. Virtualized Compute &amp;amp; Zero-Trust Access&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;KVM Hypervisor&lt;/strong&gt; - Ubuntu guest virtual machine running efficiently on a bare-metal Kali Linux host.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tailscale Mesh VPN&lt;/strong&gt; - Hardened, zero-trust SSH access tunnel completely isolating the host from the public internet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Declarative Provisioning&lt;/strong&gt; - Utilizing OpenTofu to dynamically provision and manage the infrastructure state.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-container-orchestration--networking"&gt;2. Container Orchestration &amp;amp; Networking&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;K3s Orchestration&lt;/strong&gt; - Lightweight, highly available Kubernetes deployment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dynamic Ingress&lt;/strong&gt; - Traefik configured as the primary ingress controller to manage robust routing and load balancing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-cicd--gitops"&gt;3. CI/CD &amp;amp; GitOps&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Continuous Integration&lt;/strong&gt; - GitHub Actions automates the building and pushing of Docker images for immutable rollbacks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ArgoCD Synchronization&lt;/strong&gt; - Cluster state is bound directly to the Git repository using Helm and Kustomize.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero Manual Drift&lt;/strong&gt; - ArgoCD automatically detects and overwrites any manual changes, enforcing strict GitOps compliance.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4-secret-management"&gt;4. Secret Management&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HashiCorp Vault&lt;/strong&gt; - Centralized, encrypted storage for all sensitive credentials and API keys.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External Secrets Operator (ESO)&lt;/strong&gt; - Dynamically injects Vault secrets directly into Kubernetes pods.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="system-architecture"&gt;System Architecture&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;┌─────────────────┐ (Builds Docker Image) ┌───────────────────────┐
│ GitHub Actions │────────────────────────────▶│ Container Registry │
│ (CI Pipeline) │ │ (GHCR / Hub) │
└─────────────────┘ └───────────────────────┘
│ │
│ (Updates Manifests) │ (Pulls Image)
▼ ▼
┌──────────────┐ ┌───────────────┐ ┌───────────────────────┐
│ │ │ │ │ Kubernetes (K3s) │
│ Git Repo │────▶│ ArgoCD │────▶│ ┌───────────────────┐ │
│ (Manifests) │ │ (Controller) │ │ │ Traefik Ingress │ │
│ │ │ │ │ └───────────────────┘ │
└──────────────┘ └───────────────┘ │ ┌───────────────────┐ │
│ │ k3s-whatsapp-bot │ │
┌──────────────┐ ┌───────────────┐ │ │ (n8n + Postgres) │ │
│ │ │ External │ │ └───────────────────┘ │
│ HashiCorp │◀────│ Secrets │◀────│ ┌───────────────────┐ │
│ Vault │ │ Operator │ │ │ ESO Injector │ │
│ │ │ │ │ └───────────────────┘ │
└──────────────┘ └───────────────┘ └───────────────────────┘
[ Infrastructure: KVM Ubuntu Guest on Kali Metal | Secured via Tailscale ]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="engineering-outcomes"&gt;Engineering Outcomes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;🚀 &lt;strong&gt;Full CI/CD&lt;/strong&gt;: 100% automated pipeline from code push (GitHub Actions) to cluster synchronization (ArgoCD).&lt;/li&gt;
&lt;li&gt;🔒 &lt;strong&gt;Security&lt;/strong&gt;: Host isolated via Tailscale; zero hardcoded secrets via Vault and ESO.&lt;/li&gt;
&lt;li&gt;📉 &lt;strong&gt;Configuration Drift&lt;/strong&gt;: Reduced to 0% through strict ArgoCD reconciliation loops.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="technical-deep-dives-architecture-series"&gt;Technical Deep Dives (Architecture Series)&lt;/h2&gt;
&lt;p&gt;To explore the raw code, YAML manifests, and how I solved specific architectural challenges across the lifecycle, read my detailed engineering write-ups:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;📝 &lt;strong&gt;
&lt;/strong&gt; - &lt;em&gt;Deep dive into Phases 1-4: Virtualizing Ubuntu on Kali via KVM, Tailscale SSH tunnels, and OpenTofu provisioning.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;📝 &lt;strong&gt;
&lt;/strong&gt; - &lt;em&gt;Deep dive into Phases 5-6: Docker builds via GitHub Actions, Helm/ArgoCD drift elimination, and Vault/ESO injection.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;📝 &lt;strong&gt;
&lt;/strong&gt; (Coming Soon) - &lt;em&gt;Deep dive into Phases 7-8: Cloudflare Tunnels and Prometheus/Grafana telemetry.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;📝 &lt;strong&gt;
&lt;/strong&gt; (Coming Soon) - &lt;em&gt;Deep dive into Phases 9-10: Executing automated attack paths against the cluster and Building a SIEM alerting pipeline.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-10-phase-engineering-roadmap"&gt;The 10-Phase Engineering Roadmap&lt;/h2&gt;
&lt;p&gt;This cluster is designed as a living DevSecOps laboratory. I am currently executing Phase 7 of a comprehensive, capability-driven lifecycle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stage I: The Compute Foundation (✅ Completed)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Phase 1: Base Hypervisor&lt;/strong&gt; - Bare-metal Kali Linux hosting KVM.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Phase 2: Virtualization &amp;amp; Access&lt;/strong&gt; - OpenTofu provisioning of the Ubuntu guest and zero-trust Tailscale SSH tunneling.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Phase 3: Container Orchestration&lt;/strong&gt; - High-availability K3s cluster bootstrapping.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Phase 4: Edge Routing&lt;/strong&gt; - Dynamic ingress and load balancing via Traefik.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Stage II: Automation &amp;amp; Zero-Trust (✅ Completed)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Phase 5: CI/CD Pipeline&lt;/strong&gt; - GitHub Actions building Docker images and ArgoCD/Helm synchronizing the GitOps state.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Phase 6: Ephemeral Secrets&lt;/strong&gt; - Zero-trust credential injection via HashiCorp Vault and ESO.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Stage III: Perimeter Defense &amp;amp; Observability (⏳ In Progress)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⏳ &lt;strong&gt;Phase 7: Zero-Trust Perimeter&lt;/strong&gt; - Integrating Cloudflare Tunnels for unexposed, secure ingress.&lt;/li&gt;
&lt;li&gt;⏳ &lt;strong&gt;Phase 8: Full-Stack Telemetry&lt;/strong&gt; - Deploying Prometheus &amp;amp; Grafana for cluster observability.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Stage IV: Purple Teaming Laboratory (📅 Planned)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;📅 &lt;strong&gt;Phase 9: Offensive Simulation (Red)&lt;/strong&gt; - Executing automated attack paths against the cluster to validate resilience.&lt;/li&gt;
&lt;li&gt;📅 &lt;strong&gt;Phase 10: Threat Detection (Blue)&lt;/strong&gt; - Building a SIEM alerting pipeline to capture the offensive testing telemetry.&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>