<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Anas Rhimi - Technical Blog</title>
    <link>https://anasrhimi.tech/blog</link>
    <description>Case studies, tutorials, and deep-dives into DevOps, Linux, and Cloud Infrastructure.</description>
    <language>en-us</language>
    <atom:link href="https://anasrhimi.tech/rss.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title><![CDATA[Data Sovereignty in 2026: Production-Grade Local LLMs]]></title>
      <link>https://anasrhimi.tech/blog/local-llms-data-sovereignty</link>
      <description><![CDATA[Stop leaking data to SaaS APIs. Learn how to architect a sovereign AI stack using vLLM and Ollama for secure, high-throughput local inference.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/local-llms-data-sovereignty.jpg" alt="Data Sovereignty in 2026: Production-Grade Local LLMs"></figure>
<h2>The API Problem</h2>
<p>Architecting core product features around proprietary APIs like GPT-4 or Claude can quickly become cost-prohibitive. Beyond unpredictable recurring expenses, transmitting sensitive user or healthcare records to external third-party endpoints poses significant privacy, security, and regulatory compliance risks. Furthermore, if a provider adjusts pricing structures or deprecates specific model versions, critical application logic can break unexpectedly.</p>
<p>To retain strict data sovereignty and operational stability, an increasing number of engineering teams are bringing AI inference workloads in-house.</p>
<h2>Running Capable Models Locally</h2>
<p>The emergence of high-performing open-weight models such as Llama 3.1, Mistral, and Qwen combined with advanced quantization techniques allows organizations to deliver reliable inference on standard hardware without massive infrastructure expenditures.</p>
<p>In production environments, two principal tools handle most deployment requirements:</p>
<ul>
<li><strong>vLLM for high throughput:</strong> Engineered for demanding server environments, vLLM leverages PagedAttention to optimize GPU memory allocation and efficiently manage high concurrent request volumes.</li>
<li><strong>Ollama for local development:</strong> Optimized for internal tooling, rapid prototyping, and automated CI/CD workflows, Ollama enables developers to download and execute models locally using a Docker-like CLI interface.</li>
</ul>
<h2>Building a Sovereign AI Stack</h2>
<p>A fully private AI architecture resides entirely within your Virtual Private Cloud (VPC):</p>
<p>1. Provision vLLM on a GPU-enabled Kubernetes node.
2. Expose an OpenAI-compatible API endpoint restricted to your private internal network.
3. Direct your applications, microservices, and orchestration engines (such as n8n) to this internal address.</p>
<p>With this design, proprietary data never leaves your controlled infrastructure. Operational costs remain predictable and linked to fixed hardware capacity rather than scaling arbitrarily per generated token.</p>
<h2>Bottom Line</h2>
<p>Depending on external cloud APIs for core business intelligence introduces unnecessary supply-chain risks and compliance vulnerabilities. Deploying self-hosted inference via vLLM and Ollama ensures data privacy, stabilizes infrastructure costs, and grants total control over your AI pipeline.</p>]]></content:encoded>
      <pubDate>Tue, 11 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/local-llms-data-sovereignty</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/local-llms-data-sovereignty.jpg" length="80464" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/local-llms-data-sovereignty.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Automating the Chaos: n8n and the Need for Order]]></title>
      <link>https://anasrhimi.tech/blog/n8n-automation-executive-dysfunction</link>
      <description><![CDATA[Building complex, automated pipelines (n8n, CI/CD) as a coping mechanism for ADHD executive dysfunction.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/n8n-automation-executive-dysfunction.jpg" alt="Automating the Chaos: n8n and the Need for Order"></figure>
<p>Executive dysfunction is a hallmark of ADHD. It is the invisible wall that makes starting a simple, repetitive task feel physically impossible, even when you know it only takes five minutes. In the IT world, where daily operations consist of dozens of small, repetitive tasks (checking logs, updating tickets, deploying code), executive dysfunction is a career killer.</p>
    <p>The solution? Automation. If a task requires manual intervention, it is a point of failure. Automation isn't just about saving time; it is a vital coping mechanism. It offloads the cognitive burden of "remembering to do the thing" onto a system that never forgets.</p>
    <h2>Visualizing Logic with n8n</h2>
    <p>While writing Python scripts is powerful, managing hundreds of disparate cron jobs across various servers quickly becomes its own chaotic mess. This is where visual workflow automation tools like n8n shine, particularly for visual thinkers.</p>
    <p>n8n allows you to map out complex logic gates, API calls, and data transformations as a visual graph. When an alert fires in Grafana, a node catches it. It passes the data to an LLM to summarize the error, then routes it to Jira to create a ticket, and finally messages Slack. You can <em>see</em> the data flowing through the pipeline.</p>
    <h2>The Trap of Over-Engineering</h2>
    <p>Of course, the neurodivergent urge to automate everything can quickly spiral out of control. We often fall into the trap of spending twenty hours building a perfectly robust, fault-tolerant n8n workflow to automate a task we only have to do twice a year.</p>
    <p>But honestly? Sometimes that's okay. The dopamine hit of watching a complex pipeline execute flawlessly from start to finish is often exactly what the brain needs to reset and regain momentum for the actual work. Automation is how we impose order on a chaotic world, one Webhook at a time.</p>]]></content:encoded>
      <pubDate>Tue, 11 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/n8n-automation-executive-dysfunction</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/n8n-automation-executive-dysfunction.jpg" length="82718" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/n8n-automation-executive-dysfunction.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Linux Systems Architecture: A Neurodivergent Safe Space]]></title>
      <link>https://anasrhimi.tech/blog/linux-systems-neurodivergent-safe-space</link>
      <description><![CDATA[Why Linux environments (bash, kernel tuning, absolute control) feel safe and predictable for autistic/ADHD minds.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/linux-systems-neurodivergent-safe-space.jpg" alt="Linux Systems Architecture: A Neurodivergent Safe Space"></figure>
<p>If you ask a neurodivergent IT professional why they prefer the command line over a graphical user interface (GUI), the answer usually boils down to one word: predictability. GUIs change. Buttons move. Corporate design teams push updates that hide advanced settings behind three sub-menus of "user-friendly" wizards.</p>
    <p>A Linux terminal does not care about user-friendliness. It cares about exactness. It is a text-based environment where a command typed today will yield the exact same result ten years from now. For a brain that seeks order and gets easily overwhelmed by shifting variables, the POSIX standard is practically a weighted blanket.</p>
    <h2>Absolute Control vs. Black Box Anxiety</h2>
    <p>Proprietary software is a "black box." You give it an input, it does something mysterious, and it spits out an output. When it breaks, you are at the mercy of opaque error codes and Tier 1 customer support. This lack of control triggers intense anxiety for engineers who rely on understanding the entire system to feel comfortable working within it.</p>
    <p>Linux, on the other hand, is open. From the bootloader to the init system, down to the kernel parameters, everything is a file. If something breaks, the logs will tell you why. If the logs are confusing, you can read the source code. This level of transparency is incredibly soothing to the autistic or ADHD mind. It transforms a frustrating roadblock into a solvable puzzle.</p>
    <h2>The Double-Edged Sword of Hyperfocus</h2>
    <p>However, this absolute control comes with a dark side: the endless rabbit hole of hyperfocus. When you can configure <em>everything</em>, you will inevitably try to optimize <em>everything</em>. It is incredibly easy to lose four days writing the "perfect" bash script to automate a task that only takes ten seconds to do manually.</p>
    <p>The key to surviving in Linux systems administration isn't just knowing how to configure the kernel; it's learning when to stop. It's recognizing that the system is "good enough" and forcing yourself to step away from the keyboard before the hyperfocus turns into exhaustion.</p>]]></content:encoded>
      <pubDate>Tue, 11 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/linux-systems-neurodivergent-safe-space</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/linux-systems-neurodivergent-safe-space.jpg" length="74819" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/linux-systems-neurodivergent-safe-space.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[eBPF for Cloud-Native Security and Observability]]></title>
      <link>https://anasrhimi.tech/blog/ebpf-cloud-native-security</link>
      <description><![CDATA[Explore how eBPF is revolutionizing Kubernetes networking and security without kernel modules, featuring tools like Cilium.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/ebpf-cloud-native-security.jpg" alt="eBPF for Cloud-Native Security and Observability"></figure>
<h2>Why Traditional K8s Security Hits a Wall</h2>
<p>For years, securing a Kubernetes cluster meant accepting difficult trade-offs. You either injected heavy sidecar containers (like Envoy) into every pod or loaded custom kernel modules to intercept traffic at the host level.</p>
<p>Sidecars quickly consume memory and CPU across large clusters, while custom kernel modules introduce stability risks a bug in kernel space can crash the entire node.</p>
<p>eBPF (Extended Berkeley Packet Filter) changes this equation by enabling clean inspection and processing of events directly within the kernel.</p>
<h2>How eBPF Works</h2>
<p>eBPF allows you to execute sandboxed programs directly inside the Linux kernel without modifying kernel source code or loading unstable modules. Because eBPF programs execute right where kernel events occur, you gain deep visibility into system behavior:</p>
<ul>
<li><strong>Low-overhead observability:</strong> Kernel-level tracing captures system calls, file access, and network packets across the entire host without needing sidecar injection.</li>
<li><strong>Safer network enforcement:</strong> Security tools filter or drop unwanted packets at line rate before they pass through the full K8s networking stack.</li>
<li><strong>Low-overhead profiling:</strong> eBPF profilers trace CPU spikes down to specific application call stacks in production with minimal performance overhead.</li>
</ul>
<h2>Cilium and the CNI Shift</h2>
<p>If you are provisioning a Kubernetes cluster today on Proxmox, AWS, or bare metal, Cilium has become a highly compelling CNI option. By utilizing eBPF to bypass or streamline standard <code>kube-proxy</code> iptables rules, Cilium reduces latency and boosts network throughput for service-to-service traffic.</p>
<h2>Summary</h2>
<p>eBPF redefines how cloud-native networking, monitoring, and security operate. By safely shifting inspection into the kernel, it eliminates much of the overhead that burdened sidecar architectures. Understanding how eBPF works is rapidly becoming essential knowledge for anyone managing production Kubernetes infrastructure.</p>]]></content:encoded>
      <pubDate>Tue, 11 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/ebpf-cloud-native-security</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/ebpf-cloud-native-security.jpg" length="98833" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/ebpf-cloud-native-security.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[The Death of Traditional DevOps: Platform Engineering & IDPs]]></title>
      <link>https://anasrhimi.tech/blog/platform-engineering-idp</link>
      <description><![CDATA[Why traditional DevOps failed at scale and how Internal Developer Platforms (IDPs) are building golden paths for self-service infrastructure.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/platform-engineering-idp.jpg" alt="The Death of Traditional DevOps: Platform Engineering & IDPs"></figure>
<p>When DevOps initial principles were widely adopted, the underlying mandate appeared straightforward: if you build software, you run codebase. Developers were no longer responsible solely for application logic they were also tasked with authoring Dockerfiles, configuring Terraform infrastructure manifests, and troubleshooting production Kubernetes deployments.</p>
<p>While this model offered theoretical advantages, it rapidly resulted in developer cognitive overload. Product engineers were allocating substantial bandwidth to debugging Helm charts, fixing complex IAM syntax, and resolving pipeline failures rather than shipping core application features. As engineering organizations scaled, expecting every developer to maintain deep, full-stack infrastructure domain expertise proved impractical.</p>
<p>Platform engineering surfaced to mitigate this cognitive operational overhead. Instead of requiring product teams to directly manage raw cloud resources, specialized platform teams construct Internal Developer Platforms (IDPs) that simplify daily development operations.</p>
<p>Core components of a modern IDP include:</p>
<ul>
<li><strong>Golden Paths</strong>: Standardized, supported templates and production-ready defaults for common architectural patterns. These provide developers with tested, compliant deployment options without requiring manual security configuration.</li>
<li><strong>Self-Service Tooling</strong>: Engineers can bootstrap new microservices autonomously using developer portals like Backstage or Port. The platform automatically handles repository provisioning, CI/CD pipeline configuration, database initialization, and Kubernetes namespace creation in seconds eliminating manual Jira tickets or Slack requests.</li>
<li><strong>Infrastructure Abstraction</strong>: Developers maintain focus on application code and Git workflows, delegating ingress routing, DNS management, and underlying cluster orchestration to automated platform capabilities.</li>
</ul>
<p>In practical implementations, an IDP integrates a unified suite of specialized open-source and cloud technologies: Backstage or Port for developer portal interfaces, ArgoCD for GitOps-driven continuous deployment, Crossplane or Terraform for automated infrastructure provisioning, and OPA Gatekeeper for policy enforcement.</p>
<p>This operational evolution does not compromise DevOps principles; rather, it makes them sustainable at scale. By removing repetitive infrastructure maintenance tasks from product engineering workflows, organizations accelerate release velocity and reduce time spent on manual ops management.</p>]]></content:encoded>
      <pubDate>Tue, 11 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/platform-engineering-idp</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/platform-engineering-idp.jpg" length="90780" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/platform-engineering-idp.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[The Rise of Agentic DevOps: How Autonomous AI is Replacing Static CI/CD]]></title>
      <link>https://anasrhimi.tech/blog/agentic-devops</link>
      <description><![CDATA[Discover how Agentic DevOps and autonomous AI are revolutionizing software delivery by replacing static CI/CD pipelines with self-healing, adaptive workflows.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/agentic-devops.jpg" alt="The Rise of Agentic DevOps: How Autonomous AI is Replacing Static CI/CD"></figure>
<p>For years, CI/CD pipelines have relied on rigid, hardcoded YAML configurations. The workflow is familiar: pull the latest commits, operate test suites, build containers, push to registries, and deploy. When a step breaks, the pipeline halts immediately, sends a Slack notification, and requires an engineer to manually analyze the build logs.</p>
<p>As AI coding tools accelerate development throughput, these static pipelines quickly become team bottlenecks. Waiting for manual intervention on straightforward failures slows down the entire delivery process. This is where agentic workflows come into play.</p>
<h2>What Does Agentic DevOps Look Like?</h2>
<p>Rather than following a rigid linear script, agentic DevOps integrates autonomous components into the build and release process. Provided with sufficient context and permissions, these systems can analyze failures and take targeted corrective actions automatically:</p>
<ul>
<li><strong>Self-healing pipelines:</strong> When a test fails due to a missing package or a mismatched lockfile, the agent reads the execution log, updates <code>package.json</code>, re-runs tests locally, and opens a pull request with the resolution if checks pass.</li>
<li><strong>Smart assess selection:</strong> By evaluating incoming git diffs, agents run only the test modules impacted by recent changes rather than triggering full, time-consuming assess suites on every single run.</li>
<li><strong>Automated resource cleanup:</strong> Monitoring agents scan staging infrastructure to tear down idle cluster nodes or GPU instances left behind after integration tests complete.</li>
</ul>
<h2>Building Practical Agentic Pipelines</h2>
<p>Adopting an agentic approach does not require replacing established tools like GitHub Actions or Jenkins. Instead, it adds an intelligent feedback layer around them.</p>
<p>For example, workflow automation tools like n8n paired with self-hosted LLMs provide a solid foundation:</p>
<p>1. A webhook captures failure events directly from GitHub Actions.
2. The error payload is sent to an LLM context buffer to diagnose the root cause.
3. Proposed fixes execute inside isolated ephemeral containers to verify correctness before any changes reach primary repositories or notify on-call engineers.</p>
<h2>Shifting Focus from Maintenance to Architecture</h2>
<p>Moving toward agentic automation changes how engineers interact with CI/CD. Less time is spent triaging broken builds or maintaining fragile scripts, freeing up capacity to focus on infrastructure security, core infrastructure, and release reliability.</p>]]></content:encoded>
      <pubDate>Tue, 11 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/agentic-devops</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/agentic-devops.jpg" length="102879" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/agentic-devops.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[WebAssembly (WASM) in Kubernetes: The Micro-Edge Revolution]]></title>
      <link>https://anasrhimi.tech/blog/wasm-in-kubernetes</link>
      <description><![CDATA[Learn why WebAssembly (WASM) is replacing heavy Docker containers for edge computing and serverless workloads in modern Kubernetes clusters.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/wasm-in-kubernetes.jpg" alt="WebAssembly (WASM) in Kubernetes: The Micro-Edge Revolution"></figure>
<p>Docker transformed program deployment by bundling applications with their runtime dependencies. However, standard containers still depend on complete operating system filesystems, resulting in image sizes measuring hundreds of megabytes and startup latencies of several seconds.</p>
<p>When deploying workloads to edge environments such as serverless functions, IoT gateways, or localized AI inference nodes cold-start latency and substantial memory overhead present significant bottlenecks. WebAssembly (WASM) addresses these challenges within Kubernetes ecosystems.</p>
<h2>Why WASM Matters for Kubernetes</h2>
<p>WebAssembly originated as a browser runtime for executing high-performance code. Through the WebAssembly Architecture Interface (WASI), WASM executes directly on server infrastructure inside a secure sandbox at near-native speeds.</p>
<ul>
<li><strong>Sub-millisecond cold starts:</strong> WASM modules initialize in milliseconds rather than seconds, making them ideal for event-driven architectures and serverless functions.</li>
<li><strong>Minimal resource footprint:</strong> Most compiled WASM modules measure only a few megabytes. This efficiency allows operators to schedule thousands of WASM modules on a single Kubernetes node that would otherwise handle only dozens of traditional containers.</li>
<li><strong>Cross-platform compilation:</strong> Build application code in languages like Rust, Go, Python, or C++, compile it to a single WASM target, and execute the identical binary across x86 architecture or ARM64 edge systems without recompiling.</li>
</ul>
<h2>Deploying WASM Workloads on Kubernetes</h2>
<p>Tooling such as SpinKube and Kwasm allows teams to deploy WASM workloads side-by-side with standard container pods within existing Kubernetes clusters.</p>
<pre><code class="language-yaml">apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
 name: wasm-hello-world
spec:
 components:
 - name: wasm-hello
 type: wasm-worker
 properties:
  image: ghcr.io/fermyon/hello-world:latest</code></pre>
<h2>Production Use Cases</h2>
<p>WASM will not entirely replace Docker containers for large monolithic applications. However, for lightweight microservices, edge AI processing, and high-concurrency tasks, WASM offers a lightweight runtime alternative that integrates seamlessly into cloud-native infrastructure.</p>]]></content:encoded>
      <pubDate>Tue, 11 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/wasm-in-kubernetes</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/wasm-in-kubernetes.jpg" length="88401" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/wasm-in-kubernetes.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Local LLMs, Data Sovereignty, and the Ultimate Rabbit Hole]]></title>
      <link>https://anasrhimi.tech/blog/local-llms-data-sovereignty-hyperfocus</link>
      <description><![CDATA[The hyperfocus of running local LLMs (Ollama, vLLM) to escape the noise of SaaS APIs and build isolated intelligence.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/local-llms-data-sovereignty-hyperfocus.jpg" alt="Local LLMs, Data Sovereignty, and the Ultimate Rabbit Hole"></figure>
<p>There is a growing unease with the current state of Artificial Intelligence. Everything is a subscription. Everything requires an API key. Every prompt you write, every line of code you ask it to analyze, is sent to a corporate server to be ingested, analyzed, and monetized. For individuals who value privacy, control, and data sovereignty, this is fundamentally unacceptable.</p>
    <p>Enter the world of Local LLMs. It is the ultimate technical rabbit hole, and it is a paradise for the hyperfocused mind.</p>
    <h2>Building the Sovereign Brain</h2>
    <p>Running models locally isn't just about privacy; it's about ownership. When you spin up an instance of Ollama or deploy vLLM on your own hardware, you are interacting with a closed system. There is no rate limiting. There is no unexpected downtime because a cloud provider had an outage. There is no corporate filter silently altering your prompts.</p>
    <p>The process of optimizing these models—quantizing weights from FP16 to 4-bit, balancing VRAM constraints across multiple GPUs, tuning the context window to squeeze out maximum performance—is deeply satisfying. It is systems architecture pushed to the bleeding edge.</p>
    <h2>Escaping the Noise</h2>
    <p>For the neurodivergent developer, the constant churn of the SaaS ecosystem is exhausting. The rules change daily. But a local model, running on a server sitting in your closet, is a constant. It is an isolated intelligence that you control completely.</p>
    <p>By moving AI workloads in-house, we aren't just protecting our data. We are reclaiming our infrastructure from the noise of the cloud, returning to a model of computing where we actually own the tools we use to think.</p>]]></content:encoded>
      <pubDate>Tue, 11 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/local-llms-data-sovereignty-hyperfocus</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/local-llms-data-sovereignty-hyperfocus.jpg" length="76467" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/local-llms-data-sovereignty-hyperfocus.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[The Beauty of C and the Exhaustion of Modern Web Stacks]]></title>
      <link>https://anasrhimi.tech/blog/c-programming-vs-modern-web</link>
      <description><![CDATA[The hyperfocus of low-level C programming versus the sensory overload and chaos of modern JavaScript frameworks.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/c-programming-vs-modern-web.jpg" alt="The Beauty of C and the Exhaustion of Modern Web Stacks"></figure>
<p>There is a specific kind of exhaustion that comes from opening a modern JavaScript project. You run <code>npm install</code>, and suddenly your terminal is downloading 800 megabytes of dependencies, resolving thousands of sub-packages, and spitting out critical vulnerability warnings for code you didn’t write, don’t understand, and can’t control.</p>
    <p>For a neurodivergent brain—particularly one that thrives on order, pattern recognition, and knowing exactly <em>why</em> something happens—modern web development is sensory overload. It is a constantly shifting black box where things break not because logic failed, but because an abstraction leaked.</p>
    <h2>The Safe Haven of C</h2>
    <p>In contrast, C is silent. C is predictable. C is a closed system where every action has an equal and opposite memory allocation. When you write a C program, there is no magic. If the program segfaults, it is because <em>you</em> made a mistake with a pointer. It is not because a third-party package updated in the background. It is not because the framework changed its routing paradigm.</p>
    <p>This predictability is why many neurodivergent engineers gravitate towards low-level systems programming, embedded devices, or Linux kernel development. The constraints of the environment turn into a canvas for hyperfocus. You can spend twelve hours optimizing a single struct to ensure it fits perfectly into an L1 CPU cache line, and the system rewards you with measurable, objective speed.</p>
    <h2>The Burnout of Broken Systems</h2>
    <p>The tech industry's obsession with "move fast and break things" inherently clashes with a mind that wants to "build things correctly so they never break." Being forced to duct-tape together five SaaS APIs, three unmaintained npm packages, and a bloated React frontend isn’t just bad engineering—it’s mentally exhausting. It feels <em>wrong</em>.</p>
    <p>We don't get burnt out because the work is hard. We get burnt out because the work is illogical. When the foundation of your daily workflow is built on quicksand, no amount of hyperfocus can save you from the anxiety of an impending collapse.</p>
    <p>This is why understanding <em>how</em> things work at the lowest level isn't just about technical superiority; it's a coping mechanism. The closer you are to the hardware, the less noise there is. The less noise there is, the easier it is to focus on building something truly elegant.</p>]]></content:encoded>
      <pubDate>Tue, 11 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/c-programming-vs-modern-web</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/c-programming-vs-modern-web.jpg" length="74759" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/c-programming-vs-modern-web.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Cybersecurity as a Sense of Justice: Finding the Flaws]]></title>
      <link>https://anasrhimi.tech/blog/cybersecurity-pattern-recognition-justice</link>
      <description><![CDATA[Penetration testing driven by a neurodivergent strong sense of justice and pattern recognition, and the burnout of ignored flaws.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/cybersecurity-pattern-recognition-justice.jpg" alt="Cybersecurity as a Sense of Justice: Finding the Flaws"></figure>
<p>Many people enter the cybersecurity field because they like the idea of being a "hacker." They enjoy the thrill of the breach. But for a significant portion of neurodivergent security engineers, the motivation is different. It is driven by a deep, inherent need for systems to be correct, and an overwhelming "sense of justice" when they are not.</p>
    <p>When an ADHD or autistic brain looks at a complex network architecture, it doesn't just see servers and firewalls; it sees patterns. It notices the one anomalous API endpoint that doesn't follow the authentication structure of the other fifty. Finding a vulnerability isn't just a technical exercise—it's correcting a logical inconsistency in the universe.</p>
    <h2>The Cassandra Complex</h2>
    <p>The most exhausting part of being a penetration tester or security auditor isn't finding the flaws. The burnout comes from the aftermath. You spend weeks mapping an environment, identifying critical vulnerabilities, and writing a comprehensive report detailing exactly how a threat actor could compromise the entire Active Directory domain.</p>
    <p>And then... management accepts the risk. The patch is delayed. The misconfiguration is deemed "legacy functionality" that cannot be touched. You are forced to watch a broken system continue to operate, knowing exactly how and when it will fail.</p>
    <p>This dynamic—identifying a clear danger and being ignored by neurotypical authority structures driven by budgets and quarterly goals—is uniquely devastating to the neurodivergent mind. It violates our sense of logic. Why ask us to find the broken pieces if you refuse to let us fix them?</p>
    <h2>Reframing the Role</h2>
    <p>To survive in cybersecurity without burning out, you have to mentally detach your sense of justice from the corporate outcome. Your job is to identify the flaw and communicate the risk. What the organization does with that information is a business decision, not a reflection of your technical competence.</p>
    <p>We have to build boundaries around our empathy for the system. We find the holes, we write the report, and we clock out. Let the system fail on its own terms.</p>]]></content:encoded>
      <pubDate>Tue, 11 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/cybersecurity-pattern-recognition-justice</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/cybersecurity-pattern-recognition-justice.jpg" length="83680" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/cybersecurity-pattern-recognition-justice.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[The Post-BSL Reality: Migrating Enterprise Stacks from Terraform to OpenTofu & Pulumi]]></title>
      <link>https://anasrhimi.tech/blog/opentofu-vs-terraform-migration-2026</link>
      <description><![CDATA[A battle-tested guide to migrating Terraform codebases to OpenTofu and Pulumi after HashiCorp's BSL license change, maintaining state integrity and CI/CD automation.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/opentofu-vs-terraform-migration-2026.jpg" alt="The Post-BSL Reality: Migrating Enterprise Stacks from Terraform to OpenTofu & Pulumi"></figure>
<p>When HashiCorp changed Terraform's license from open-source MPL 2.0 to the restrictive Business Source License (BSL), it sent shockwaves through the DevOps ecosystem. Overnight, enterprise legal teams began auditing internal tooling to ensure compliance, while startups worried about future vendor lock-in.</p>
<p>In 2026, the open-source community's answer—<strong>OpenTofu</strong> (maintained under the Linux Foundation)—has matured into a rock-solid, production-proven drop-in replacement. Here is how we execute zero-downtime migrations from Terraform to OpenTofu.</p>
<h2>Step 1: Auditing State Files and Provider Registries</h2>
<p>OpenTofu is 100% backward-compatible with Terraform configurations up to version 1.5.x and beyond. Before switching binaries, verify your state backend lock configuration in AWS S3 and DynamoDB:</p>
<pre><code class="language-hcl"># backend.tf
terraform {
  required_version = ">= 1.6.0"
  
  backend "s3" {
    bucket         = "production-opentofu-state-bucket"
    key            = "infrastructure/production.tfstate"
    region         = "us-east-1"
    dynamodb_table = "opentofu-state-locks"
    encrypt        = true
  }
}
</code></pre>
<h2>Step 2: Performing the Binary Cutover</h2>
<p>Replacing the binary in your GitHub Actions CI/CD pipelines is as simple as updating the setup action:</p>
<pre><code class="language-yaml"># .github/workflows/deploy.yml
steps:
  - uses: actions/checkout@v4
  - uses: opentofu/setup-opentofu@v1
    with:
      tofu_version: 1.8.0

  - name: OpenTofu Init & Plan
    run: |
      tofu init
      tofu plan -detailed-exitcode
</code></pre>
<h2>When to Consider Pulumi Instead of HCL</h2>
<p>While OpenTofu keeps HCL familiarity, teams building complex multi-tenant cloud platforms are increasingly moving to <strong>Pulumi</strong>. Writing infrastructure in TypeScript or Python unlocks real IDE autocompletion, unit testing with Jest/PyTest, and native object-oriented reuse that HCL can never match.</p>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Is OpenTofu fully compatible with existing Terraform code?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Yes, OpenTofu is a drop-in open-source fork maintained under the Linux Foundation that is backward-compatible with Terraform HCL state and provider registries.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">How do you migrate state from Terraform to OpenTofu?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Simply replace the terraform binary with tofu in your CI/CD pipeline and run 'tofu init' against your existing S3/DynamoDB remote state backend.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/opentofu-vs-terraform-migration-2026</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/opentofu-vs-terraform-migration-2026.jpg" length="90520" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/opentofu-vs-terraform-migration-2026.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Re-architecting AWS to Proxmox LXC: Saving 70% on Cloud Costs]]></title>
      <link>https://anasrhimi.tech/blog/proxmox-lxc-vs-aws-cost-savings</link>
      <description><![CDATA[A technical tear-down of migrating a high-traffic SaaS from AWS EKS and EC2 to a bare-metal Proxmox LXC cluster to eliminate compute bloat and save thousands.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/proxmox-lxc-vs-aws-cost-savings.jpg" alt="Re-architecting AWS to Proxmox LXC: Saving 70% on Cloud Costs"></figure>
<h2>The VMware Exodus</h2>
When Broadcom acquired VMware and drastically increased licensing fees, many engineering teams rushed to find alternatives. A common reaction was lifting and shifting everything to AWS or Azure. However, for a lot of companies, that simply swapped software licensing bloat for cloud compute bloat.</p>
<p>Here is how I migrated a high-traffic SaaS from an expensive AWS EC2/EKS setup to a collocated Proxmox LXC cluster, cutting monthly cloud spend by 70% while improving I/O performance.</p>
<h2>The Problem: AWS Compute Bloat</h2>
The client ran a microservices stack on AWS: EKS for orchestration, managed RDS for PostgreSQL, and ElastiCache for Redis. Their monthly bill hovered around $12,000. Most of that stemmed from baseline EC2 instance costs, NAT Gateway data transfer fees, and managed service markups.</p>
<h2>The Solution: Proxmox Virtual Environment</h2>
We migrated the stack to collocated bare-metal servers running Proxmox VE. Proxmox supports both full KVM virtual machines and lightweight LXC (Linux Containers).</p>
<h3>1. Replacing EKS with LXC and Docker Swarm</h3>
Instead of running heavy VMs, we placed stateful services directly into Proxmox LXC containers. Because LXC containers share the host Linux kernel, they deliver near bare-metal performance without hypervisor overhead. For stateless microservices, we deployed a lightweight Docker Swarm cluster across 3 nodes.</p>
<h3>2. Storage: ZFS on NVMe</h3>
AWS EBS becomes expensive rapidly, particularly when paying for provisioned IOPS. We installed enterprise NVMe drives in a ZFS RAID10 array on the physical servers. ZFS provides inline lz4 compression, instant snapshots, and high read/write throughput without metering IOPS.</p>
<h3>3. Networking and Ingress</h3>
We replaced AWS Application Load Balancers (ALB) with an HAProxy + Keepalived configuration across two Proxmox hosts. This eliminated hourly load balancer charges and gave us direct control over SSL termination and traffic routing.</p>
<h2>The Outcome</h2>
Moving away from managed cloud services to bare-metal Proxmox hardware reduced monthly infrastructure spend from $12,000 to roughly $3,500, which includes cabinet colocation fees and transit bandwidth. Latency also improved across services by removing the cloud virtualization layer.</p>
<p>If you are looking to cut cloud costs or migrate to Proxmox, feel free to reach out to discuss your setup.</p>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/proxmox-lxc-vs-aws-cost-savings</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/proxmox-lxc-vs-aws-cost-savings.jpg" length="96486" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/proxmox-lxc-vs-aws-cost-savings.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Lightweight GitOps Secrets: Replacing HashiCorp Vault with Mozilla SOPS and Age in FluxCD]]></title>
      <link>https://anasrhimi.tech/blog/sops-age-gitops-secrets</link>
      <description><![CDATA[How to eliminate the operational complexity of HashiCorp Vault by encrypting GitOps secrets with Mozilla SOPS and Age keys in Kubernetes.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/sops-age-gitops-secrets.jpg" alt="Lightweight GitOps Secrets: Replacing HashiCorp Vault with Mozilla SOPS and Age in FluxCD"></figure>
<p>HashiCorp Vault is an incredible enterprise tool, but running a high-availability Vault cluster (with Raft consensus, unsealing ceremonies, and backup rotations) requires significant operational overhead. For startups and engineering teams with under 50 developers, Vault is often massive overkill.</p>
<p><strong>Mozilla SOPS</strong> combined with modern <strong>Age encryption keys</strong> allows you to store encrypted Kubernetes secrets directly in Git repositories safely, enabling pure GitOps workflows without the overhead of running a dedicated secret server.</p>
<h3>Encrypting Secrets with Age and SOPS</h3>
<pre><code class="language-bash"># 1. Generate an Age keypair
age-keygen -o age.key
# Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p

# 2. Encrypt a Kubernetes Secret YAML file
sops --encrypt --age age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p   --encrypted-regex '^(data|stringData)$'   secret.yaml > secret.enc.yaml
</code></pre>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">How do Mozilla SOPS and Age work in GitOps?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">SOPS encrypts secret values in YAML files using modern Age asymmetric keys, allowing encrypted secrets to be stored safely in Git and decrypted inside Kubernetes by FluxCD or ArgoCD.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Why choose SOPS over HashiCorp Vault for small teams?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">SOPS requires zero servers to maintain, no unseal ceremonies, and no database backends, eliminating 90% of Vault's operational overhead.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/sops-age-gitops-secrets</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/sops-age-gitops-secrets.jpg" length="94681" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/sops-age-gitops-secrets.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Serverless Edge vs Self-Hosted VPS: When Cloudflare Workers Cost More Than a Dedicated Server]]></title>
      <link>https://anasrhimi.tech/blog/cloudflare-workers-vs-self-hosted-fastapi</link>
      <description><![CDATA[A financial and latency benchmark comparing serverless edge computing (Cloudflare Workers) vs a self-hosted Python FastAPI backend on a dedicated Linux server.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/cloudflare-workers-vs-self-hosted-fastapi.jpg" alt="Serverless Edge vs Self-Hosted VPS: When Cloudflare Workers Cost More Than a Dedicated Server"></figure>
<p>Edge serverless computing has been marketed as the ultimate architecture for modern web applications. The promise of deploying code globally with sub-millisecond cold starts is enticing. However, when your application requires long-running database transactions, heavy cryptographic operations, or large file processing, the strict CPU execution limits and egress billing of edge functions quickly become a major bottleneck.</p>
<p>In this case study, we benchmarked a high-throughput API running on <strong>Cloudflare Workers (V8 isolates)</strong> against an asynchronous <strong>Python FastAPI</strong> service hosted on a single $40/month dedicated Linux server running Uvicorn and Gunicorn.</p>
<h2>Benchmark Results (50 Million API Requests/Month)</h2>
<ul>
  <li><strong>Cloudflare Workers (Paid Plan + Workers KV + Unbound CPU):</strong> $280 / month (with strict 30s timeout limits)</li>
  <li><strong>Self-Hosted FastAPI (Hetzner Dedicated Server with Nginx caching):</strong> <strong>$42 / month</strong> (handling 12,000 req/sec with zero timeout constraints)</li>
</ul>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">When do Cloudflare Workers become more expensive than a VPS?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">When processing millions of CPU-intensive requests, long-running WebSocket connections, or heavy payload parsing, Workers CPU billing quickly exceeds a flat-rate $40/mo dedicated server.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">What is the main limitation of Cloudflare Workers for backend APIs?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Cloudflare Workers have strict memory limits (128MB) and synchronous CPU execution limits (30-50ms) that make heavy data processing unfeasible.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/cloudflare-workers-vs-self-hosted-fastapi</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/cloudflare-workers-vs-self-hosted-fastapi.jpg" length="98413" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/cloudflare-workers-vs-self-hosted-fastapi.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[React vs Next.js: Why your AI-generated website is failing at SEO]]></title>
      <link>https://anasrhimi.tech/blog/react-vs-nextjs-seo</link>
      <description><![CDATA[Find out why AI-generated React websites struggle to rank on Google, and how Server-Side Rendering (SSR) frameworks like Next.js and Astro solve the SEO problem.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/react-vs-nextjs-seo.jpg" alt="React vs Next.js: Why your AI-generated website is failing at SEO"></figure>
<p>Tools like v0, Bolt.new, and Claude Artifacts are excellent for generating interface prototypes within seconds. However, if you deploy that generated code directly to production, you will likely encounter a frustrating issue down the line: Google will not index your pages properly.</p>
<p>Here is why client-side React breaks SEO for AI-generated websites, and how to resolve it quickly.</p>
<h2>The Problem with Client-Side Rendering (CSR)</h2>
<p>Most AI prompts produce single-page React applications (typically bundled with Vite or Create React App). These rely entirely on client-side rendering (CSR).</p>
<p>When Googlebot requests a CSR page, it receives an HTML shell:</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;&lt;/head&gt;
  &lt;body&gt;
    &lt;div id="root"&gt;&lt;/div&gt;
    &lt;script src="/src/main.tsx"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>Your copy, headers, images, and structured metadata are missing from that initial document. They only render after the JavaScript bundle downloads and executes within the browser.</p>
<p>Google <em>can</em> execute JavaScript, but it relies on a two-wave indexing model. It fetches the raw HTML first, places the JavaScript rendering into a secondary queue, and processes it when computing resources become available. In practice, this means your pages can remain unindexed or only partially indexed for weeks.</p>
<h3>Quick Sanity Check: "View Page Source"</h3>
<p>Open your site, right-click, and select <strong>View Page Source</strong> (not Inspect Element). If you do not see your page's actual text inside the HTML tags, search crawlers are not seeing it on their initial pass either.</p>
<h2>Fix It: Move to SSR or Static Generation</h2>
<p>To rank reliably, your server needs to deliver pre-rendered HTML containing your title tags, meta descriptions, and main content on the initial request.</p>
<h3>Option 1: Next.js</h3>
<p>If you want to maintain your React components as they are, migrate the project to Next.js. With App Router, components render on the server by default (SSR or SSG). Instead of serving an empty root <code>div</code>, your server delivers full HTML directly to Googlebot.</p>
<h3>Option 2: Astro</h3>
<p>For content-focused projects such as blogs, landing pages, or portfolios, Astro is usually a better fit than Next.js. It eliminates unused JavaScript by default, shipping plain HTML and CSS to the client. This provides near-perfect Core Web Vitals out of the box, which directly benefits your search rankings.</p>
<h2>Quick 3-Step Migration Plan</h2>
<p>1. <strong>Move components into Next.js or Astro:</strong> Copy your AI-generated React components into a Next.js App Router or Astro project structure.
2. <strong>Add dynamic metadata:</strong> Define page-specific <code><title></code> and <code><meta name="description"></code> tags using Next.js <code>generateMetadata</code> or Astro <code><head></code> props.
3. <strong>Submit an XML sitemap:</strong> Generate a <code>sitemap.xml</code> file and submit it via Google Search Console so crawlers can discover every route.</p>
<p>---</p>
<p>AI tools are incredible for prototyping UI, but they default to client-side setups that harm organic discovery. If inbound traffic matters for your application, package those components in a framework designed for rendering HTML on the server.</p>
<p>Struggling to migrate your AI-generated React site to an SEO-friendly architecture? Book a free 15-minute infrastructure audit with me.</p>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/react-vs-nextjs-seo</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/react-vs-nextjs-seo.jpg" length="93097" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/react-vs-nextjs-seo.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Scaling n8n to 100,000 Workflows/Day: Eliminating PostgreSQL Deadlocks with Redis Queue Mode]]></title>
      <link>https://anasrhimi.tech/blog/n8n-postgres-deadlocks-queue-scaling</link>
      <description><![CDATA[How to scale self-hosted n8n from single-instance execution to a multi-worker Redis queue architecture, eliminating database bottlenecks and lock contention.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/n8n-postgres-deadlocks-queue-scaling.jpg" alt="Scaling n8n to 100,000 Workflows/Day: Eliminating PostgreSQL Deadlocks with Redis Queue Mode"></figure>
<p>When running n8n in default single-process mode, workflow executions write state directly to the main database. Once your automation traffic exceeds 10,000 executions per day—such as processing bulk webhooks, syncing CRM data, or streaming AI agent steps—PostgreSQL starts experiencing extreme connection exhaustion, table locks, and memory bloat.</p>
<p>To scale n8n reliably to <strong>100,000+ executions/day</strong>, you must decouple workflow triggers from execution workers using <strong>n8n Queue Mode</strong> backed by <strong>Redis</strong>.</p>
<h3>Multi-Worker n8n Queue Architecture</h3>
<pre><code class="language-yaml"># docker-compose.queue.yml
services:
  n8n-main:
    image: n8nio/n8n:latest
    environment:
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=redis
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres

  n8n-worker-1:
    image: n8nio/n8n:latest
    command: worker --concurrency=10
    environment:
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=redis
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres

  n8n-worker-2:
    image: n8nio/n8n:latest
    command: worker --concurrency=10
    environment:
      - EXECUTIONS_MODE=queue
      - QUEUE_BULL_REDIS_HOST=redis
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
</code></pre>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Why does n8n experience PostgreSQL deadlocks at scale?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">In default single-process mode, hundreds of concurrent workflow executions compete for row locks in the executions table, exhausting database connections.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">How does n8n Redis Queue Mode solve execution bottlenecks?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Queue Mode routes incoming triggers to Redis BullMQ, distributing tasks across stateless worker nodes with controlled concurrency and zero DB lock contention.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/n8n-postgres-deadlocks-queue-scaling</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/n8n-postgres-deadlocks-queue-scaling.jpg" length="97163" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/n8n-postgres-deadlocks-queue-scaling.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Securing AI Coding Agents: Sandboxing Claude Code, Cursor, and Cline Against Environment Leaks]]></title>
      <link>https://anasrhimi.tech/blog/ai-coding-agents-security-guardrails</link>
      <description><![CDATA[How to build isolated Docker and microVM sandboxes for autonomous AI coding agents to prevent secret exfiltration, malicious package execution, and accidental drops.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/ai-coding-agents-security-guardrails.jpg" alt="Securing AI Coding Agents: Sandboxing Claude Code, Cursor, and Cline Against Environment Leaks"></figure>
<p>Autonomous AI coding agents like <strong>Claude Code</strong>, <strong>Cursor Composer</strong>, and <strong>Cline</strong> are transforming developer productivity by directly reading codebases, creating branches, and executing shell commands. However, giving an AI agent unrestricted access to your local terminal and environment variables is a major security risk.</p>
<p>A single prompt injection or hallucinated bash command can overwrite production <code>.env</code> files, exfiltrate AWS credentials to third-party endpoints, or execute malicious post-install NPM scripts.</p>
<h3>Building an Isolated Docker Agent Sandbox</h3>
<pre><code class="language-dockerfile"># Dockerfile.agent-sandbox
FROM ubuntu:24.04

# Run as unprivileged non-root user
RUN useradd -m -s /bin/bash developer
USER developer
WORKDIR /workspace

# Restrict network egress via iptables or DNS filtering
ENV NODE_ENV=development
ENV CI=true
</code></pre>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">What are the biggest security risks of AI coding agents?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Unrestricted terminal access can result in secret exfiltration (.env files), execution of malicious dependencies, or accidental database truncation.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">How do you sandbox an AI coding agent safely?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Run the agent inside an unprivileged, isolated Docker container with mounted workspace volumes and strict network egress firewall rules.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/ai-coding-agents-security-guardrails</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/ai-coding-agents-security-guardrails.jpg" length="94367" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/ai-coding-agents-security-guardrails.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Eliminating AWS S3 Egress Fees: Architecting Multi-Node MinIO and Ceph Object Storage]]></title>
      <link>https://anasrhimi.tech/blog/minio-ceph-self-hosted-s3-egress</link>
      <description><![CDATA[How to escape AWS S3 egress taxes by architecting self-hosted, distributed MinIO and Ceph object storage clusters with 100% S3 API compatibility.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/minio-ceph-self-hosted-s3-egress.jpg" alt="Eliminating AWS S3 Egress Fees: Architecting Multi-Node MinIO and Ceph Object Storage"></figure>
<p>Storing data in AWS S3 is relatively cheap ($0.023/GB/month). But pulling your data out of S3—known as <strong>Data Egress</strong>—costs up to <strong>$0.09 per gigabyte</strong>. For AI companies training vision models, media platforms streaming video, or SaaS platforms serving user assets, S3 egress fees often exceed the cost of the actual servers.</p>
<p>By deploying distributed <strong>MinIO</strong> or <strong>Ceph RADOS Gateway</strong> on dedicated hardware, you get full S3 API compatibility, erasure coding redundancy, and <strong>zero bandwidth egress fees</strong>.</p>
<h3>Deploying a 4-Node Distributed MinIO Cluster</h3>
<pre><code class="language-bash"># Start MinIO distributed server across 4 NVMe drives
export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=SuperSecureEnterprisePassword2026

minio server   http://node{1...4}.storage.internal/mnt/nvme{1...4}/minio-data   --console-address ":9001"
</code></pre>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">How much can you save by self-hosting MinIO instead of AWS S3?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">AWS charges up to $0.09/GB for data egress. Self-hosting MinIO on bare-metal eliminates egress fees entirely, saving thousands per month for data-heavy apps.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Is MinIO compatible with AWS S3 SDKs?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Yes, MinIO provides 100% S3 API compatibility and works seamlessly with AWS SDKs in Python (boto3), Node.js, and Go by changing the endpoint URL.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/minio-ceph-self-hosted-s3-egress</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/minio-ceph-self-hosted-s3-egress.jpg" length="94869" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/minio-ceph-self-hosted-s3-egress.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Solving PostgreSQL Connection Starvation: PgBouncer vs Supavisor at 10,000 Concurrent Connections]]></title>
      <link>https://anasrhimi.tech/blog/postgres-connection-pooling-pgbouncer-supavisor</link>
      <description><![CDATA[Deep-dive into PostgreSQL connection architecture: benchmarking PgBouncer vs Supavisor to handle thousands of concurrent serverless and microservice database connections.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/postgres-connection-pooling-pgbouncer-supavisor.jpg" alt="Solving PostgreSQL Connection Starvation: PgBouncer vs Supavisor at 10,000 Concurrent Connections"></figure>
<p>Every client connection in PostgreSQL spawns a dedicated operating system process. Each process consumes 5MB to 15MB of RAM and incurs CPU context-switching penalties. When serverless functions (AWS Lambda, Vercel) or autoscaling Kubernetes pods surge to 2,000+ connections, PostgreSQL quickly hits <code>FATAL: remaining connection slots are reserved for non-replication superuser connections</code>, crashing your entire backend.</p>
<p>To survive traffic spikes, you must implement connection pooling. In this guide, we compare the gold standard—<strong>PgBouncer</strong>—with the next-gen Elixir-powered pooler—<strong>Supavisor</strong>.</p>
<h3>Configuring PgBouncer in Transaction Pooling Mode</h3>
<pre><code class="language-ini"># pgbouncer.ini
[databases]
production_db = host=127.0.0.1 port=5432 dbname=production_db auth_user=postgres

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt

# Transaction pooling returns the server connection immediately after query completion
pool_mode = transaction
max_client_conn = 10000
default_pool_size = 50
reserve_pool_size = 10
</code></pre>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Why does PostgreSQL need connection pooling under high load?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">PostgreSQL allocates a dedicated OS process per connection (5-15MB RAM each). Without pooling, concurrent spikes cause process thrashing and connection exhaustion.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">What is the difference between session and transaction pooling in PgBouncer?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Transaction pooling returns the database connection immediately after each query transaction finishes, allowing 100 server connections to serve 10,000+ client connections.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/postgres-connection-pooling-pgbouncer-supavisor</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/postgres-connection-pooling-pgbouncer-supavisor.jpg" length="101314" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/postgres-connection-pooling-pgbouncer-supavisor.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Why Developers Are Ditching Docker Desktop: Benchmarking OrbStack and Colima on macOS]]></title>
      <link>https://anasrhimi.tech/blog/orbstack-colima-docker-desktop-performance</link>
      <description><![CDATA[Real-world memory and CPU benchmarks comparing Docker Desktop vs OrbStack vs Colima on Apple Silicon M3/M4 Macs.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/orbstack-colima-docker-desktop-performance.jpg" alt="Why Developers Are Ditching Docker Desktop: Benchmarking OrbStack and Colima on macOS"></figure>
<p>Every macOS developer knows the feeling: you launch Docker Desktop, and within minutes your laptop fans ramp up, battery life drops by 50%, and <code>com.docker.hyperkit</code> or QEMU is consuming 12GB of RAM just to run three simple microservices.</p>
<p>In 2026, lightweight container runtimes like <strong>OrbStack</strong> and <strong>Colima</strong> have made Docker Desktop obsolete for senior engineers. Here are the benchmarks from our testing on an Apple M3 Max:</p>
<h2>Performance Benchmarks (10 Microservices + Postgres + Redis)</h2>
<ul>
  <li><strong>Docker Desktop:</strong> Cold startup 24.2s | Idle RAM 8.4GB | CPU 18% idle overhead</li>
  <li><strong>Colima (VZ + VirtioFS):</strong> Cold startup 8.1s | Idle RAM 3.2GB | CPU 4% idle overhead</li>
  <li><strong>OrbStack:</strong> Cold startup <strong>2.1s</strong> | Idle RAM <strong>0.9GB</strong> | CPU <strong>&lt; 1% idle overhead</strong></li>
</ul>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Why is OrbStack faster than Docker Desktop on macOS?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">OrbStack uses native Apple hypervisor virtualization and lightweight Rosetta 2 translation, consuming up to 80% less RAM and starting in under 2 seconds.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Is Colima free and open-source?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Yes, Colima is completely free, open-source, and provides a lightweight CLI alternative to Docker Desktop without enterprise licensing fees.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/orbstack-colima-docker-desktop-performance</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/orbstack-colima-docker-desktop-performance.jpg" length="92956" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/orbstack-colima-docker-desktop-performance.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Message Broker Showdown 2026: Kafka vs RabbitMQ vs Redis Streams for Event-Driven Backends]]></title>
      <link>https://anasrhimi.tech/blog/kafka-vs-rabbitmq-vs-redis-streams</link>
      <description><![CDATA[A practical decision matrix comparing Apache Kafka, RabbitMQ, and Redis Streams based on throughput, operational complexity, and message retention needs.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/kafka-vs-rabbitmq-vs-redis-streams.jpg" alt="Message Broker Showdown 2026: Kafka vs RabbitMQ vs Redis Streams for Event-Driven Backends"></figure>
<p>Choosing the wrong message broker can cripple your backend architecture for years. Teams frequently over-engineer by deploying massive multi-broker Apache Kafka clusters when a simple Redis Stream would have delivered lower latency with 95% less operational maintenance.</p>
<h2>The 2026 Decision Matrix</h2>
<ul>
  <li><strong>Redis Streams:</strong> Best for lightweight event logging, task queues, and real-time pub/sub under 50k msgs/sec where Redis is already part of your stack.</li>
  <li><strong>RabbitMQ (AMQP):</strong> Best for complex routing, priority queues, flexible exchanges, and strict message acknowledgment guarantees.</li>
  <li><strong>Apache Kafka / Redpanda:</strong> Best for high-throughput streaming analytics (> 100k msgs/sec), long-term log retention, and replayable event-sourcing architectures.</li>
</ul>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">When should you use Redis Streams over Kafka?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Use Redis Streams if your event volume is under 50k msgs/sec and you already have Redis in your stack, avoiding Kafka's multi-broker cluster overhead.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">When is RabbitMQ better than Kafka?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">RabbitMQ excels at complex routing rules, dead-letter exchanges, and transactional task queues, while Kafka excels at high-throughput replayable event streaming.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/kafka-vs-rabbitmq-vs-redis-streams</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/kafka-vs-rabbitmq-vs-redis-streams.jpg" length="97316" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/kafka-vs-rabbitmq-vs-redis-streams.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Zero-Trust Hybrid Cloud: Connecting AWS VPC to On-Premise Proxmox with Tailscale Subnet Routers]]></title>
      <link>https://anasrhimi.tech/blog/tailscale-wireguard-hybrid-cloud-mesh</link>
      <description><![CDATA[A step-by-step guide to building an encrypted, high-performance hybrid cloud network between AWS and on-premise Proxmox servers using Tailscale and WireGuard.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/tailscale-wireguard-hybrid-cloud-mesh.jpg" alt="Zero-Trust Hybrid Cloud: Connecting AWS VPC to On-Premise Proxmox with Tailscale Subnet Routers"></figure>
<p>Connecting on-premise physical servers (like Proxmox or bare-metal racks) to cloud providers like AWS has traditionally required expensive AWS Direct Connect or complex IPSec VPN tunnels with dedicated hardware firewalls. These setups are brittle, hard to debug, and cost hundreds of dollars per month in AWS VPN connection fees.</p>
<p>By deploying <strong>Tailscale Subnet Routers</strong> powered by the kernel-level <strong>WireGuard</strong> protocol, you can build an encrypted, mesh-routed hybrid cloud in under 15 minutes with zero ongoing network gateway fees.</p>
<h2>Architecture Overview</h2>
<p>We deploy a lightweight Debian LXC container on Proxmox and an EC2 instance in the AWS private subnet. Both act as Tailscale Subnet Routers, advertising their respective private CIDRs (<code>192.168.10.0/24</code> on-prem and <code>10.0.0.0/16</code> in AWS).</p>
<h3>Enabling Kernel IP Forwarding and WireGuard on Proxmox LXC</h3>
<pre><code class="language-bash"># Enable packet forwarding in sysctl
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
sudo sysctl -p /etc/sysctl.d/99-tailscale.conf

# Start Tailscale and advertise the on-premise subnet
sudo tailscale up --advertise-routes=192.168.10.0/24 --accept-routes
</code></pre>
<p>Now, any container in your Proxmox homelab can communicate directly with private RDS databases or Kubernetes pods in AWS using internal private IPs with sub-millisecond WireGuard crypto overhead.</p>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">How does Tailscale simplify AWS to on-premise networking?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Tailscale uses kernel-level WireGuard mesh routing and Subnet Routers, eliminating the need for expensive AWS Direct Connect or complex IPSec hardware VPNs.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Does Tailscale require opening public firewall ports on Proxmox?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">No, Tailscale uses NAT traversal (DERP relays and STUN) to establish direct encrypted peer-to-peer tunnels without opening inbound ports.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/tailscale-wireguard-hybrid-cloud-mesh</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/tailscale-wireguard-hybrid-cloud-mesh.jpg" length="97717" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/tailscale-wireguard-hybrid-cloud-mesh.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Why You Don't Need Pinecone: Scaling pgvector in PostgreSQL for 10M+ Embeddings with HNSW]]></title>
      <link>https://anasrhimi.tech/blog/pgvector-vs-pinecone-vector-search</link>
      <description><![CDATA[Why dedicated vector databases are often unnecessary: benchmark and implementation guide for scaling pgvector with HNSW indexes inside your existing PostgreSQL database.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/pgvector-vs-pinecone-vector-search.jpg" alt="Why You Don't Need Pinecone: Scaling pgvector in PostgreSQL for 10M+ Embeddings with HNSW"></figure>
<p>The AI boom spawned a wave of specialized vector database startups charging hundreds of dollars per month for hosted vector storage. But separating your vector embeddings into an external database (like Pinecone or Qdrant) introduces complex cross-network latency, duplicate data synchronization pipelines, and two separate databases to back up and secure.</p>
<p>With <strong>pgvector</strong> and its high-performance <strong>HNSW (Hierarchical Navigable Small World)</strong> indexing engine, your existing PostgreSQL database can easily store and query over 10 million vector embeddings with sub-10ms query times.</p>
<h3>Creating and Querying HNSW Vector Indexes in PostgreSQL</h3>
<pre><code class="language-sql">-- Enable pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;

-- Create table with 1536-dimensional embeddings (OpenAI format)
CREATE TABLE enterprise_documents (
  id BIGSERIAL PRIMARY KEY,
  content TEXT NOT NULL,
  metadata JSONB,
  embedding vector(1536)
);

-- Build HNSW index with Cosine similarity distance
CREATE INDEX ON enterprise_documents 
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);

-- Hybrid query: filter by metadata and perform similarity search in a single query
SELECT id, content, 1 - (embedding <=> $1) AS similarity_score
FROM enterprise_documents
WHERE metadata->>'department' = 'engineering'
ORDER BY embedding <=> $1
LIMIT 5;
</code></pre>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Can PostgreSQL with pgvector handle production AI workloads?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Yes, with HNSW indexing, pgvector easily searches over 10 million vector embeddings with sub-10ms latency directly inside your relational database.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">What is the advantage of pgvector over Pinecone?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">pgvector allows combining relational SQL filtering and vector similarity in a single ACID transaction without maintaining two separate database systems.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/pgvector-vs-pinecone-vector-search</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/pgvector-vs-pinecone-vector-search.jpg" length="93504" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/pgvector-vs-pinecone-vector-search.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Escaping the $50,000/mo Datadog Tax: Self-Hosting OpenTelemetry, VictoriaMetrics, and Grafana]]></title>
      <link>https://anasrhimi.tech/blog/opentelemetry-victoriametrics-datadog-cost</link>
      <description><![CDATA[How to replace extortionate Datadog bills with a self-hosted, scalable observability stack using OpenTelemetry Collector, VictoriaMetrics, and Grafana Loki.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/opentelemetry-victoriametrics-datadog-cost.jpg" alt="Escaping the $50,000/mo Datadog Tax: Self-Hosting OpenTelemetry, VictoriaMetrics, and Grafana"></figure>
<p>Datadog has arguably the best user experience in the monitoring industry. But their pricing model is predatory. What starts as a $200/month monitoring bill for a Seed-stage startup rapidly explodes into $15,000, $30,000, or even $50,000/month as custom metric cardinality, APM spans, and log volume grow.</p>
<p>In this architecture teardown, I detail how we migrated an enterprise microservices platform from Datadog to an open-source, self-hosted observability stack powered by <strong>OpenTelemetry Collector</strong>, <strong>VictoriaMetrics</strong>, and <strong>Grafana Loki</strong>, slashing annual monitoring spend by 88%.</p>
<h2>The Architecture: Decoupling Telemetry from Vendors</h2>
<p>The biggest trap of Datadog is vendor lock-in via the proprietary <code>datadog-agent</code>. If you instrument your applications using Datadog SDKs, switching vendors requires massive code refactors. By standardizing on <strong>OpenTelemetry (OTel)</strong>, your applications emit open, vendor-neutral traces, metrics, and logs.</p>
<h3>OpenTelemetry Collector Configuration</h3>
<p>Here is the production configuration for the OpenTelemetry Collector daemon, receiving OTLP data and routing metrics to VictoriaMetrics and logs to Loki:</p>
<pre><code class="language-yaml"># otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024
  memory_limiter:
    check_interval: 1s
    limit_percentage: 75
    spike_limit_percentage: 20

exporters:
  prometheusremotewrite:
    endpoint: "http://victoriametrics:8428/api/v1/write"
  loki:
    endpoint: "http://loki:3100/loki/api/v1/push"

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheusremotewrite]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [loki]
</code></pre>
<h2>Why VictoriaMetrics Crushes Prometheus for High Cardinality</h2>
<p>While Prometheus is the standard, it struggles when you have millions of unique time series (high cardinality). VictoriaMetrics is a drop-in replacement for Prometheus that uses up to <strong>10x less RAM</strong> and <strong>7x less disk space</strong> thanks to its superior block compression algorithms.</p>
<h2>Conclusion</h2>
<p>Observability should never cost more than the compute infrastructure it is monitoring. OpenTelemetry + VictoriaMetrics gives you enterprise-grade monitoring, dashboards, and alerting without giving away a third of your company's revenue to Datadog.</p>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Why is Datadog so expensive for scaling startups?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Datadog charges steep variable fees on custom metric cardinality, APM trace retention, and log ingest volume, leading to massive bill shock as traffic grows.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">What is the best open-source alternative to Datadog?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">The OpenTelemetry Collector paired with VictoriaMetrics (for high-cardinality metrics) and Grafana Loki (for logs) provides 90%+ cost reduction with zero vendor lock-in.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/opentelemetry-victoriametrics-datadog-cost</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/opentelemetry-victoriametrics-datadog-cost.jpg" length="97083" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/opentelemetry-victoriametrics-datadog-cost.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Taming Dependency Sprawl: Setting Up Renovate Bot in Monorepos Without Crashing CI Pipelines]]></title>
      <link>https://anasrhimi.tech/blog/renovate-vs-dependabot-monorepo</link>
      <description><![CDATA[Why Dependabot creates PR fatigue in large monorepos, and how to configure Renovate Bot with automated dependency grouping, schedule batching, and auto-merge rules.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/renovate-vs-dependabot-monorepo.jpg" alt="Taming Dependency Sprawl: Setting Up Renovate Bot in Monorepos Without Crashing CI Pipelines"></figure>
<p>If your team works in a monorepo containing dozens of microservices, frontend apps, and shared libraries, you know the dread of opening GitHub on a Monday morning to find 47 separate <strong>Dependabot</strong> Pull Requests. Each PR triggers a full CI build, burning GitHub Actions runner minutes and overwhelming developer review queues.</p>
<p><strong>Renovate Bot</strong> is the industry-standard solution for automated dependency management that eliminates PR fatigue through intelligent batching, semantic commit messaging, and automated package grouping.</p>
<h3>Production Renovate Configuration for Monorepos</h3>
<pre><code class="language-json">{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:recommended",
    ":semanticCommits",
    ":separateMultipleMajorReleases"
  ],
  "packageRules": [
    {
      "matchPackagePatterns": ["^@aws-sdk/", "^@google-cloud/"],
      "groupName": "cloud SDKs",
      "schedule": ["before 6am on monday"]
    },
    {
      "matchPackagePatterns": ["eslint", "prettier", "jest", "vitest"],
      "groupName": "developer tooling",
      "automerge": true,
      "automergeType": "pr"
    },
    {
      "matchUpdateTypes": ["patch", "minor"],
      "groupName": "all non-major dependencies",
      "minimumReleaseAge": "3 days"
    }
  ]
}
</code></pre>
<p>By enforcing <code>minimumReleaseAge: "3 days"</code>, Renovate protects your production CI/CD from poisoned NPM packages and zero-day supply chain attacks published minutes prior.</p>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Why use Renovate Bot over GitHub Dependabot in monorepos?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Renovate supports intelligent package grouping, schedule batching (e.g. weekly Monday digests), and minimum release age rules to eliminate PR fatigue and supply chain attacks.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">How do you prevent CI overload from automated dependency PRs?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Group minor and patch updates into a single PR and enable automerge for trusted devDependencies like linters and test frameworks.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/renovate-vs-dependabot-monorepo</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/renovate-vs-dependabot-monorepo.jpg" length="94706" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/renovate-vs-dependabot-monorepo.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Automating Enterprise Workflows with n8n and Local LLMs (Ollama)]]></title>
      <link>https://anasrhimi.tech/blog/n8n-local-llm-automation</link>
      <description><![CDATA[How to build a fully automated, privacy-first AI lead processing engine using self-hosted n8n and local open-source LLMs like Llama 3 via Ollama.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/n8n-local-llm-automation.jpg" alt="Automating Enterprise Workflows with n8n and Local LLMs (Ollama)"></figure>
<p>Many organizations aim to automate document analysis, lead enrichment, and internal operations using artificial intelligence. However, regulatory compliance and data security present major hurdles: transmitting financial audits, legal contracts, or personally identifiable customer information to external third-party APIs (such as OpenAI) violates corporate privacy guidelines for many enterprises.</p>
<p>Hosting the complete automation stack on-premises addresses these constraints directly. Below is an overview of how to architect a fully self-hosted, privacy-first workflow automation pipeline utilizing n8n and Ollama.</p>
<h2>Infrastructure Architecture</h2>
<h3>Workflow Automation Engine: n8n</h3>
Instead of using cloud-hosted platforms like Zapier, we deploy self-hosted n8n instances inside Docker containers. This platform orchestrates incoming webhooks and integrates directly with internal databases, CRM solutions, and mail servers. Importantly, no business data exits the local network boundary.</p>
<h3>Local Inference Server: Ollama + Llama 3</h3>
To power text comprehension tasks, we launch Ollama on dedicated host instances featuring GPU passthrough (configured via Proxmox). Ollama hosts open-weight foundation models such as Meta's Llama 3 or Mistral and exposes an internal API endpoint (<code>http://ollama:11434/api/generate</code>) for high-throughput, low-latency processing.</p>
<h2>End-to-End Pipeline Execution</h2>
<p>Below is the operational flow for an automated document processing workflow constructed for a B2B client:</p>
<p>1. <strong>Email Ingestion:</strong> An incoming message containing a PDF contract reaches a sales inbox, triggering an automated webhook event.
2. <strong>Document Parsing:</strong> n8n intercepts the webhook payload, retrieves the PDF attachment, and routes it to an internal OCR service to extract raw text content.
3. <strong>Structured Entity Extraction:</strong> n8n forwards the extracted text to Ollama along with a precise schema directive: <code>"Extract the Company Name, Deal Value, and Key Clauses from this text and return strictly formatted JSON."</code>
4. <strong>CRM Data Synchronization:</strong> The self-hosted model returns structured JSON. n8n parses the payload, updates a local PostgreSQL database, and registers a new deal within the self-hosted CRM (e.g., Odoo or ERPNext).
5. <strong>Internal Team Notification:</strong> Finally, n8n broadcasts a summarized message to an internal Slack channel, enabling the account team to immediately evaluate incoming leads.</p>
<h2>Key Benefits & Strategic Takeaways</h2>
<p>Transitioning AI inference from commercial cloud services to self-hosted models allows organizations to process high volumes of incoming documents without recurring per-token API charges. Most crucially, keeping processing localized inside the corporate VPC ensures complete compliance with strict GDPR and HIPAA requirements.</p>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/n8n-local-llm-automation</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/n8n-local-llm-automation.jpg" length="90967" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/n8n-local-llm-automation.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Self-Hosting DeepSeek-R1 and Qwen 2.5 on Private GPUs with vLLM and PagedAttention]]></title>
      <link>https://anasrhimi.tech/blog/deepseek-r1-vllm-self-hosted-inference</link>
      <description><![CDATA[Complete architecture guide for deploying open-weight reasoning models (DeepSeek-R1, Qwen 2.5) on private cloud GPUs using vLLM for high-throughput, private inference.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/deepseek-r1-vllm-self-hosted-inference.jpg" alt="Self-Hosting DeepSeek-R1 and Qwen 2.5 on Private GPUs with vLLM and PagedAttention"></figure>
<p>The release of <strong>DeepSeek-R1</strong> and <strong>Qwen 2.5 Coder</strong> has completely shifted the economics of AI. Open-weight reasoning models now match or exceed proprietary models on coding and mathematical benchmarks, giving engineering teams the power to run frontier-tier AI entirely behind their corporate firewall.</p>
<p>In this technical walkthrough, I detail how to deploy DeepSeek-R1 Distill and Qwen 2.5 models on private GPU nodes (NVIDIA A10G / L40S) using <strong>vLLM</strong> to maximize token generation throughput via PagedAttention.</p>
<h3>Deploying vLLM with Docker and OpenAI-Compatible API</h3>
<pre><code class="language-bash">docker run --gpus all   --shm-size 16g   -p 8000:8000   -v ~/.cache/huggingface:/root/.cache/huggingface   vllm/vllm-openai:latest   --model deepseek-ai/DeepSeek-R1-Distill-Qwen-14B   --max-model-len 8192   --gpu-memory-utilization 0.95   --enforce-eager
</code></pre>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Why use vLLM for self-hosting DeepSeek-R1?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">vLLM uses PagedAttention to manage KV cache memory with zero waste, delivering up to 10x higher token generation throughput than naive PyTorch deployments.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Can you run DeepSeek-R1 Distill on a single GPU?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Yes, quantized 14B and 32B DeepSeek-R1 models run efficiently on a single NVIDIA A10G (24GB) or L40S GPU.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/deepseek-r1-vllm-self-hosted-inference</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/deepseek-r1-vllm-self-hosted-inference.jpg" length="90193" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/deepseek-r1-vllm-self-hosted-inference.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Bulletproof PostgreSQL Disaster Recovery: Point-in-Time Recovery (PITR) with WAL-G and S3]]></title>
      <link>https://anasrhimi.tech/blog/postgres-pitr-disaster-recovery-walg</link>
      <description><![CDATA[How to set up automated continuous WAL streaming and Point-in-Time Recovery (PITR) for PostgreSQL with WAL-G, recovering from accidental drops down to the exact second.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/postgres-pitr-disaster-recovery-walg.jpg" alt="Bulletproof PostgreSQL Disaster Recovery: Point-in-Time Recovery (PITR) with WAL-G and S3"></figure>
<p>Relying solely on nightly <code>pg_dump</code> scripts for database backups is a recipe for disaster. If your production database corrupts at 4:30 PM, you lose an entire day's worth of financial transactions and customer records. Furthermore, <code>pg_dump</code> takes exclusive locks on large databases and cannot restore state to a specific second before a rogue <code>DROP TABLE</code> query was executed.</p>
<p><strong>Point-in-Time Recovery (PITR)</strong> with <strong>WAL-G</strong> streams Write-Ahead Logs continuously to encrypted S3 storage, enabling instant restoration to any millisecond in history.</p>
<h3>Configuring PostgreSQL for Continuous WAL Archiving</h3>
<pre><code class="language-ini"># postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'wal-g wal-push %p'
archive_timeout = 60
</code></pre>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">What is Point-in-Time Recovery (PITR) in PostgreSQL?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">PITR allows you to restore a database to the exact second before a failure or accidental data deletion by replaying continuous Write-Ahead Logs (WAL) over a base backup.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Why use WAL-G instead of WAL-E?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">WAL-G is written in Go and performs parallel multi-threaded compression and S3 uploads, making backup and restore operations up to 10x faster.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/postgres-pitr-disaster-recovery-walg</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/postgres-pitr-disaster-recovery-walg.jpg" length="95090" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/postgres-pitr-disaster-recovery-walg.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Zero-Downtime PostgreSQL Migrations: Adding Indexes & Constraints on 100M+ Row Tables Safely]]></title>
      <link>https://anasrhimi.tech/blog/zero-downtime-postgres-migrations-large-scale</link>
      <description><![CDATA[Avoid blocking production traffic: battle-tested SQL strategies for adding indexes, foreign keys, and NOT NULL constraints on multi-gigabyte PostgreSQL tables.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/zero-downtime-postgres-migrations-large-scale.jpg" alt="Zero-Downtime PostgreSQL Migrations: Adding Indexes & Constraints on 100M+ Row Tables Safely"></figure>
<p>Running a standard <code>ALTER TABLE orders ADD COLUMN status VARCHAR NOT NULL DEFAULT 'pending';</code> on a table with 50 million rows takes an <code>ACCESS EXCLUSIVE</code> lock. This blocks all incoming read and write transactions, creating a cascading queue of blocked HTTP requests that will crash your web application in seconds.</p>
<p>Here are the non-blocking SQL patterns required to perform zero-downtime database migrations at scale.</p>
<h3>Safe Non-Blocking Index Creation</h3>
<pre><code class="language-sql">-- BAD: Locks table for writes during the entire index build
CREATE INDEX idx_orders_user_id ON orders(user_id);

-- GOOD: Builds index in background without locking table writes
SET lock_timeout = '2s';
CREATE INDEX CONCURRENTLY idx_orders_user_id ON orders(user_id);
</code></pre>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Why does standard CREATE INDEX lock PostgreSQL tables?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">A standard CREATE INDEX acquires a SHARE lock that blocks all incoming write queries. Using CREATE INDEX CONCURRENTLY avoids write locks entirely.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">How do you add a NOT NULL column to a 50M row table safely?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Add the column with a default value without NOT NULL, backfill in batches, add a CHECK constraint with NOT VALID, validate it concurrently, and then enforce NOT NULL.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/zero-downtime-postgres-migrations-large-scale</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/zero-downtime-postgres-migrations-large-scale.jpg" length="96308" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/zero-downtime-postgres-migrations-large-scale.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Kubernetes Ingress in 2026: Traefik vs Envoy Gateway vs Caddy for Automated Wildcard SSL]]></title>
      <link>https://anasrhimi.tech/blog/traefik-vs-caddy-kubernetes-ingress</link>
      <description><![CDATA[Comprehensive benchmark and architecture guide comparing Traefik, Envoy Gateway, and Caddy for high-concurrency Kubernetes ingress and automated Let's Encrypt TLS certificates.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/traefik-vs-caddy-kubernetes-ingress.jpg" alt="Kubernetes Ingress in 2026: Traefik vs Envoy Gateway vs Caddy for Automated Wildcard SSL"></figure>
<p>The Kubernetes Ingress landscape is evolving rapidly. For years, <code>ingress-nginx</code> was the default choice for almost every cluster. But with the emergence of the official Kubernetes <strong>Gateway API</strong>, and modern proxies like <strong>Traefik v3</strong>, <strong>Envoy Gateway</strong>, and <strong>Caddy</strong>, developers have far better alternatives for automated SSL, HTTP/3, and dynamic routing.</p>
<h2>Feature Matrix: Ingress Controllers Compared</h2>
<ul>
  <li><strong>Traefik v3:</strong> Native Kubernetes CRDs (IngressRoute), built-in Let's Encrypt ACME with DNS-01 challenge support, excellent middleware ecosystem for rate limiting and basic auth.</li>
  <li><strong>Envoy Gateway:</strong> The enterprise standard for the Kubernetes Gateway API. Ultra-high performance, advanced traffic splitting, circuit breaking, and canary deployments.</li>
  <li><strong>Caddy Ingress:</strong> The simplest zero-config HTTPS proxy on the planet. Built-in automatic TLS renewal with memory-efficient Go concurrency.</li>
</ul>
<h3>Traefik IngressRoute with Wildcard DNS-01 ACME</h3>
<pre><code class="language-yaml">apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: api-gateway-ingress
  namespace: production
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`api.anasrhimi.tech`) && PathPrefix(`/v1`)
      kind: Rule
      services:
        - name: backend-api-service
          port: 8080
      middlewares:
        - name: api-rate-limit
        - name: security-headers
  tls:
    secretName: wildcard-anasrhimi-tls
</code></pre>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">What is the difference between Traefik and Caddy in Kubernetes?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Traefik offers native Kubernetes CRDs (IngressRoute) and granular middleware routing, whereas Caddy excels at zero-config automatic TLS certificate issuance.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Does Traefik v3 support the Kubernetes Gateway API?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Yes, Traefik v3 natively supports the Kubernetes Gateway API standard alongside traditional Ingress and IngressRoute CRDs.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/traefik-vs-caddy-kubernetes-ingress</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/traefik-vs-caddy-kubernetes-ingress.jpg" length="94704" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/traefik-vs-caddy-kubernetes-ingress.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Self-Hosting Supabase on Bare-Metal: Zero-Lockin Auth, Realtime Postgres, and Storage Architecture]]></title>
      <link>https://anasrhimi.tech/blog/self-hosting-supabase-vs-firebase</link>
      <description><![CDATA[How to deploy and harden self-hosted Supabase using Docker Compose and Proxmox to escape Firebase lock-in and retain 100% data sovereignty.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/self-hosting-supabase-vs-firebase.jpg" alt="Self-Hosting Supabase on Bare-Metal: Zero-Lockin Auth, Realtime Postgres, and Storage Architecture"></figure>
<p>Firebase makes starting a project easy, but as your application scales, the vendor lock-in becomes debilitating: proprietary NoSQL query limits, soaring read/write bills, and zero control over your user data. <strong>Supabase</strong> offers an open-source, relational alternative built on top of the world's most powerful database: <strong>PostgreSQL</strong>.</p>
<p>In this guide, I break down how we architect and harden a self-hosted Supabase cluster on a private Linux server, providing instant REST APIs, WebSockets, JWT Authentication, and S3-compatible file storage with zero monthly SaaS subscriptions.</p>
<h3>Production Supabase Docker Compose Stack</h3>
<pre><code class="language-yaml"># docker-compose.yml (Core components)
services:
  db:
    image: supabase/postgres:15.1.1.78
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./volumes/db/data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s

  kong:
    image: kong:2.8.1
    restart: unless-stopped
    environment:
      KONG_DATABASE: "off"
      KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
    ports:
      - "8000:8000"
      - "8443:8443"

  auth:
    image: supabase/gotrue:v2.158.1
    restart: unless-stopped
    environment:
      GOTRUE_JWT_SECRET: ${JWT_SECRET}
      GOTRUE_DB_DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres
</code></pre>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Can you self-host Supabase with all features?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Yes, self-hosted Supabase includes PostgreSQL, PostgREST for instant APIs, GoTrue for JWT Auth, Realtime WebSockets, and S3-compatible storage via Docker Compose.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Why migrate from Firebase to Supabase?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Supabase eliminates Firebase's proprietary NoSQL lock-in, provides full SQL relational queries, lower latency, and 100% data sovereignty under GDPR.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/self-hosting-supabase-vs-firebase</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/self-hosting-supabase-vs-firebase.jpg" length="97636" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/self-hosting-supabase-vs-firebase.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Building a Zero-Trust Kubernetes Cluster from Scratch]]></title>
      <link>https://anasrhimi.tech/blog/zero-trust-kubernetes-architecture</link>
      <description><![CDATA[A comprehensive guide on implementing a default-deny network policy with Cilium, mTLS with Istio, and dynamic secrets via HashiCorp Vault in Kubernetes.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/zero-trust-kubernetes-architecture.jpg" alt="Building a Zero-Trust Kubernetes Cluster from Scratch"></figure>
<p>Many engineering teams secure cluster perimeters with web application firewalls (WAFs) or ingress controllers while leaving internal pod-to-pod network paths entirely unencrypted and open. If a single workload is compromised, an attacker can freely probe and access resources across every cluster namespace.</p>
<p>A Zero-Trust security model reverses this architecture: no internal workload is trusted implicitly, and every service must authenticate and authorize its identity before transmitting network traffic. Below is the blueprint for implementing a Zero-Trust Kubernetes architecture using Cilium, Istio, and HashiCorp Vault.</p>
<h2>1. Network Policy Enforcement (CNI Level)</h2>
<p>By default, Kubernetes networking allows unrestricted communication between all pods. Securing the cluster requires implementing a strict default-deny network stance.</p>
<p>Using Cilium as the Container Network Interface (CNI) rather than standard iptables-based proxies leverages eBPF to filter packet streams directly within the Linux kernel. This approach yields optimal throughput and protocol-level visibility. Applying a global <code>CiliumClusterwideNetworkPolicy</code> blocks all inter-namespace traffic, requiring developers to declare explicit egress and ingress rules for allowed service interactions.</p>
<h2>2. Mutual TLS Encryption with Istio</h2>
<p>While CNI network policies enforce IP and port isolation, they do not encrypt data payloads or prevent IP spoofing. Istio addresses transport-level security.</p>
<p>Configuring Istio to enforce <code>STRICT</code> mutual TLS (mTLS) across all cluster namespaces delegates certificate issuance and rotation to Envoy sidecar proxies. When a client microservice issues an HTTP request to a downstream dependency, Istio encrypts traffic in transit and validates SPIFFE identities on both endpoints. Requests originating from unauthenticated workloads are rejected during the initial TLS handshake.</p>
<h2>3. SSO and Identity-Aware Proxying</h2>
<p>Exposing internal administrative interfaces (such as Grafana, ArgoCD, or Kibana) via NodePorts or static HTTP basic authentication introduces significant credential exposure risks.</p>
<p>Routing internal administration traffic through an Identity-Aware Proxy (IAP) such as Pomerium or <code>oauth2-proxy</code> integrated with an OIDC provider (Okta, Entra ID, or Google Workspace) enforces centralized SSO authentication and multi-factor authentication (MFA) before HTTP requests are forwarded to internal cluster services.</p>
<h2>4. Ephemeral Secret Management via Vault</h2>
<p>Storing database credentials inside static Kubernetes Secrets leaves sensitive data vulnerable to users with namespace read permissions or access to etcd backups.</p>
<p>Integrating HashiCorp Vault with the cluster allows pods to authenticate using native Kubernetes Service Account JWT tokens. Vault verifies the token identity, generates short-lived database credentials (with configurable Time-To-Live, e.g., 1 hour), and automates credential rotation. In the event of a secret leak, the window of vulnerability remains severely restricted.</p>
<h2>Architecture Summary</h2>
<p>A resilient Zero-Trust Kubernetes architecture operates under a core defense strategy: assume any individual pod could be compromised. Combining eBPF kernel packet filtering, strict service mesh mTLS, SSO-backed identity proxying, and ephemeral Vault secrets isolates security incidents to their immediate point of origin and prevents lateral movement across infrastructure.</p>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/zero-trust-kubernetes-architecture</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/zero-trust-kubernetes-architecture.jpg" length="103632" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/zero-trust-kubernetes-architecture.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Hetzner vs AWS: Building a $100/mo Bare-Metal Kubernetes Cluster with Talos Linux & Cilium]]></title>
      <link>https://anasrhimi.tech/blog/hetzner-bare-metal-kubernetes-talos</link>
      <description><![CDATA[How I replaced an overpriced $1,400/mo AWS EKS setup with a $100/mo bare-metal Talos Linux cluster on Hetzner, achieving sub-millisecond pod networking with Cilium eBPF.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/hetzner-bare-metal-kubernetes-talos.jpg" alt="Hetzner vs AWS: Building a $100/mo Bare-Metal Kubernetes Cluster with Talos Linux & Cilium"></figure>
<p>If you have ever looked at your monthly AWS bill and wondered why a simple 3-node Kubernetes cluster costs $1,400/month before you even run your first workload, you are not alone. Between EKS control plane fees ($73/mo), NAT Gateways ($32/mo each plus $0.045/GB), EBS gp3 volumes with IOPS surcharges, and inter-AZ data transfer fees, AWS has turned basic compute into a financial minefield.</p>
<p>Last month, I completed a production migration for a high-throughput SaaS client, moving their entire containerized stack from AWS EKS to a bare-metal dedicated server setup on Hetzner running <strong>Talos Linux</strong> and <strong>Cilium eBPF</strong>. The result? Total monthly infrastructure cost dropped from <strong>$1,420 to $108</strong>, while request latency was cut in half.</p>
<h2>Why Talos Linux is the Ultimate Kubernetes OS</h2>
<p>Traditional Linux distributions (Ubuntu, Debian, Alpine) were never designed specifically to host Kubernetes. They come with systemd, SSH daemons, package managers (apt/apk), cron jobs, and mutable file systems—all of which create security vulnerabilities and configuration drift across worker nodes.</p>
<p><strong>Talos Linux</strong> is an immutable, minimal, security-hardened Linux distribution built solely for Kubernetes. It has no shell, no SSH, no console, and no package manager. The entire OS is managed declaratively via a secure gRPC API using <code>talosctl</code>.</p>
<h3>Talos Machine Configuration Example</h3>
<p>Here is the declarative YAML configuration used to bootstrap our Hetzner control plane nodes with Cilium CNI pre-installed:</p>
<pre><code class="language-yaml"># controlplane.yaml
version: v1alpha1
machine:
  type: controlplane
  install:
    disk: /dev/nvme0n1
    image: factory.talos.dev/installer/v1.7.5:latest
    bootloader: true
  network:
    interfaces:
      - interface: eth0
        dhcp: true
  features:
    rbac: true
    kubernetesTalosAPIAccess:
      enabled: true
      allowedRoles:
        - os:admin
cluster:
  network:
    cni:
      name: none # Disable Flannel to deploy Cilium eBPF
  proxy:
    disabled: true # Replace kube-proxy with Cilium eBPF host routing
</code></pre>
<h2>Deploying Cilium eBPF for Line-Rate Networking</h2>
<p>By default, Kubernetes uses <code>kube-proxy</code> with iptables, which inspects packet rules sequentially. When you scale to hundreds of microservices, iptables evaluation creates significant CPU overhead. Cilium uses <strong>eBPF</strong> in the Linux kernel to route network traffic directly using BPF maps in <code>O(1)</code> time.</p>
<pre><code class="language-bash"># Installing Cilium on Talos with Helm
helm install cilium cilium/cilium   --version 1.15.5   --namespace kube-system   --set kubeProxyReplacement=true   --set k8sServiceHost=10.0.0.1   --set k8sServicePort=6443   --set hubble.enabled=true   --set hubble.relay.enabled=true   --set hubble.ui.enabled=true
</code></pre>
<h2>The Financial Tear-Down: AWS EKS vs. Hetzner Dedicated</h2>
<ul>
  <li><strong>AWS EKS (3x m5.xlarge + EKS Control Plane + NAT + EBS):</strong> $1,420 / month</li>
  <li><strong>Hetzner AX42 (AMD Ryzen 7 8700G, 64GB DDR5, 2x 1TB NVMe Gen4):</strong> €46 / month ($50/mo) x 2 nodes = $100 / month</li>
  <li><strong>Net Annual Savings:</strong> <strong>$15,840 per year (92% reduction)</strong></li>
</ul>
<p>If your startup is burning precious venture capital on managed cloud taxes, bare-metal Kubernetes with modern tooling like Talos is no longer an operational nightmare—it is a massive competitive advantage.</p>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">Why use Talos Linux over Ubuntu or Debian for Kubernetes?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Talos Linux is an immutable, minimal OS with no SSH or shell, reducing the attack surface to zero and eliminating configuration drift across worker nodes.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">How does Hetzner dedicated compare to AWS EKS in price?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">A 3-node bare-metal cluster on Hetzner costs approximately $100/month compared to $1,400+/month on AWS EKS for equivalent compute, memory, and NVMe IOPS.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/hetzner-bare-metal-kubernetes-talos</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/hetzner-bare-metal-kubernetes-talos.jpg" length="93635" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/hetzner-bare-metal-kubernetes-talos.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[DevSecOps Policy as Code: Blocking Vulnerable Images & Root Containers with Trivy and Kyverno]]></title>
      <link>https://anasrhimi.tech/blog/trivy-kyverno-admission-controller</link>
      <description><![CDATA[Automating container security in Kubernetes: implementing Kyverno admission controller policies and Trivy vulnerability scans to enforce zero-trust security.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/trivy-kyverno-admission-controller.jpg" alt="DevSecOps Policy as Code: Blocking Vulnerable Images & Root Containers with Trivy and Kyverno"></figure>
<p>Traditional security reviews happen at the end of the development cycle, delaying software releases and frustrating engineering teams. <strong>Policy as Code</strong> shifts security enforcement directly into the Kubernetes API admission controller phase, automatically rejecting any pod that attempts to run as root or contains critical unpatched CVEs.</p>
<h3>Kyverno ClusterPolicy: Forbidding Root Execution</h3>
<pre><code class="language-yaml">apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-root-user
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: check-runAsNonRoot
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Running containers as root is strictly forbidden in production namespaces."
        pattern:
          spec:
            securityContext:
              runAsNonRoot: true
            containers:
              - securityContext:
                  runAsNonRoot: true
                  allowPrivilegeEscalation: false
</code></pre>

<div class="faq-section" style="margin-top: 3.5rem;">
  <h2 style="font-size: 1.75rem; color: var(--gray-0); margin-bottom: 1.5rem; font-weight: 700;">Frequently Asked Questions</h2>
  
<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">What is Policy as Code in Kubernetes?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Policy as Code uses admission controllers like Kyverno to validate and mutate Kubernetes manifests in real time, automatically rejecting non-compliant or insecure pods.</p>
</div>

<div style="margin-bottom: 1.5rem; background: var(--gray-900); padding: 1.25rem 1.5rem; border-radius: 12px; border: 1px solid var(--gray-800);">
  <h3 style="font-size: 1.15rem; color: var(--gray-0); margin-top: 0; margin-bottom: 0.5rem; font-weight: 600;">How does Trivy integrate with Kubernetes DevSecOps?</h3>
  <p style="color: var(--gray-300); margin-bottom: 0; font-size: 0.95rem; line-height: 1.6;">Trivy scans container images for CVEs, exposed secrets, and misconfigurations in CI/CD before images are ever pushed to the registry.</p>
</div>

</div>]]></content:encoded>
      <pubDate>Mon, 10 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/trivy-kyverno-admission-controller</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/trivy-kyverno-admission-controller.jpg" length="96310" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/trivy-kyverno-admission-controller.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Content Optimization for Search Intent in the AI Era]]></title>
      <link>https://anasrhimi.tech/blog/content-optimization-search-intent</link>
      <description><![CDATA[Learn how to optimize content for user search intent in the AI era. Discover techniques to structure documents for LLMs and build topical authority that survives algorithm updates.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/content-optimization-search-intent.jpg" alt="Content Optimization for Search Intent in the AI Era"></figure>
<p>Keyword research was once primarily about targeting high-volume search terms. Today, modern search engines prioritize user intent over simple keyword density. If a webpage fails to resolve a user's specific problem, traditional technical SEO enhancements will not improve its search rankings.</p>
<h2>Understanding the Four Types of Search Intent</h2>
<p>The majority of search queries fall into four primary categories. Satisfying intent requires tailoring your content's structure, format, and depth to match these expectations:</p>
<ul>
<li><strong>Informational</strong> (e.g., <em>"How to deploy Next.js"</em>): Searchers are seeking step-by-step instructions or technical background. Use structured breakdowns, practical code snippets, and clear diagrams.</li>
<li><strong>Navigational</strong> (e.g., <em>"GitHub login"</em>): Searchers are attempting to reach a specific destination page or dashboard. Focus on brand-related queries, intuitive site hierarchy, and direct internal links.</li>
<li><strong>Commercial</strong> (e.g., <em>"Vercel vs Netlify"</em>): Searchers are comparing solutions prior to making a purchasing decision. Offer detailed comparison matrix tables, feature analyses, and realistic pros/cons.</li>
<li><strong>Transactional</strong> (e.g., <em>"Buy AWS credits"</em>): Searchers are prepared to take immediate action or complete a transaction. Design streamlined landing pages emphasizing clear pricing and minimal conversion friction.</li>
</ul>
<h2>Structuring Content for LLMs and Search Parsers</h2>
<p>Modern search engines and AI generative tools (such as Google's AI Overviews) rely on efficient information extraction from structured text. Formatting content logically allows both automated parsers and human readers to quickly digest key points.</p>
<h3>The Inverted Pyramid Approach</h3>
<p>Present the core answer immediately before diving into deeper context:</p>
<p>1. <strong>BLUF (Bottom Line Up Front):</strong> Deliver a concise, direct answer to the query within the opening paragraph.
2. <strong>Technical Details:</strong> Supply supporting architectural context, implementation guidance, and step-by-step instructions.
3. <strong>Edge Cases:</strong> Outline potential troubleshooting scenarios, operational caveats, or performance constraints.
4. <strong>References:</strong> Provide links to official documentation, relevant source repositories, or supplemental reading.</p>
<p>Aligning content structure with target user intent and clear visual hierarchy ensures search crawlers and human readers obtain fast answers, keeping your content durable through search algorithm updates.</p>]]></content:encoded>
      <pubDate>Sun, 09 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/content-optimization-search-intent</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/content-optimization-search-intent.jpg" length="80944" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/content-optimization-search-intent.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Comprehensive Guide to Image, Local, and International SEO]]></title>
      <link>https://anasrhimi.tech/blog/visual-local-international-seo</link>
      <description><![CDATA[Master multi-faceted SEO strategies including next-gen image optimization, hreflang architecture for international markets, and localized schema for global dominance.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/visual-local-international-seo.jpg" alt="Comprehensive Guide to Image, Local, and International SEO"></figure>
<p>Search engine optimization extends beyond optimizing textual content for a single language or geographic region. If your digital platform targets international audiences or relies heavily on visual assets, you require specialized strategies covering image optimization, localized discovery, and multi-regional technical architecture.</p>
<h2>Image SEO: Advanced Visual Optimization</h2>
<p>Search crawlers, Google Lens, and visual search platforms depend on precise image metadata and optimized delivery performance. Comprehensive image optimization requires specific technical practices:</p>
<ul>
<li><strong>Next-Gen Image Formats</strong>: Convert legacy JPEG or PNG files into WebP or AVIF formats. These formats deliver smaller asset payload sizes while preserving visual quality, directly improving page load times and Core Web Vitals (specifically Largest Contentful Paint).</li>
<li><strong>Descriptive Filenames</strong>: Avoid uploading generic camera exports such as <code>IMG_4921.jpg</code>. Implement descriptive, hyphenated file naming structures such as <code>serverless-architecture-diagram.webp</code>.</li>
<li><strong>Responsive Delivery</strong>: Implement the <code>srcset</code> attribute on HTML <code><img></code> elements or utilize <code><picture></code> blocks to serve optimized image resolutions matched to the user's viewport.</li>
</ul>
<h2>International SEO: Implementing Hreflang Tags</h2>
<p>When managing multi-regional or multilingual websites (such as distinct variants for US English, UK English, and French), search engines require explicit signals to avoid duplicate content penalties and deliver the correct localized page. Implement bidirectional <code>hreflang</code> metadata within your document <code><head></code>.</p>
<h3>Example Hreflang Configuration</h3>
<p>Include these tags inside the <code><head></code> section of each localized page variant:</p>
<pre><code class="language-html">&lt;link rel="alternate" hreflang="en-us" href="https://example.com/us/services" /&gt;
&lt;link rel="alternate" hreflang="en-gb" href="https://example.com/uk/services" /&gt;
&lt;link rel="alternate" hreflang="fr-fr" href="https://example.com/fr/services" /&gt;
&lt;link rel="alternate" hreflang="x-default" href="https://example.com/global/services" /&gt;</code></pre>
<p>Ensure every target URL includes a reciprocal link back to the originating page to maintain valid bidirectional verification, and configure <code>x-default</code> as the fallback destination for unsupported locales.</p>
<h2>Local SEO Essentials</h2>
<p>For businesses associated with physical locations or regional service areas, maintaining consistent NAP (Name, Address, Phone number) information across digital directories forms the foundation of localized visibility.</p>
<p>Combine a verified Google Business Profile with structured <code>LocalBusiness</code> Schema markup directly on dedicated regional landing pages. This provides search crawlers with unambiguous data points to index your business within local map packs and regional search results.</p>]]></content:encoded>
      <pubDate>Sun, 09 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/visual-local-international-seo</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/visual-local-international-seo.jpg" length="92608" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/visual-local-international-seo.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Advanced Internal Linking Strategies to Boost Crawlability]]></title>
      <link>https://anasrhimi.tech/blog/advanced-internal-linking-strategies</link>
      <description><![CDATA[Explore advanced internal linking strategies to optimize crawl budget, flatten site architecture, and effectively distribute PageRank across enterprise websites.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/advanced-internal-linking-strategies.jpg" alt="Advanced Internal Linking Strategies to Boost Crawlability"></figure>
<p>Internal linking is one of the most effective SEO levers you directly control. It dictates how PageRank flows across your website, defines how pages relate to one another, and helps search engine bots crawl your content efficiently. Bad architecture wastes crawl budget; a clean layout speeds up indexing.</p>
<h2>Optimizing Crawl Budget</h2>
<p>Crawl budget is the total number of pages search engines will crawl on your site within a given timeframe. For enterprise sites or large e-commerce platforms, spending this budget wisely is critical. Every internal link provides Googlebot with a path to follow.</p>
<ul>
<li><strong>Eliminate Dead Ends</strong>: Make sure no internal links point to 404 errors or redirect chains (<code>301 > 301</code>). Broken links waste crawl budget immediately.</li>
<li><strong>Flatten Your Site Architecture</strong>: Keep key pages within 3 to 4 clicks of your homepage. A flat architecture distributes link equity across your site.</li>
<li><strong>Use Nofollow Strategically</strong>: Add <code>rel="nofollow"</code> to links pointing to non-essential pages, like login screens or faceted filters. This directs link equity toward your high-value pages.</li>
</ul>
<h2>Contextual Linking and Anchor Text Strategy</h2>
<p>Anchor text provides search engines with clear context about a target page. Avoid generic text such as "click here." Instead, write descriptive, natural anchors directly inside your body text using relevant keywords.</p>
<h3>Accessible and SEO-Friendly Links</h3>
<p>Use standard HTML anchor tags instead of JavaScript <code>onClick</code> events so search bots can easily discover your pages.</p>
<pre><code class="language-html">&lt;!-- BAD: Bots cannot crawl this efficiently --&gt;
&lt;span onClick={() =&gt; navigate('/services/cloud-migration')}&gt;Cloud Migration Services&lt;/span&gt;
<p>&lt;!-- GOOD: Standard href with descriptive anchor text --&gt;
&lt;a href="/hire/cloud-infrastructure/" title="Learn more about our AWS and Azure cloud migration services"&gt;
  Enterprise Cloud Migration Services
&lt;/a&gt;</code></pre></p>
<h2>Topic Clusters</h2>
<p>Build topic clusters by linking pillar pages to supporting sub-pages, and linking those sub-pages back to the pillar. This structure clarifies your topic coverage for search engines and helps your pages rank better for related terms.</p>]]></content:encoded>
      <pubDate>Sun, 09 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/advanced-internal-linking-strategies</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/advanced-internal-linking-strategies.jpg" length="109175" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/advanced-internal-linking-strategies.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Programmatic SEO (PSEO) and Topic Clusters for Massive Growth]]></title>
      <link>https://anasrhimi.tech/blog/programmatic-seo-topic-clusters</link>
      <description><![CDATA[Learn how to leverage Programmatic SEO (PSEO) and Topic Clusters to scale your content strategy, capture long-tail search traffic, and achieve massive organic growth.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/programmatic-seo-topic-clusters.jpg" alt="Programmatic SEO (PSEO) and Topic Clusters for Massive Growth"></figure>
<p>Publishing individual articles manually is an effective strategy for thought leadership, but it fails to scale when attempting to capture search traffic across hundreds of specific long-tail user queries. Combining Programmatic SEO (pSEO) with structured topic clusters offers a scalable alternative.</p>
<p>Rather than authoring standalone blog posts for every query variation, pSEO enables the automated generation of targeted landing pages from structured data sources, while topic cluster architectures interlink those assets into cohesive, authoritative site networks.</p>
<h2>How Programmatic SEO (pSEO) Works in Practice</h2>
<p>Programmatic SEO leverages structured datasets and pre-defined page templates to generate targeted landing pages dynamically. This approach excels at capturing long-tail search intent that follows predictable query patterns such as <em>"Best API gateway for AWS serverless"</em> or <em>"PostgreSQL vs MySQL performance for analytics"</em>.</p>
<p>Building a production-ready pSEO architecture requires three core components:</p>
<ul>
<li><strong>Clean, Structured Data</strong>: High-quality data serves as the foundation. Incomplete, duplicate, or unformatted data directly degrades generated page quality. Normalizing records before rendering is essential.</li>
<li><strong>Flexible Page Templates</strong>: Component templates must render distinct, high-value content for each record. Incorporating conditional sections, custom charts, and dynamic comparison tables prevents thin content penalties.</li>
<li><strong>Smart URL & Sitemap Routing</strong>: Implement clean, semantic URL paths (e.g., <code>/integrations/aws-lambda</code>) and configure dynamic sitemap generation to ensure seamless discovery and indexing by search engine crawlers.</li>
</ul>
<h2>Building Topic Clusters to Transfer Link Authority</h2>
<p>Generating hundreds of dynamic pSEO pages represents only part of the solution. Without structured internal linking, search engines treat generated URLs as orphan pages, which dilutes overall domain authority.</p>
<p>Topic clusters address this by structuring related content around a central "pillar page." The pillar page provides a comprehensive overview of a broad topic, while satellite cluster pages explore granular subtopics in detail.</p>
<p>Here is how you might represent a topic cluster structure in code:</p>
<pre><code class="language-javascript">// Simple topic cluster internal link mapping
const topicCluster = {
 pillarPage: "/infrastructure-as-code",
 clusterPages: [
 { url: "/infrastructure-as-code/terraform-vs-pulumi", parent: "/infrastructure-as-code" },
 { url: "/infrastructure-as-code/aws-cdk-tutorial", parent: "/infrastructure-as-code" },
 { url: "/infrastructure-as-code/ci-cd-integration", parent: "/infrastructure-as-code" }
 ]
};</code></pre>
<p>When each subtopic page links back to the central pillar and the pillar links out to its child nodes you create a bi-directional internal linking topology. This structure clarifies topical hierarchy for search crawlers while distributing PageRank equity across the entire cluster.</p>]]></content:encoded>
      <pubDate>Sun, 09 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/programmatic-seo-topic-clusters</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/programmatic-seo-topic-clusters.jpg" length="89455" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/programmatic-seo-topic-clusters.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[AI Visibility Optimization: Ranking in ChatGPT and Perplexity]]></title>
      <link>https://anasrhimi.tech/blog/ai-visibility-optimization-chatgpt-perplexity</link>
      <description><![CDATA[Master AI Visibility Optimization (AIVO) and learn how to structure your content to rank in ChatGPT, Perplexity, and Google's AI Overviews using RAG and entity extraction.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/ai-visibility-optimization-chatgpt-perplexity.jpg" alt="AI Visibility Optimization: Ranking in ChatGPT and Perplexity"></figure>
<p>Search behavior is shifting rapidly. Users increasingly query answer engines like ChatGPT, Perplexity, and Google AI Overviews directly rather than browsing through pages of conventional search results. If your content is not optimized for AI Visibility Optimization (AIVO), AI tools will fail to extract or cite it effectively.</p>
<h2>How AI Search Works Under the Hood</h2>
<p>Traditional search engines index web pages based on keywords, backlinks, and metadata. Large Language Model (LLM) search engines follow a different approach: they prioritize entity recognition, semantic relationships, and verifiable sources.</p>
<p>When a user submits a query to Perplexity, it leverages Retrieval-Augmented Generation (RAG) to pull real-time facts from multiple authoritative websites and synthesizes a direct answer complete with inline citations.</p>
<p>To secure citations from these models, your content must serve as a clear, authoritative reference for specific queries.</p>
<h2>Practical Strategies for AIVO</h2>
<ul>
<li><strong>Lead with direct answers</strong>: Position concise, summary answers directly at the top of your page. Avoid filler introductory text so LLMs can extract direct quotes easily.</li>
<li><strong>Maintain high entity density</strong>: Use precise technical terminology. LLMs construct internal knowledge graphs by analyzing co-occurring concepts. For instance, an article on Kubernetes should naturally incorporate terms like <em>Pods</em>, <em>Nodes</em>, <em>Control Plane</em>, and <em>etcd</em>.</li>
<li><strong>Use clean structured data and HTML tables</strong>: LLMs process tabular content and structured markup far more accurately than dense paragraphs of text. Standard HTML tables, clear lists, and explicit schemas improve extraction performance.</li>
</ul>
<h2>Technical Implementation: JSON-LD Schema</h2>
<p>Including explicit entity declarations via Schema.org markup helps LLM crawlers map your content accurately to recognized technical concepts:</p>
<pre><code class="language-json">{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "headline": "Understanding Kubernetes Architecture",
  "about": {
    "@type": "Thing",
    "name": "Kubernetes",
    "sameAs": "https://en.wikipedia.org/wiki/Kubernetes"
  }
}</code></pre>
<h2>Shifting Focus: From Rankings to Citations</h2>
<p>Succeeding in AI search relies on being cited, not merely indexed. Establishing deep topical authority, ensuring strict factual accuracy, and keeping your content easy for RAG pipelines to parse will determine your site's reach as answer engines become the primary interface for information retrieval.</p>]]></content:encoded>
      <pubDate>Sun, 09 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/ai-visibility-optimization-chatgpt-perplexity</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/ai-visibility-optimization-chatgpt-perplexity.jpg" length="116468" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/ai-visibility-optimization-chatgpt-perplexity.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Semantic SEO and Entity Optimization: Beyond Keywords]]></title>
      <link>https://anasrhimi.tech/blog/semantic-entity-seo-optimization</link>
      <description><![CDATA[Move beyond traditional keyword research. Uncover the power of Semantic SEO and Entity Optimization to align your content with search engine knowledge graphs and user intent.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/semantic-entity-seo-optimization.jpg" alt="Semantic SEO and Entity Optimization: Beyond Keywords"></figure>
<h2>From Text Strings to Real-World Concepts</h2>
<p>Search engines stopped relying purely on exact keyword matching years ago. Thanks to advances in natural language processing and massive knowledge graphs, modern algorithms parse context, relationships, and user intent.</p>
<p>Semantic SEO flips the traditional playbook: instead of optimizing for literal text strings, you structure content around actual entities people, places, concepts, or tools that define your subject area.</p>
<h2>How Entities and Knowledge Graphs Function</h2>
<p>An entity is any distinct, well-defined concept or real-world object such as a specific framework, organization, or technical term.</p>
<p>Search engines construct Knowledge Graphs to connect these entities. When you structure your content around entities, you make it significantly easier for search crawlers to resolve ambiguity and position your page within the broader web of knowledge.</p>
<h2>Practical Steps for Semantic Optimization</h2>
<p>Rather than scattering isolated keywords across an article, focus on topical depth:</p>
<ul>
<li><strong>Natural Industry Terminology</strong>: Avoid forced LSI keyword lists. Instead, naturally incorporate the technical vocabulary and domain terms that an experienced specialist would use.</li>
<li><strong>Structured Data (Schema.org)</strong>: Provide search crawlers with explicit signals using JSON-LD metadata. Define your primary subject using accurate <code>@type</code> schema declarations (such as <code>SoftwareApplication</code>, <code>Person</code>, or <code>TechArticle</code>).</li>
</ul>
<pre><code class="language-json">{
 "@context": "https://schema.org",
 "@type": "Organization",
 "name": "Google",
 "sameAs": [
 "https://en.wikipedia.org/wiki/Google",
 "https://www.wikidata.org/wiki/Q95"
 ]
}</code></pre>
<h2>Establishing Topical Authority</h2>
<p>When you consistently publish deep, thorough content around a core topic and its related subtopics, search algorithms recognize your domain's topical authority. Search engines reliably favor sites that demonstrate genuine, comprehensive expertise in a subject over generic sites offering superficial overviews.</p>]]></content:encoded>
      <pubDate>Sun, 09 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/semantic-entity-seo-optimization</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/semantic-entity-seo-optimization.jpg" length="88769" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/semantic-entity-seo-optimization.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[The Future of Search: GEO, AEO, and LLMO Explained]]></title>
      <link>https://anasrhimi.tech/blog/future-of-ai-search-geo-aeo</link>
      <description><![CDATA[Explore the evolution of AI search with a deep dive into Generative Engine Optimization (GEO), Answer Engine Optimization (AEO), and Large Language Model Optimization (LLMO). Learn actionable strategies to future-proof your digital presence in an AI-first world.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/future-of-ai-search-geo-aeo.jpg" alt="The Future of Search: GEO, AEO, and LLMO Explained"></figure>
<h2>Moving From SERPs to Generative Answers</h2>
<p>Search engines no longer merely return lists of blue hyperlinks. With AI search engines such as Google's Search Generative Experience (SGE) and Perplexity, users expect direct, synthesized answers directly at the top of the page.</p>
<p>To maintain content visibility, standard SEO alone is insufficient. You must also optimize for Generative Engine Optimization (GEO), Answer Engine Optimization (AEO), and Large Language Model Optimization (LLMO).</p>
<h2>Generative Engine Optimization (GEO)</h2>
<p>GEO focuses on securing content citations within AI search engine responses. While standard SEO heavily emphasizes keyword density and backlinks, GEO prioritizes factual density, structured formatting, and source authority. Generative engines construct responses by retrieving and synthesizing details from multiple sources, requiring your content to be easily interpretable by models.</p>
<pre><code class="language-json">// Example: Structuring data for GEO using JSON-LD
{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "headline": "Generative Engine Optimization",
  "author": {
    "@type": "Person",
    "name": "Anas Rhimi"
  },
  "publisher": {
    "@type": "Organization",
    "name": "Anas Rhimi Tech"
  },
  "about": [
    {"@type": "Thing", "name": "Artificial Intelligence"},
    {"@type": "Thing", "name": "Search Engine Optimization"}
  ]
}</code></pre>
<h2>Answer Engine Optimization (AEO)</h2>
<p>AEO targets voice assistants and direct-answer platforms such as ChatGPT or Claude. The objective is concise and direct: provide explicit answers to targeted user inquiries. This is achieved by utilizing Q&A structures, clear subheadings, and precise definitions.</p>
<h2>Large Language Model Optimization (LLMO)</h2>
<p>LLMO centers on ensuring your brand, tools, or core concepts are incorporated into LLM training datasets and retrieval-augmented generation (RAG) pipelines. Achieving this requires establishing a presence across authoritative platforms, contributing to open-source repositories, and publishing original technical research ingested by training pipelines.</p>
<p>Engineers and marketers must collaborate effectively: build performant, semantic HTML, and expose rich structured data that AI agents can seamlessly digest.</p>]]></content:encoded>
      <pubDate>Sun, 09 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/future-of-ai-search-geo-aeo</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/future-of-ai-search-geo-aeo.jpg" length="76256" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/future-of-ai-search-geo-aeo.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[E-E-A-T and Topical Authority: Building Trust with Search Engines]]></title>
      <link>https://anasrhimi.tech/blog/eeat-topical-authority-information-gain</link>
      <description><![CDATA[Deepen your understanding of E-E-A-T, Topical Authority, and Information Gain. Discover expert strategies to build trust with search engines and establish your site as an authoritative industry resource.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/eeat-topical-authority-information-gain.jpg" alt="E-E-A-T and Topical Authority: Building Trust with Search Engines"></figure>
<h2>Trust and Originality on the Modern Web</h2>
Now that AI can rapidly generate massive amounts of low-cost content, search engines evaluate pages based on trust and unique value. Modern SEO revolves around three core concepts: E-E-A-T (Experience, Expertise, Authoritativeness, Trustworthiness), Topical Authority, and Information Gain.</p>
<h2>How E-E-A-T Works</h2>
E-E-A-T is not a direct ranking algorithm factor. It is a evaluation framework used by Google Search Quality Raters to assess search results. To align with it, focus on three key areas:</p>
<ul>
<li><strong>Experience & Expertise</strong>: Ensure real experts author your content. Include clear author biographies, link to verified social profiles, and utilize <code>Person</code> schema to confirm author identity.</li>
<li><strong>Authoritativeness</strong>: Acquire backlinks from respected websites in your industry and establish a recognizable brand presence.</li>
<li><strong>Trustworthiness</strong>: Implement HTTPS, publish clear privacy policies and contact information, and link to authoritative primary data sources.</li>
</ul>
<h2>Information Gain</h2>
Google holds a patent for Information Gain Scores. Algorithms analyze new pages to check whether they provide original facts or insights beyond what is already indexed. Simply rewording existing top-ranking articles is no longer effective.</p>
<pre><code class="language-javascript">// A simplified mental model for Information Gain
function calculateInformationGain(newArticle, existingSerpResults) {
  let uniqueValue = 0;
  if (newArticle.hasOriginalData) uniqueValue += 50;
  if (newArticle.hasExpertQuotes) uniqueValue += 30;
  if (newArticle.hasUniquePerspective) uniqueValue += 20;
<p>return uniqueValue - getOverlapScore(newArticle, existingSerpResults);
}</code></pre></p>
<h2>Building Topical Authority</h2>
Consistently demonstrating E-E-A-T and adding fresh value across a cluster of related topics builds topical authority. Over time, search engines trust your domain more deeply within your niche, resulting in faster indexing and higher rankings for newly published content.</p>]]></content:encoded>
      <pubDate>Sun, 09 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/eeat-topical-authority-information-gain</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/eeat-topical-authority-information-gain.jpg" length="117554" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/eeat-topical-authority-information-gain.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Mastering Technical & On-Page SEO for Next-Gen Web Apps]]></title>
      <link>https://anasrhimi.tech/blog/technical-on-page-seo-core-web-vitals</link>
      <description><![CDATA[A comprehensive guide for developers and SEOs on mastering Technical SEO, On-Page SEO, and Core Web Vitals. Discover advanced performance optimization techniques for next-generation web applications.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/technical-on-page-seo-core-web-vitals.jpg" alt="Mastering Technical & On-Page SEO for Next-Gen Web Apps"></figure>
<p>SEO is no longer confined to keyword placement and basic meta tags. For web applications built with frameworks like React, Next.js, or Vue, front-end performance directly influences how search engines crawl, index, and rank your pages. Google incorporates Core Web Vitals (CWV) as explicit ranking factors, elevating page speed and visual stability into primary engineering priorities.</p>
<h2>Core Web Vitals Breakdown</h2>
<p>Google evaluates page user experience based on three primary metrics:</p>
<ul>
<li><strong>Largest Contentful Paint (LCP):</strong> Measures how quickly the primary content element renders. Target <strong>2.5 seconds or less</strong>. Improve LCP by delivering critical CSS inline, lazy-loading offscreen images, utilizing modern formats like WebP or AVIF, and caching static assets via a CDN.</li>
<li><strong>Interaction to Next Paint (INP):</strong> Assesses page responsiveness during user interactions. Target <strong>under 200ms</strong>. Keep INP low by splitting long tasks, minimizing heavy main-thread computations, and deferring non-essential JavaScript.</li>
<li><strong>Cumulative Layout Shift (CLS):</strong> Quantifies visual stability during rendering. Target a score of <strong>0.1 or lower</strong>. Eliminate layout shifts by specifying explicit <code>width</code> and <code>height</code> attributes on images and embedded elements, and avoiding dynamic content injection above existing content without user input.</li>
</ul>
<h2>On-Page SEO in JavaScript Frameworks</h2>
<p>Client-side rendered Single Page Applications (SPAs) frequently encounter indexing delays when search engine crawlers receive empty root containers. Utilizing Server-Side Rendering (SSR) or Static Site Generation (SSG) ensures search crawlers receive fully rendered HTML on the initial HTTP payload.</p>
<p>Modern frameworks like Next.js offer native primitives to manage metadata dynamically per route.</p>
<pre><code class="language-typescript">// app/posts/[slug]/page.tsx
import type { Metadata } from 'next';
<p>type Props = {
  params: { slug: string };
};</p>
<p>export async function generateMetadata({ params }: Props): Promise&lt;Metadata&gt; {
  const post = await fetchPost(params.slug);</p>
<p>return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
    },
  };
}</code></pre></p>
<h2>Essential Optimization Checklist</h2>
<p>1. <strong>Rely on Field Data (CrUX):</strong> While Lighthouse lab scores provide helpful guidance during development, Google evaluates ranking factors using real-user metrics from the Chrome User Experience Report (CrUX).
2. <strong>Code-Split JavaScript Bundles:</strong> Large JavaScript bundles delay thread execution and degrade INP. Use dynamic imports (<code>next/dynamic</code> or <code>React.lazy</code>) to defer non-critical components.
3. <strong>Configure Robust Caching Headers:</strong> Serve static assets using extended <code>Cache-Control</code> max-age directives to prevent redundant network fetch requests.</p>]]></content:encoded>
      <pubDate>Sun, 09 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/technical-on-page-seo-core-web-vitals</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/technical-on-page-seo-core-web-vitals.jpg" length="90308" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/technical-on-page-seo-core-web-vitals.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[FinOps Architecture Tear-Down: How I Cut a Startup's AWS Bill by 40%]]></title>
      <link>https://anasrhimi.tech/blog/finops-teardown</link>
      <description><![CDATA[A technical tear-down of how poorly designed AWS architectures bleed money, and how a freelance Cloud Architect optimizes them.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/finops-teardown.jpg" alt="FinOps Architecture Tear-Down: How I Cut a Startup's AWS Bill by 40%"></figure>
<p>A few months ago, a Series A B2B SaaS startup engaged me to review their infrastructure. Aside from payroll, their largest expenditure was an AWS bill averaging $14,000 per month. They planned to hire a full-time Senior DevOps engineer at $130,000/year primarily to get spending under control.</p>
<p>Instead, we agreed on a 4-week freelance architectural audit and overhaul. During that month, I re-architected their Kubernetes cluster, eliminated legacy infrastructure, and reduced their monthly AWS bill to under $8,000 a 40% reduction saving over $70,000 annually without increasing permanent headcount.</p>
<p>Here is a detailed breakdown of the cost drivers and how we resolved them.</p>
<h2>1. Orphaned EBS Volumes and Snapshots</h2>
<p>Unused infrastructure frequently represents the easiest opportunity for immediate savings. In this environment, developers regularly launched EC2 instances for testing, terminated the instances when complete, but left attached EBS volumes behind.</p>
<p>I executed a quick Boto3 script to list available (unattached) volumes:</p>
<pre><code class="language-python">import boto3
<p>ec2 = boto3.client('ec2', region_name='us-east-1')
volumes = ec2.describe_volumes(Filters=[{'Name': 'status', 'Values': ['available']}])</p>
<p>total_wasted_gb = 0
for volume in volumes['Volumes']:
 print(f"Orphaned Volume: {volume['VolumeId']}, Size: {volume['Size']}GB")
 total_wasted_gb += volume['Size']</p>
<p>print(f"Total Wasted Storage: {total_wasted_gb}GB")</code></pre></p>
<p>The script identified over 4TB of unattached EBS volumes alongside outdated RDS snapshots. Combined, they were incurring $800/month in unused storage costs. We removed them immediately.</p>
<h2>2. Over-Provisioned Kubernetes Nodes (EKS)</h2>
<p>The application ran on Amazon EKS utilizing static managed node groups based on <code>m5.2xlarge</code> instances.</p>
<p>After deploying <code>kube-state-metrics</code> and Prometheus to measure actual pod resource consumption, the data indicated that even during peak hours, cluster utilization averaged only 15% CPU and 40% RAM. The company was paying for capacity it seldom utilized.</p>
<p><strong>The Solution:</strong> I replaced static node groups with Karpenter, an open-source Kubernetes node autoscaler. Karpenter provisions instances dynamically according to pending pod requests rather than rigid node pools.</p>
<pre><code class="language-yaml">apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
 name: default
spec:
 template:
 spec:
 requirements:
  - key: karpenter.sh/capacity-type
  operator: In
  values: ["spot", "on-demand"]
  - key: kubernetes.io/arch
  operator: In
  values: ["amd64", "arm64"]
 limits:
 cpu: 1000
 disruption:
 consolidationPolicy: WhenUnderutilized
 expireAfter: 720h</code></pre>
<p>By enabling Karpenter to mix ARM64 (Graviton) instances with Spot instances for stateless workloads, compute costs decreased by 55%.</p>
<h2>3. NAT Gateway Data Transfer Fees</h2>
<p>NAT Gateways can silently generate substantial data processing expenses. The startup's workloads in private subnets transmitted high volumes of internal traffic primarily S3 uploads and DynamoDB queries through NAT Gateways, which AWS bills at $0.045 per GB.</p>
<p>I implemented VPC Gateway Endpoints for both S3 and DynamoDB. This routed internal service traffic directly across the AWS network backbone instead of traversing the NAT Gateway.</p>
<p>This 10-minute Terraform configuration update saved $1,200/month.</p>
<h2>Conclusion: Targeted Audit vs. Full-Time Headcount</h2>
<p>Startups frequently assume that managing cloud infrastructure requires a dedicated full-time DevOps hire. In practice, initial cost optimization, CI/CD pipeline configuration, and Kubernetes tuning are often discrete projects. Once automated using Terraform and GitOps, daily maintenance can be effectively managed by existing development teams.</p>
<p>Before creating a new engineering position for infrastructure costs, evaluate whether a targeted architectural audit can resolve bottlenecks first.</p>]]></content:encoded>
      <pubDate>Sun, 09 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/finops-teardown</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/finops-teardown.jpg" length="124548" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/finops-teardown.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[The Ultimate Guide to Schema, Structured Data, and Sitemaps]]></title>
      <link>https://anasrhimi.tech/blog/schema-structured-data-sitemaps-seo</link>
      <description><![CDATA[A comprehensive technical guide to implementing Schema markup, dynamic JSON-LD structured data, and advanced XML sitemap architectures for optimal search engine indexation.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/schema-structured-data-sitemaps-seo.jpg" alt="The Ultimate Guide to Schema, Structured Data, and Sitemaps"></figure>
<p>Search engines rely on explicit markup to parse page context with accuracy. Even as AI search and natural language processing (NLP) improve, JSON-LD schema remains the cleanest way to tell Googlebot exactly what your content means. Here is how to structure your schema, select the appropriate schemas, and configure dynamic sitemaps.</p>
<h2>Why Schema Markup Matters</h2>
<p>Schema.org provides search crawlers with a standardized vocabulary. Adding structured data transforms standard web pages into machine-readable entities, helping you earn Rich Snippets and secure clear placements in Google's Knowledge Graph.</p>
<p>Key schema types worth implementing:</p>
<ul>
<li><strong>Article / TechArticle:</strong> Defines authorship, publication dates, and target topics for engineering articles.</li>
<li><strong>FAQPage:</strong> Helps push key question-and-answer pairs into Google search features.</li>
<li><strong>SoftwareApplication:</strong> Useful for SaaS products to display pricing, ratings, and operating setup support directly within search results.</li>
</ul>
<h3>Injecting Dynamic JSON-LD in React</h3>
<p>If you generate FAQ items dynamically, you can inject the JSON-LD script using a lightweight React component:</p>
<pre><code class="language-tsx">export const FAQSchema = ({ questions }) =&gt; {
  const schema = {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    "mainEntity": questions.map(q =&gt; ({
      "@type": "Question",
      "name": q.question,
      "acceptedAnswer": {
        "@type": "Answer",
        "text": q.answer
      }
    }))
  };
<p>return (
    &lt;script 
      type="application/ld+json" 
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }} 
    /&gt;
  );
};</code></pre></p>
<h2>Structuring XML Sitemaps</h2>
<p>Once your site expands beyond a few hundred URLs, placing everything into a single <code>sitemap.xml</code> file makes debugging more difficult. A better approach is using a Sitemap Index file that links to focused sub-sitemaps (such as <code>sitemap-posts.xml</code> or <code>sitemap-products.xml</code>). This way, when indexation drops, you can instantly identify which content category has issues inside Google Search Console.</p>
<p>A few essential rules for sitemaps:</p>
<ul>
<li>Keep sitemaps dynamic so they update automatically when you publish or update content.</li>
<li>Include only canonical URLs that return a <code>200 OK</code> status code.</li>
<li>Remove <code>404</code> errors, draft URLs, and redirects to avoid wasting crawl budget.</li>
</ul>]]></content:encoded>
      <pubDate>Sun, 09 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/schema-structured-data-sitemaps-seo</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/schema-structured-data-sitemaps-seo.jpg" length="93852" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/schema-structured-data-sitemaps-seo.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Managing Dynamic AWS Secrets with HashiCorp Vault]]></title>
      <link>https://anasrhimi.tech/blog/vault-dynamic-secrets-aws</link>
      <description><![CDATA[Eliminate long-lived AWS credentials by leveraging HashiCorp Vault's dynamic secrets engine. Learn the architecture and implementation for secure, time-bound access.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/vault-dynamic-secrets-aws.jpg" alt="Managing Dynamic AWS Secrets with HashiCorp Vault"></figure>
<h2>Why Static Credentials Fail</h2>
<p>Hardcoded <code>AWS_ACCESS_KEY_ID</code> and <code>AWS_SECRET_ACCESS_KEY</code> key pairs represent a persistent vulnerability in cloud infrastructure. Even when stored securely, long-lived credentials remain exposed to potential leaks or unauthorized exposure.</p>
<p>HashiCorp Vault's AWS secrets engine addresses this risk by generating ephemeral credentials on demand that expire automatically according to a configurable Time-To-Live (TTL).</p>
<h2>Configuration via CLI</h2>
<p>Configure Vault with the necessary permissions to manage IAM resources, then establish a role that issues temporary credentials:</p>
<pre><code class="language-bash"># 1. Enable the AWS secrets engine
vault secrets enable -path=aws aws
<p># 2. Configure root credentials Vault will use with AWS
# Note: On EKS, use IRSA (IAM Roles for Service Accounts) to avoid static keys here.
vault write aws/config/root \
    access_key=$VAULT_AWS_ACCESS_KEY \
    secret_key=$VAULT_AWS_SECRET_KEY \
    region=us-east-1</p>
<p># 3. Create a Vault role mapped to an IAM policy
vault write aws/roles/s3-readonly \
    credential_type=iam_user \
    policy_document=- &lt;&lt;EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": "*"
    }
  ]
}
EOF</code></pre></p>
<h2>Provisioning Ephemeral Credentials</h2>
<p>Applications authenticate to Vault (for example, utilizing Kubernetes service account tokens) and request temporary AWS access keys directly from the role path:</p>
<pre><code class="language-bash">$ vault read aws/creds/s3-readonly
<p>Key                Value
---                -----
lease_id           aws/creds/s3-readonly/f8a...
lease_duration     1h
access_key         AKIA...
secret_key         wJalr...
security_token     </code></pre></p>
<p>Vault dynamically creates an IAM user within AWS, attaches the specified policy, and returns short-lived API keys. Once the lease duration expires (1 hour in this example), Vault automatically removes the temporary IAM user.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/vault-dynamic-secrets-aws</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/vault-dynamic-secrets-aws.jpg" length="89177" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/vault-dynamic-secrets-aws.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Practical AIOps: Integrating AI into DevOps Without the Chaos]]></title>
      <link>https://anasrhimi.tech/blog/practical-aiops-devops-automation</link>
      <description><![CDATA[A complete technical walkthrough of migrating from standalone security to a centralized CrowdSec Hub-and-Spoke architecture across Proxmox LXC containers. by Anas Rhimi C, Python, TypeScript, Swift, and more.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/practical-aiops-devops-automation.jpg" alt="Practical AIOps: Integrating AI into DevOps Without the Chaos"></figure>
<p>AIOps receives significant industry attention, yet granting an LLM direct administrative control over production Kubernetes environments presents severe operational risk. If deployment automation is fragile, introducing unchecked AI mechanisms merely accelerates failures at scale.</p>
<p>How can engineering organizations leverage AI within DevOps workflows effectively while mitigating potential risks?</p>
<h2>Focus on High-Value, Low-Risk Automation</h2>
<p>The key strategy involves positioning AI as an operational assistant rather than an autonomous controller.</p>
<ul>
<li><strong>Log Summarization:</strong> Process high-volume, noisy log streams through targeted LLM prompts to rapidly synthesize failure causes during active service disruptions.</li>
<li><strong>RCA Drafting:</strong> Gather incident telemetry and infrastructure metrics to automatically draft preliminary Root Cause Analysis (RCA) reports for engineering review.</li>
<li><strong>Infrastructure Code Audits:</strong> Integrate automated AI inspection gates into CI pipelines to identify security vulnerabilities or misconfigurations in Terraform and Ansible manifests prior to peer code reviews.</li>
</ul>
<p>Ultimately, AI delivers maximum utility by reducing developer cognitive load not by substituting fundamental platform engineering practices.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/practical-aiops-devops-automation</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/practical-aiops-devops-automation.jpg" length="92495" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/practical-aiops-devops-automation.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Enforcing mTLS across Microservices using Istio Service Mesh]]></title>
      <link>https://anasrhimi.tech/blog/service-mesh-istio-mtls</link>
      <description><![CDATA[In a modern microservices architecture running on Kubernetes, perimeter defense is insufficient.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/service-mesh-istio-mtls.jpg" alt="Enforcing mTLS across Microservices using Istio Service Mesh"></figure>
<h2>Why Zero-Trust Matters in Kubernetes</h2>
<p>Relying solely on perimeter security inside a Kubernetes cluster introduces significant risk. Once an attacker bypasses the ingress layer, they can typically move laterally across the flat network with minimal resistance.</p>
<p>A zero-trust model addresses this vulnerability by requiring every internal request to be authenticated, authorized, and encrypted. Istio simplifies this process: it offloads mutual TLS (mTLS) from your application code into Envoy sidecar proxies deployed alongside your pods.</p>
<p>Istio serves as the Certificate Authority (CA), manages automatic certificate rotation, and configures Envoy sidecars to establish encrypted TLS tunnels for all inter-service communication.</p>
<p>---</p>
<h2>1. Enabling Strict mTLS Cluster-Wide</h2>
<p>By default, Istio operates in <code>PERMISSIVE</code> mode to allow workload migration without disrupting existing plaintext connections. To enforce full encryption, update the configuration to <code>STRICT</code> using a <code>PeerAuthentication</code> policy.</p>
<pre><code class="language-yaml"># strict-mtls.yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default-strict-mtls
  namespace: istio-system # Applying to istio-system makes it cluster-scoped
spec:
  mtls:
    mode: STRICT</code></pre>
<h3>Breakdown:</h3>
<ul>
<li><code>kind: PeerAuthentication</code>: Governs how sidecars handle incoming traffic.</li>
<li><code>namespace: istio-system</code>: Applying this policy within Istio's root namespace enforces mTLS globally across the entire service mesh.</li>
<li><code>mode: STRICT</code>: Instructs sidecars to reject any unencrypted HTTP or raw TCP connection. Workloads will accept only connections encrypted with a valid Istio certificate.</li>
</ul>
<p>---</p>
<h2>2. Restricting Access with AuthorizationPolicies</h2>
<p>While mTLS handles encryption and authentication (verifying <em>who</em> is issuing the request), access control requires explicit authorization rules (verifying <em>what</em> actions they are permitted to execute).</p>
<p>Istio assigns SPIFFE (Secure Production Identity Framework for Everyone) identities based on Kubernetes ServiceAccounts. You can utilize these identities within an <code>AuthorizationPolicy</code> to secure sensitive endpoints.</p>
<pre><code class="language-yaml"># authz-policy-finance.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: require-frontend-identity
  namespace: finance-app
spec:
  selector:
    matchLabels:
      app: payment-processor # Target workload
  action: ALLOW
  rules:
  - from:
    - source:
        # Require a valid cryptographically verified SPIFFE ID
        principals: ["cluster.local/ns/frontend-app/sa/frontend-service-account"]
    to:
    - operation:
        methods: ["POST"]
        paths: ["/api/v1/process-payment"]</code></pre>
<h3>Breakdown:</h3>
<ul>
<li><code>action: ALLOW</code>: When an <code>ALLOW</code> rule is defined, Istio defaults to denying all unspecified requests.</li>
<li><code>selector.matchLabels</code>: Applies this security policy specifically to the <code>payment-processor</code> pod in the <code>finance-app</code> namespace.</li>
<li><code>source.principals</code>: Validates the client's identity using its mTLS certificate. In this configuration, only requests originating from <code>frontend-service-account</code> within the <code>frontend-app</code> namespace are accepted, preventing IP spoofing attempts.</li>
<li><code>operation</code>: Restricts access strictly to <code>POST /api/v1/process-payment</code>, blocking all other HTTP methods and endpoints.</li>
</ul>
<p>---</p>
<p>Combining strict mTLS with SPIFFE-based authorization rules delivers robust service-level security without requiring modifications to your application source code.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/service-mesh-istio-mtls</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/service-mesh-istio-mtls.jpg" length="109337" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/service-mesh-istio-mtls.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Handling Silent Context Drops in n8n AI Agent Pipelines]]></title>
      <link>https://anasrhimi.tech/blog/n8n-silent-context-drops-ai</link>
      <description><![CDATA[Learn how to handle silent context drops in n8n AI agent pipelines by bypassing volatile memory buffers and using custom tool-based context injection.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/n8n-silent-context-drops-ai.jpg" alt="Handling Silent Context Drops in n8n AI Agent Pipelines"></figure>
<p>If you have designed multi-agent LLM workflows in n8n, you have likely encountered silent context loss. The execution log indicates success, yet the model hallucinates missing fields because upstream variables vanished during execution.</p>
<p>This issue typically stems from memory buffer management: n8n's Window Buffer Memory maintains only the most recent N dialogue turns. When substantial JSON payloads (such as complex API payloads) cycle through the agent's prompt context, the buffer hits max token capacity and quietly purges earlier context keys or platform prompt directives. Unable to locate the evicted variables, the model generates fabricated responses rather than failing gracefully.</p>
<p>The fix involves separating vital execution state from volatile conversation memory. Instead of loading large objects into the prompt buffer, access state deterministically using a custom tool node.</p>
<p>Below is an n8n node definition for an explicit context retrieval tool:</p>
<pre><code class="language-json">{
  "nodes": [
    {
      "parameters": {
        "name": "fetchContextPayload",
        "description": "Retrieves the immutable execution context required for this run. Call this before generating the final response.",
        "jsCode": "/<em> Line 1: Retrieve the globally stored context from the workflow's static data </em>/\nconst workflowStaticData = $getWorkflowStaticData('node');\n\n/<em> Line 2: Extract the specific payload needed for the current execution ID </em>/\nconst executionId = $('Execute Workflow Trigger').first().json.executionId;\nconst payload = workflowStaticData[executionId];\n\n/<em> Line 3: Throw an explicit error if context is missing, breaking the silent failure loop </em>/\nif (!payload) throw new Error(<code>CRITICAL: Context dropped for execution ${executionId}</code>);\n\n/<em> Line 4: Return stringified payload to the agent </em>/\nreturn JSON.stringify(payload);"
      },
      "id": "e2c34d56-7890-1234-5678-90abcdef1234",
      "name": "Context Injector Tool",
      "type": "n8n-nodes-base.tool",
      "typeVersion": 1,
      "position": [ 820, 340 ]
    }
  ]
}</code></pre>
<h3>Breakdown of the implementation:</h3>
<ul>
<li><strong>Line 1:</strong> Using <code>$getWorkflowStaticData('node')</code> bypasses regular node data output streams and conversation buffers, ensuring data remains intact across internal ReAct loop iterations.</li>
<li><strong>Line 2:</strong> Keying data strictly to <code>executionId</code> guarantees complete isolation and prevents cross-contamination during parallel workflow executions.</li>
<li><strong>Line 3:</strong> Throwing an explicit JavaScript exception terminates execution if state is missing, replacing silent degradation with immediate failure logging for monitoring systems.</li>
<li><strong>Line 4:</strong> Returning stringified JSON ensures the agent receives clean, predictable structured data.</li>
</ul>
<p>Decoupling critical state storage from prompt memory buffers eliminates silent failure modes and ensures n8n AI workflows operate reliably.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/n8n-silent-context-drops-ai</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/n8n-silent-context-drops-ai.jpg" length="85714" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/n8n-silent-context-drops-ai.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Mitigating AWS Lambda Cold Starts for Latency-Sensitive APIs]]></title>
      <link>https://anasrhimi.tech/blog/aws-lambda-cold-starts</link>
      <description><![CDATA[AWS Lambda provides unparalleled scalability and operational simplicity. However, for synchronous, latency-sensitive APIs, the 'cold start' phenomenon is a critical engineering challenge.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/aws-lambda-cold-starts.jpg" alt="Mitigating AWS Lambda Cold Starts for Latency-Sensitive APIs"></figure>
<h2>The Cold Start Dilemma in Serverless Architectures</h2>
<p>AWS Lambda scales automatically and eliminates server management overhead, but synchronous, low-latency APIs face a significant challenge: cold starts. When a function wakes up after an idle period or when concurrent traffic surges AWS provisions a new execution environment. It downloads your code package, initializes a Firecracker micro-VM, boots the runtime (such as Node.js or Python), and runs initialization code. That startup sequence can add seconds of latency, causing API Gateway timeouts and degrading user experiences.</p>
<p>Resolving cold starts requires two complementary strategies: trimming initialization overhead within application code and deploying AWS infrastructure tools such as Provisioned Concurrency.</p>
<h2>1. Optimizing the Initialization Phase (Init Context)</h2>
<p>Code located outside your primary request handler executes during the Init phase. AWS grants up to 10 seconds for this phase to complete. Keeping it lean requires deferring heavy client initialization and removing unused dependencies.</p>
<pre><code class="language-python"># Python Lambda example demonstrating lazy initialization
import boto3
import json
import os
<p># BAD: Eager initialization. This blocks the Init phase.
# If DynamoDB is slow, the entire cold start is delayed, 
# even if the specific request doesn't need it.
# dynamodb = boto3.resource('dynamodb')</p>
<p># GOOD: Initialize globally, but lazily inside the handler.
_dynamodb = None
_table = None</p>
<p>def get_dynamo_table():
 global _dynamodb, _table
 if _dynamodb is None:
  # Boto3 client initialization takes ~100-300ms. 
  # Only pay this penalty when absolutely necessary.
  _dynamodb = boto3.resource('dynamodb', region_name=os.environ['AWS_REGION'])
  _table = _dynamodb.Table(os.environ['TABLE_NAME'])
 return _table</p>
<p>def lambda_handler(event, context):
 """
 Main execution handler.
 """
 # Fast path: Serve request from memory/cache without AWS SDK overhead
 if event.get('action') == 'ping':
  return {"statusCode": 200, "body": "pong"}</p>
<p># Slow path: Lazy load the expensive resource only when required
 table = get_dynamo_table()</p>
<p>response = table.get_item(Key={'id': event['id']})
 return {
  "statusCode": 200,
  "body": json.dumps(response.get('Item', {}))
 }</code></pre></p>
<h3>Code Analysis</h3>
<ul>
<li><code>_dynamodb = None</code>: Declaring client variables globally allows instance state to persist across warm invocations within the same execution environment.</li>
<li><code>if _dynamodb is None:</code>: Lazy initialization skips SDK setup for lightweight requests like <code>ping</code>. For database reads, the ~200ms SDK setup penalty occurs once during the initial call; warm calls bypass it entirely.</li>
</ul>
<h2>2. Eliminating Cold Starts with Provisioned Concurrency</h2>
<p>Optimizing code can reduce cold starts from five seconds down to one second, but strict SLAs often require sub-100ms response times. Provisioned Concurrency eliminates cold start latency by pre-initializing execution environments and keeping them warm continuously.</p>
<p>You can configure Provisioned Concurrency using Terraform:</p>
<pre><code class="language-hcl"># Terraform configuration for Provisioned Concurrency
resource "aws_lambda_function" "api_handler" {
 filename = "deployment_package.zip"
 function_name = "payment-api-handler"
 role  = aws_iam_role.lambda_exec.arn
 handler  = "index.lambda_handler"
 runtime  = "python3.9"
 publish  = true # CRITICAL: PC requires versioning
}
<p>resource "aws_lambda_alias" "prod_alias" {
 name   = "prod"
 description = "Production alias serving live traffic"
 function_name = aws_lambda_function.api_handler.function_name
 function_version = aws_lambda_function.api_handler.version
}</p>
<p>resource "aws_lambda_provisioned_concurrency_config" "api_pc" {
 # Attach PC to the ALIAS, not the $LATEST function
 function_name    = aws_lambda_function.api_handler.function_name
 qualifier     = aws_lambda_alias.prod_alias.name
 provisioned_concurrent_executions = 50 # Keep 50 instances permanently warm</p>
<p># Ensure the alias is created before applying PC
 depends_on = [aws_lambda_alias.prod_alias] 
}</code></pre></p>
<h3>Code Analysis</h3>
<ul>
<li><code>publish = true</code>: Provisioned Concurrency applies exclusively to immutable published function versions, not <code>$LATEST</code>.</li>
<li><code>aws_lambda_alias</code>: Routes incoming traffic to a specific published version.</li>
<li><code>aws_lambda_provisioned_concurrency_config</code>: Instructs AWS to maintain 50 pre-warmed execution environments for the <code>prod</code> alias. AWS initializes these instances in the background, serving incoming requests with single-digit millisecond latency without triggering cold starts.</li>
</ul>
<p>Combining lazy initialization in code with Provisioned Concurrency in Terraform keeps serverless APIs brisk and predictable under tight latency budgets.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/aws-lambda-cold-starts</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/aws-lambda-cold-starts.jpg" length="86859" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/aws-lambda-cold-starts.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Building a CrowdSec Hub on Proxmox]]></title>
      <link>https://anasrhimi.tech/blog/crowdsec-proxmox</link>
      <description><![CDATA[A complete technical walkthrough of migrating from standalone security to a centralized CrowdSec Hub-and-Spoke architecture across Proxmox LXC containers. by Anas Rhimi C, Python, TypeScript, Swift, and more.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/crowdsec-proxmox.jpg" alt="Building a CrowdSec Hub on Proxmox"></figure>
<p>Managing security across multiple Proxmox LXC containers quickly becomes cumbersome. Originally, running standalone Fail2ban or CrowdSec instances inside every individual LXC container meant parsing logs on 15 separate instances, maintaining duplicate blocklists, and wasting CPU resources on redundant threat processing.</p>
<p>A significantly cleaner pattern is implementing a CrowdSec hub-and-spoke model: deploying one central Local API (LAPI) node to manage security decisions, while the remaining containers operate as lightweight agents and enforcement bouncers.</p>
<h2>How the Architecture Works</h2>
<p>Rather than executing a full CrowdSec engine inside every container, responsibilities are logically separated:</p>
<ul>
<li><strong>The Hub (LAPI Server):</strong> A dedicated, lightweight LXC container running the CrowdSec Local API daemon. It aggregates threat intelligence from the global CrowdSec network, processes incoming security events, and maintains the authoritative blocklist.</li>
<li><strong>The Spokes (Agents & Bouncers):</strong> Containers running application workloads (such as Nginx Proxy Manager, Nextcloud, or Gitea). They execute the lightweight CrowdSec agent to forward log events to the Hub, or query the Hub's LAPI to drop malicious traffic at the firewall or reverse proxy layer.</li>
</ul>
<h2>Implementation Steps</h2>
<h3>1. Setting Up the Central Hub</h3>
<p>Provision a fresh Debian or Ubuntu LXC container in Proxmox to serve as the hub. Install CrowdSec:</p>
<pre><code class="language-bash">curl -s https://install.crowdsec.net | sudo sh
sudo apt-get install -y crowdsec</code></pre>
<p>By default, CrowdSec binds its Local API to <code>127.0.0.1:8080</code>. To allow spoke containers on your internal subnet to connect, modify <code>/etc/crowdsec/config.yaml</code>:</p>
<pre><code class="language-yaml">api:
 server:
 listen_uri: 0.0.0.0:8080</code></pre>
<p>Restart CrowdSec to apply the changes:</p>
<pre><code class="language-bash">sudo systemctl restart crowdsec</code></pre>
<h3>2. Registering Spoke Machines</h3>
<p>On the Hub container, create machine credentials for each spoke (for instance, your Nginx Proxy Manager container):</p>
<pre><code class="language-bash">sudo cscli machines add nginx-proxy-spoke --auto</code></pre>
<p>Save the generated password and API key.</p>
<p>On the spoke container, install the CrowdSec agent. Disable its local LAPI service so it does not evaluate decisions locally, and update <code>/etc/crowdsec/config.yaml</code> to point the agent to your Hub's IP address:</p>
<pre><code class="language-yaml">api:
 client:
 insecure_skip_verify: false
 credentials_path: /etc/crowdsec/local_api_credentials.yaml</code></pre>
<p>Update <code>/etc/crowdsec/local_api_credentials.yaml</code> with the Hub's LAPI connection details:</p>
<pre><code class="language-yaml">url: http://&lt;HUB_IP&gt;:8080
login: nginx-proxy-spoke
password: &lt;GENERATED_PASSWORD&gt;</code></pre>
<p>Finally, verify the agent connection on the Hub:</p>
<pre><code class="language-bash">sudo cscli machines list</code></pre>
<h3>3. Attaching Bouncers</h3>
<p>Install the appropriate bouncer plugin on each spoke container (such as <code>crowdsec-firewall-bouncer</code> or the Nginx Proxy Manager bouncer plugin). Register the bouncer with the Hub to receive real-time blocklist updates:</p>
<pre><code class="language-bash">sudo cscli bouncers add nginx-proxy-bouncer</code></pre>
<h2>Results</h2>
<p>Decoupling log parsing from threat analysis reduced total security-related CPU consumption across the Proxmox cluster by roughly 60%. Crucially, threat mitigation is now synchronized across the entire network: if an IP triggers a brute-force threshold on Nextcloud, it is immediately blocked across Nginx Proxy Manager and all other exposed services before any further request attempts occur.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/crowdsec-proxmox</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/crowdsec-proxmox.jpg" length="90761" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/crowdsec-proxmox.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Automating IAM Least Privilege Policies in AWS using Access Analyzer]]></title>
      <link>https://anasrhimi.tech/blog/iam-least-privilege-aws</link>
      <description><![CDATA[Automate the creation of least privilege IAM policies in AWS with IAM Access Analyzer.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/iam-least-privilege-aws.jpg" alt="Automating IAM Least Privilege Policies in AWS using Access Analyzer"></figure>
<p>Overly permissive IAM roles represent a persistent security vulnerability in AWS environments. During early prototyping or local development, developers frequently assign broad actions such as <code>s3:<em></code> or <code>dynamodb:</em></code> to expedite setup. Unfortunately, these wildcard configurations regularly find their way directly into production. Manually auditing and pruning permissions is both cumbersome and hazardous, as revoking required actions without visibility into application behavior can easily trigger unexpected outages.</p>
<p>AWS IAM Access Analyzer addresses this challenge by synthesizing refined, least-privilege IAM policies directly from observed CloudTrail activity logs. Rather than estimating necessary permissions, Access Analyzer audits actual API invocations made by your application and constructs a tailored JSON policy.</p>
<h2>Generating Policies via the AWS CLI</h2>
<p>IAM Access Analyzer evaluates CloudTrail management and data events across a designated timeframe to construct a policy that includes only the actions that were actively executed. The following Bash script automates policy generation and retrieval:</p>
<pre><code class="language-bash">#!/bin/bash
ROLE_ARN="arn:aws:iam::123456789012:role/OverPermissiveAppRole"
TRAIL_ARN="arn:aws:cloudtrail:us-east-1:123456789012:trail/management-events"
START_TIME=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)
END_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
<p># Step 1: Initiate Policy Generation
JOB_ID=$(aws accessanalyzer start-policy-generation \
    --policy-generation-details "principalArn=$ROLE_ARN" \
    --cloud-trail-details "accessRole=arn:aws:iam::123456789012:role/AnalyzerRole,startTime=$START_TIME,endTime=$END_TIME,trails=[$TRAIL_ARN]" \
    --query 'jobId' --output text)</p>
<p>echo "Started job: $JOB_ID. Waiting for completion..."</p>
<p># Step 2: Poll for Completion
while true; do
    STATUS=$(aws accessanalyzer get-generated-policy \
        --job-id "$JOB_ID" \
        --query 'jobDetails.status' --output text)
    if [ "$STATUS" == "SUCCEEDED" ]; then break; fi
    if [ "$STATUS" == "FAILED" ]; then echo "Generation failed"; exit 1; fi
    sleep 10
done</p>
<p># Step 3: Retrieve and Save the Granular Policy
aws accessanalyzer get-generated-policy \
    --job-id "$JOB_ID" \
    --include-resource-placeholders \
    --query 'generatedPolicyResult.generatedPolicies[0].policy' \
    --output json &gt; least_privilege_policy.json</p>
<p>echo "Policy saved to least_privilege_policy.json"</code></pre></p>
<h2>How the Automation Workflow Works</h2>
<ul>
<li><strong><code>START_TIME</code> and <code>END_TIME</code></strong>: Establishes a 7-day observation window to capture routine traffic alongside periodic scheduled tasks, weekly batch processes, and cron jobs.</li>
<li><strong><code>start-policy-generation</code></strong>: Directs Access Analyzer to analyze CloudTrail logs associated with the targeted <code>ROLE_ARN</code>. The service uses <code>AnalyzerRole</code> (which requires read access to CloudTrail logs stored in S3) to inspect historical requests.</li>
<li><strong>Polling loop (<code>while true ... sleep 10</code>)</strong>: Policy generation executes asynchronously within AWS. The script checks the job status every 10 seconds until processing concludes.</li>
<li><strong><code>--include-resource-placeholders</code></strong>: Access Analyzer maps precise API calls (e.g., <code>s3:GetObject</code>), but may not consistently identify target resource ARNs. Setting this flag inserts placeholder variables like <code>${ResourceName}</code> into the document, enabling manual substitution of specific bucket names or DynamoDB tables prior to deployment.</li>
<li><strong><code>--query</code> filter</strong>: Applies a JMESPath filter to extract the raw JSON policy document directly from the API output and write it locally for integration with Infrastructure as Code workflows like Terraform or CloudFormation.</li>
</ul>
<p>Executing this evaluation cycle regularly across your application roles allows you to systematically remove wildcard permissions while ensuring service reliability and continuity.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/iam-least-privilege-aws</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/iam-least-privilege-aws.jpg" length="117869" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/iam-least-privilege-aws.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Optimizing GitHub Actions Caching for Monorepo CI/CD Pipelines]]></title>
      <link>https://anasrhimi.tech/blog/ci-cd-github-actions-caching</link>
      <description><![CDATA[Slash your monorepo build times by implementing advanced caching strategies in GitHub Actions. Learn how to isolate caches by workspace and hash lockfiles accurately.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/ci-cd-github-actions-caching.jpg" alt="Optimizing GitHub Actions Caching for Monorepo CI/CD Pipelines"></figure>
<h2>The Monorepo CI Bottleneck</h2>
<p>Monorepos simplify code sharing across projects, but inefficient dependency caching can quickly slow down CI/CD pipelines. Re-running <code>npm install</code> or <code>pip install</code> across a massive codebase for every pull request consumes unnecessary compute credits and delays developer feedback. Implementing hash-based caching in GitHub Actions solves this problem by efficiently storing package archives at a granular level.</p>
<h2>Implementing Granular Dependency Caching (Node.js Example)</h2>
<p>Caching the entire <code>node_modules</code> directory directly often results in oversized cache files and potential cross-platform binary incompatibilities between runner environments. A more effective strategy is targeting the global npm or Yarn cache folder. Deriving the cache key from a lockfile hash ensures that cached assets are automatically invalidated whenever dependencies are updated.</p>
<pre><code class="language-yaml">name: Monorepo CI Pipeline
<p>on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]</p>
<p>jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4</p>
<ul>
<li>name: Setup Node.js 20.x uses: actions/setup-node@v4 with: node-version: '20.x'</li>
</ul>
<ul>
<li>name: Get npm cache directory id: npm-cache-dir # Extracts the exact path to the npm cache dynamically run: echo "dir=$(npm config get cache)" &gt;&gt; $GITHUB_OUTPUT</li>
</ul>
<ul>
<li>name: Cache npm dependencies uses: actions/cache@v4 id: npm-cache # Used to check for cache hits later with: path: ${{ steps.npm-cache-dir.outputs.dir }} # The key incorporates the OS, Node version, and the hash of the package-lock.json # If the lockfile changes, the hash changes, and a new cache is created. key: ${{ runner.os }}-node-${{ hashFiles('<em></em>/package-lock.json') }} # Restore keys provide fallback options if an exact match isn't found, # speeding up resolution for minor dependency updates. restore-keys: | ${{ runner.os }}-node-</li>
</ul>
<ul>
<li>name: Install Dependencies # If we got a cache hit, npm ci will use the cache, significantly reducing network IO run: npm ci</li>
</ul>
<ul>
<li>name: Run Build (Nx / Lerna / Turborepo) run: npx turbo run build --filter=...</code></pre></li>
</ul>
<p>The critical element in this configuration is <code>hashFiles('<em></em>/package-lock.json')</code>. In a monorepo setup, this function evaluates and hashes lockfiles throughout the entire repository structure. For further optimization, you can isolate CI jobs per package and hash only the lockfile corresponding to that specific component.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/ci-cd-github-actions-caching</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/ci-cd-github-actions-caching.jpg" length="95408" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/ci-cd-github-actions-caching.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Building Akua: A Production-Ready WhatsApp AI Shopping Assistant]]></title>
      <link>https://anasrhimi.tech/blog/akua-whatsapp-ai-case-study</link>
      <description><![CDATA[How I built a reliable WhatsApp AI shopping assistant that survives hallucinated products, silent save failures, and multi-agent handoff bugs using n8n and LLMs. by Anas Rhimi AI-Infra DevOps Engineer.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/akua-whatsapp-ai-case-study.jpg" alt="Building Akua: A Production-Ready WhatsApp AI Shopping Assistant"></figure>
<p>Building an AI shopping assistant for an e-commerce store like Clickmothercare leaves very little margin for error. When a conventional web application fails, users encounter a 500 error page and hit refresh. However, when an AI assistant invents a product that does not exist in stock, recommends an incorrect size to an expecting mother, or drops a cart mid-conversation, you risk losing that customer permanently.</p>
<p>Clickmothercare required Akua: a WhatsApp assistant capable of understanding natural customer inquiries, checking real-time inventory, and guiding shoppers through checkout. The core challenge involved reliably connecting unpredictable language models to a rigid e-commerce backend.</p>
<h2>Common AI Failure Modes We Encountered</h2>
<p>Early during development, three primary issues emerged:</p>
<ul>
<li><strong>Product Hallucinations:</strong> The model would confidently suggest products Clickmothercare did not carry, frustrating users when they attempted to purchase them.</li>
<li><strong>Context Loss During Handoffs:</strong> Transferring state between specialized agents (such as greeting, inventory lookup, and checkout) frequently lost conversation history, forcing customers to repeat themselves.</li>
<li><strong>Silent Cart Failures:</strong> Network timeouts between the model API and the database caused cart updates to occasionally fail without reporting an error, leading the bot to lose track of selected items.</li>
</ul>
<h2>Re-Architecting for Reliability with n8n and Kubernetes</h2>
<p>To address these challenges, I replaced the initial monolithic script architecture with an asynchronous, event-driven backend utilizing n8n deployed on Kubernetes.</p>
<p>Here is how we resolved each issue:</p>
<ul>
<li><strong>Strict Inventory Checks:</strong> Instead of allowing the model to answer queries from memory, we restricted it to strictly typed tool calls against a Redis-cached inventory database.</li>
<li><strong>Centralized State Management:</strong> We transferred conversation state out of individual agent prompts and stored it in a PostgreSQL cluster, ensuring context remained intact during agent transitions.</li>
<li><strong>Message Queues and Retries:</strong> We introduced message queues configured with exponential backoff to handle transient network drops, guaranteeing that database writes and cart updates execute reliably.</li>
</ul>
<p>Akua currently handles thousands of WhatsApp messages daily without losing context or recommending out-of-stock items.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/akua-whatsapp-ai-case-study</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/akua-whatsapp-ai-case-study.jpg" length="100078" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/akua-whatsapp-ai-case-study.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Preventing API Key Leaks with Pre-Commit Secrets Scanning]]></title>
      <link>https://anasrhimi.tech/blog/secrets-scanning-pre-commit-hooks</link>
      <description><![CDATA[Automate secrets scanning in your git repository using pre-commit hooks and TruffleHog.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/secrets-scanning-pre-commit-hooks.jpg" alt="Preventing API Key Leaks with Pre-Commit Secrets Scanning"></figure>
<p>Pushing API keys, AWS tokens, or private keys to Git is a developer nightmare automated scrapers index public repositories and exploit exposed credentials within minutes. Waiting for server-side scanners or CI/CD pipelines to catch them means the secret is already recorded in your commit history and requires immediate revocation.</p>
<p>Detecting credentials before they leave your local development environment is a much more effective approach. Here is how to configure TruffleHog as a pre-commit hook to catch secrets locally.</p>
<h2>Pre-Commit Setup with TruffleHog</h2>
<p>Add this hook definition to your <code>.pre-commit-config.yaml</code>:</p>
<pre><code class="language-yaml">repos:
 - repo: https://github.com/trufflesecurity/trufflehog
 rev: v3.63.7
 hooks:
 - id: trufflehog
  name: TruffleHog Secret Scanner
  description: Detects verified secrets in committed code.
  entry: bash -c 'trufflehog git file://. --since-commit HEAD --only-verified --fail'
  language: system
  stages: [commit]
  pass_filenames: false</code></pre>
<h2>How This Configuration Works</h2>
<ul>
<li><strong><code>repo</code></strong>: Points to TruffleHog's official repository to fetch the hook definition.</li>
<li><strong><code>entry</code></strong>: Overrides the default command to customize how TruffleHog scans staged local changes.</li>
<li><strong><code>trufflehog git file://. --since-commit HEAD</code></strong>: Instead of scanning the entire Git history (which takes significant time), it only evaluates the diff of staged files against <code>HEAD</code>. This keeps commit check times under a few milliseconds.</li>
<li><strong><code>--only-verified</code></strong>: TruffleHog attempts to authenticate any detected key against the provider API in real time. If validation fails, it ignores the match, eliminating false positives so developers do not contend with harmless alerts.</li>
<li><strong><code>--fail</code></strong>: Exits with a non-zero status code if an active secret is found, causing <code>pre-commit</code> to block the Git commit.</li>
<li><strong><code>pass_filenames: false</code></strong>: Prevents <code>pre-commit</code> from appending a list of modified files to the end of the <code>entry</code> string, as TruffleHog manages diff inspection directly.</li>
</ul>
<p>Implementing this configuration across your team prevents credentials from entering commits initially, sparing your organization from complex credential rotation emergency procedures.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/secrets-scanning-pre-commit-hooks</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/secrets-scanning-pre-commit-hooks.jpg" length="91500" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/secrets-scanning-pre-commit-hooks.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Proxmox Backup Server (PBS): The Ultimate Automated Backup Strategy]]></title>
      <link>https://anasrhimi.tech/blog/proxmox-backup-server-automated</link>
      <description><![CDATA[A complete technical walkthrough of migrating from standalone security to a centralized CrowdSec Hub-and-Spoke architecture across Proxmox LXC containers. by Anas Rhimi C, Python, TypeScript, Swift, and more.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/proxmox-backup-server-automated.jpg" alt="Proxmox Backup Server (PBS): The Ultimate Automated Backup Strategy"></figure>
<p>If you execute Proxmox VE in a homelab or production environment, standard <code>vzdump</code> tarballs sent over NFS quickly slow down and waste storage space. Proxmox Backup Server (PBS) resolves this by delivering enterprise-grade backups right out of the box.</p>
<h2>Key Technical Advantages</h2>
<ul>
<li><strong>Chunk-Level Deduplication:</strong> PBS chunks data blocks across all backup snapshots. Running 10 similar Linux VMs means shared OS blocks are stored only once, saving significant disk space.</li>
<li><strong>Fast Incremental Backups:</strong> After the initial synchronization, PBS transfers only changed data blocks. Backing up a 50GB VM often completes in seconds rather than minutes.</li>
<li><strong>Ransomware & Access Protection:</strong> Fine-grained permissions, client-side encryption, and tape support make it straightforward to maintain immutable and air-gapped backups.</li>
</ul>
<p>Setting up PBS on a dedicated physical host (or an isolated instance outside your main compute nodes) provides your Proxmox cluster with a fast, reliable disaster recovery target.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/proxmox-backup-server-automated</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/proxmox-backup-server-automated.jpg" length="100681" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/proxmox-backup-server-automated.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Implementing GitOps with FluxCD for Multi-Tenant Kubernetes]]></title>
      <link>https://anasrhimi.tech/blog/gitops-fluxcd-multi-tenant</link>
      <description><![CDATA[Managing a single Kubernetes cluster for a single team is straightforward. But when you introduce multiple engineering teams, each requiring isolated environments, the complexity multiplies.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/gitops-fluxcd-multi-tenant.jpg" alt="Implementing GitOps with FluxCD for Multi-Tenant Kubernetes"></figure>
<p>Running a single Kubernetes cluster for one team is straightforward. Once you introduce multiple engineering teams requiring isolated environments, managing permissions and operational boundaries becomes complex.</p>
<p>Developers require autonomy to ship code via GitOps without accessing cluster-scoped resources or other teams' namespaces. FluxCD addresses this natively by pairing Kubernetes Role-Based Access Control (RBAC) with ServiceAccount impersonation.</p>
<p>Multi-tenancy in FluxCD relies on two levels of isolation:
1. <strong>Git repository isolation</strong> Each team manages its own repository with specific access permissions.
2. <strong>Kubernetes namespace isolation</strong> Reconcilers operate strictly within designated target namespaces.</p>
<p>Here is how to structure a multi-tenant FluxCD setup step by step.</p>
<p>---</p>
<h2>1. Bootstrapping the Cluster Admin Repository</h2>
<p>The cluster admin repository defines the overall cluster state, infrastructure components (like ingress controllers and monitoring tools), and tenant onboarding manifests. Only the platform engineering team should have write access to this repo.</p>
<pre><code class="language-yaml"># clusters/production/infrastructure.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
 name: infrastructure
 namespace: flux-system
spec:
 interval: 10m0s
 sourceRef:
 kind: GitRepository
 name: flux-system
 path: ./infrastructure
 prune: true
 wait: true</code></pre>
<p><strong>Key configurations:</strong>
- <code>kind: Kustomization</code>: Uses Flux's Kustomize controller to sync manifests from Git.
- <code>namespace: flux-system</code>: Core infrastructure remains managed centrally by the primary Flux instance.
- <code>path: ./infrastructure</code>: Points to base cluster configs inside the admin repository.
- <code>prune: true</code>: Deletes cluster resources automatically if they are removed from Git.</p>
<p>---</p>
<h2>2. Defining Tenant Boundaries with RBAC</h2>
<p>To onboard a tenant (for example, a <code>frontend</code> team), provision a dedicated namespace, a ServiceAccount for impersonation, and a <code>Role</code> / <code>RoleBinding</code> restricting that ServiceAccount to the tenant namespace.</p>
<pre><code class="language-yaml"># tenants/frontend/rbac.yaml
apiVersion: v1
kind: Namespace
metadata:
 name: frontend
---
apiVersion: v1
kind: ServiceAccount
metadata:
 name: frontend-reconciler
 namespace: frontend
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
 name: frontend-reconciler
 namespace: frontend
rules:
 - apiGroups: ["*"]
 resources: ["*"]
 verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
 name: frontend-reconciler
 namespace: frontend
roleRef:
 apiGroup: rbac.authorization.k8s.io
 kind: Role
 name: frontend-reconciler
subjects:
 - kind: ServiceAccount
 name: frontend-reconciler
 namespace: frontend</code></pre>
<p><strong>Key configurations:</strong>
- <code>Namespace</code>: Defines the logical isolation boundary for the team.
- <code>ServiceAccount</code>: The identity Flux impersonates when applying team manifests.
- <code>Role</code> & <code>RoleBinding</code>: Grants <code>frontend-reconciler</code> full permissions within the <code>frontend</code> namespace only. If a team attempts to deploy a cluster-scoped object (such as a <code>ClusterRole</code>), Flux rejects the deployment due to RBAC restrictions.</p>
<p>---</p>
<h2>3. Wiring the Tenant's Git Repository</h2>
<p>Next, configure Flux to monitor the tenant's Git repository while impersonating their restricted ServiceAccount.</p>
<pre><code class="language-yaml"># tenants/frontend/sync.yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
 name: frontend-repo
 namespace: frontend
spec:
 interval: 5m0s
 url: ssh://git@github.com/my-org/frontend-apps.git
 ref:
 branch: main
 secretRef:
 name: frontend-git-auth # Deploy key secret
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
 name: frontend-apps
 namespace: frontend
spec:
 interval: 10m0s
 sourceRef:
 kind: GitRepository
 name: frontend-repo
 path: ./deploy/production
 prune: true
 serviceAccountName: frontend-reconciler</code></pre>
<p><strong>Key configurations:</strong>
- <code>GitRepository</code>: Created inside the tenant namespace (<code>frontend</code>) to keep deployment keys (<code>secretRef</code>) isolated from other teams.
- <code>serviceAccountName</code>: Directs the global Flux Kustomize controller to relinquish cluster-admin privileges and impersonate <code>frontend-reconciler</code> during reconciliation.
- <code>targetNamespace</code> (optional setting or implicit scope): Scopes operations to <code>frontend</code>, preventing accidental or unauthorized cross-namespace deployments.</p>
<p>---</p>
<h2>Summary</h2>
<p>Combining repository-level access control, namespace boundaries, and Flux's ServiceAccount impersonation provides engineering teams with self-service deployments while maintaining cluster infrastructure security.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/gitops-fluxcd-multi-tenant</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/gitops-fluxcd-multi-tenant.jpg" length="91464" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/gitops-fluxcd-multi-tenant.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[The DevOps Burnout Crisis & How Platform Engineering Fixes It]]></title>
      <link>https://anasrhimi.tech/blog/devops-burnout-platform-engineering</link>
      <description><![CDATA[A complete technical walkthrough of migrating from standalone security to a centralized CrowdSec Hub-and-Spoke architecture across Proxmox LXC containers. by Anas Rhimi C, Python, TypeScript, Swift, and more.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/devops-burnout-platform-engineering.jpg" alt="The DevOps Burnout Crisis & How Platform Engineering Fixes It"></figure>
<p>DevOps was originally intended to streamline software delivery through automation. In reality, many engineers spend their days managing endless ticketing queues, stitching together fragmented toolchains, and serving as operational bottlenecks for software development teams.</p>
<p>Industry reports indicate that engineering teams waste approximately one-third of their working hours on repetitive infrastructure tasks. This continuous firefighting environment directly accelerates burnout across teams.</p>
<h2>The Shift to Platform Engineering</h2>
<p>Organizations are increasingly adopting Platform Engineering to break this unsustainable cycle. Instead of manually responding to ad-hoc requests for database provisioning or Kubernetes manifest modifications, platform teams build Internal Developer Platforms (IDPs).</p>
<ul>
<li><strong>Self-Service Enablement:</strong> Application engineers gain access to centralized portals such as Backstage allowing them to independently provision compliant environments adhering to guardrails set by the platform team.</li>
<li><strong>Platform as a Product:</strong> The platform engineering team treats developers as internal customers, continuously collecting feedback to ensure the IDP actively eliminates workflow friction.</li>
</ul>
<p>By abstracting away the operational complexity of Terraform, Helm charts, and CI/CD configurations, developers release application faster while platform engineers focus on architecture security, reliability, and scaling.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/devops-burnout-platform-engineering</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/devops-burnout-platform-engineering.jpg" length="111285" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/devops-burnout-platform-engineering.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Tuning the Linux Kernel TCP Stack for High-Throughput AI Workloads]]></title>
      <link>https://anasrhimi.tech/blog/optimizing-linux-kernel-tcp-stack</link>
      <description><![CDATA[Optimize your Linux TCP stack for massive bandwidth and low latency using BBR and sysctl tuning for AI data pipelines.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/optimizing-linux-kernel-tcp-stack.jpg" alt="Tuning the Linux Kernel TCP Stack for High-Throughput AI Workloads"></figure>
<p>Transferring multi-gigabyte or terabyte model checkpoints and training datasets across cluster nodes requires substantial network throughput. Default Linux kernel TCP settings are optimized for general web traffic, which severely caps performance below 100Gbps datacenter link speeds. Small default socket buffer sizes and loss-based congestion control algorithms throttle network throughput prematurely.</p>
<h2>The Problem: Bandwidth-Delay Product and Buffer Starvation</h2>
<p>The Bandwidth-Delay Product (BDP) determines the volume of unacknowledged data that must remain in transit to fully saturate a network link. On a 100Gbps network with 1ms round-trip latency, the calculated BDP requirement is approximately 12.5MB:</p>
<p>$$\text{BDP} = \frac{100 \text{ Gbps} \times 0.001 \text{ s}}{8} = 12.5 \text{ MB}$$</p>
<p>If <code>sysctl</code> receive and send socket buffers are restricted to default caps of 4MB or 6MB, TCP sender windows stall while waiting for acknowledgments (ACKs), leaving available network bandwidth underutilized.</p>
<h2>Persistent Sysctl Configuration</h2>
<p>The following script writes optimized TCP kernel parameters to <code>/etc/sysctl.d/99-ai-network-tuning.conf</code> and applies them across the host setup immediately.</p>
<pre><code class="language-bash">#!/bin/bash
# Apply high-throughput TCP settings for AI/ML Nodes
set -euo pipefail
<p>SYSCTL_CONF="/etc/sysctl.d/99-ai-network-tuning.conf"</p>
<p>cat &lt;&lt; 'EOF' | sudo tee "$SYSCTL_CONF" &gt; /dev/null
# 1. Socket buffer maximum caps (64MB)
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864</p>
<p># 2. TCP buffer auto-tuning ranges (min, default, max)
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864</p>
<p># 3. Fair Queueing + BBR congestion control
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr</p>
<p># 4. NIC backlog queue length
net.core.netdev_max_backlog = 30000</p>
<p># 5. TCP Window Scaling (RFC 1323)
net.ipv4.tcp_window_scaling = 1
EOF</p>
<p>echo "Applying sysctl settings..."
sudo sysctl --system</p>
<p>echo "Current congestion control algorithm:"
sysctl net.ipv4.tcp_congestion_control</code></pre></p>
<h2>Kernel Parameter Breakdown</h2>
<ul>
<li><strong><code>net.core.rmem_max</code> / <code>net.core.wmem_max</code> = 67108864</strong>: Increases the maximum socket read and write buffer cap to 64MB, providing high-BDP network connections with sufficient memory headroom to sustain in-flight data volumes.</li>
<li><strong><code>net.ipv4.tcp_rmem</code> / <code>net.ipv4.tcp_wmem</code></strong>: Configures kernel auto-tuning limits (minimum, default, and maximum buffer sizes). The Linux kernel dynamically adjusts buffer allocations per socket connection up to 64MB without consuming unnecessary memory on idle connections.</li>
<li><strong><code>net.ipv4.tcp_congestion_control = bbr</code></strong>: Replaces traditional loss-based CUBIC with BBR (Bottleneck Bandwidth and RTT). Unlike CUBIC, which reduces TCP congestion window sizes upon detecting packet loss, BBR models actual network capacity to maintain maximum throughput on high-speed links.</li>
<li><strong><code>net.core.default_qdisc = fq</code></strong>: Configures Fair Queueing packet pacing. FQ prevents BBR flows from transmitting micro-bursts of traffic that can overflow network switch buffers.</li>
<li><strong><code>net.core.netdev_max_backlog = 30000</code></strong>: Expands the network interface ingress queue depth at the driver layer, mitigating packet drops under heavy burst traffic conditions.</li>
<li><strong><code>net.ipv4.tcp_window_scaling = 1</code></strong>: Enables TCP window scaling extensions (RFC 1323), allowing window sizes to exceed the legacy 64KB boundary.</li>
</ul>
<p>Persisting these parameter configurations within <code>/etc/sysctl.d/</code> ensures settings remain active across platform reboots. On 100Gbps network links, proper TCP buffer scaling combined with BBR pacing delivers consistent line-rate data transfers between distributed training nodes.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/optimizing-linux-kernel-tcp-stack</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/optimizing-linux-kernel-tcp-stack.jpg" length="113765" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/optimizing-linux-kernel-tcp-stack.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Building a Highly Available k3s Edge Cluster on Raspberry Pi 5]]></title>
      <link>https://anasrhimi.tech/blog/raspberry-pi-k3s-cluster</link>
      <description><![CDATA[Deploy an embedded etcd HA k3s cluster across three Raspberry Pi 5 nodes for edge computing workloads.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/raspberry-pi-k3s-cluster.jpg" alt="Building a Highly Available k3s Edge Cluster on Raspberry Pi 5"></figure>
<p>With the Raspberry Pi 5, we gain sufficient CPU headroom and I/O throughput to run a genuine High Availability (HA) Kubernetes cluster on edge hardware. Here is how to set up a 3-node HA k3s cluster using embedded etcd and Kube-VIP.</p>
<h2>Prerequisites & OS Setup</h2>
<p>This setup uses three Pi 5 nodes (<code>pi-master-1</code>, <code>pi-master-2</code>, <code>pi-master-3</code>) running Ubuntu 24.04 Server. Prior to installing k3s, enable cgroups and disable swap so that <code>kubelet</code> operates reliably.</p>
<pre><code class="language-bash"># On all nodes: Edit boot parameters
sudo sed -i '$ s/$/ cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1 swapaccount=1/' /boot/firmware/cmdline.txt
<p># Disable swap right away and keep it disabled after reboot
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab</p>
<p>sudo reboot</code></pre></p>
<blockquote><strong>Why this matters:</strong> Appending cgroup flags to <code>cmdline.txt</code> allows k3s to enforce pod memory and CPU limits. Disabling swap is required by Kubernetes so <code>kubelet</code> starts up reliably without throwing errors.</blockquote>
<h2>Initializing the First Control Plane Node</h2>
<p>We will use k3s's built-in etcd datastore for HA. This saves us from managing a separate MySQL or PostgreSQL database.</p>
<pre><code class="language-bash"># On pi-master-1 (10.0.0.11)
curl -sfL https://get.k3s.io | sh -s - server \
  --cluster-init \
  --tls-san 10.0.0.100 \
  --node-taint CriticalAddonsOnly=true:NoExecute
<p># Grab the cluster token needed for joining nodes
cat /var/lib/rancher/k3s/server/node-token</code></pre></p>
<blockquote><strong>Details:</strong> <code>--cluster-init</code> instructs k3s to spin up a new etcd cluster. <code>--tls-san 10.0.0.100</code> adds an extra SAN entry for the Virtual IP (VIP) we will set up with Kube-VIP. The <code>CriticalAddonsOnly</code> taint keeps general workloads off this control plane node.</blockquote>
<h2>Joining Additional Control Plane Nodes</h2>
<p>Now that node 1 is running etcd, join the remaining two nodes to establish a proper etcd quorum.</p>
<pre><code class="language-bash"># On pi-master-2 &amp; pi-master-3
export K3S_TOKEN=""
export K3S_URL="https://10.0.0.11:6443"
<p>curl -sfL https://get.k3s.io | sh -s - server \
  --server $K3S_URL \
  --token $K3S_TOKEN \
  --tls-san 10.0.0.100 \
  --node-taint CriticalAddonsOnly=true:NoExecute</code></pre></p>
<blockquote><strong>Note:</strong> Even though we are joining existing nodes, we still pass <code>server</code> rather than <code>agent</code>. Adding <code>--server</code> tells k3s to attach as an additional control plane peer instead of a worker node.</blockquote>
<h2>Setting Up Kube-VIP for API Server HA</h2>
<p>For true high availability, client traffic and <code>kubectl</code> should point to a shared virtual IP rather than an individual node's IP address. We can deploy Kube-VIP as a static pod manifest.</p>
<pre><code class="language-bash"># Generate the Kube-VIP manifest on pi-master-1
KVVERSION=$(curl -sL https://api.github.com/repos/kube-vip/kube-vip/releases | jq -r ".[0].name")
sudo ctr image pull ghcr.io/kube-vip/kube-vip:$KVVERSION
<p># Generate static pod YAML
sudo ctr run --rm --net-host ghcr.io/kube-vip/kube-vip:$KVVERSION vip \
  /kube-vip manifest pod \
  --interface eth0 \
  --address 10.0.0.100 \
  --controlplane \
  --services \
  --arp \
  --leaderElection | sudo tee /var/lib/rancher/k3s/agent/pod-manifests/kube-vip.yaml</code></pre></p>
<blockquote><strong>How it works:</strong> We use k3s's built-in <code>ctr</code> tool to fetch the Kube-VIP container and generate the pod spec directly into <code>/var/lib/rancher/k3s/agent/pod-manifests/</code>. <code>kubelet</code> automatically picks up files in this directory and runs them as static pods. <code>--arp</code> enables Layer 2 load balancing, and <code>--leaderElection</code> guarantees only one control plane node broadcasts ARP packets for <code>10.0.0.100</code> at a time.</blockquote>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/raspberry-pi-k3s-cluster</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/raspberry-pi-k3s-cluster.jpg" length="92293" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/raspberry-pi-k3s-cluster.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Automating Terraform State Drift Detection and Remediation]]></title>
      <link>https://anasrhimi.tech/blog/terraform-state-drift-automation</link>
      <description><![CDATA[Discover how to build a robust pipeline for detecting and remediating Terraform state drift using GitHub Actions, ensuring your infrastructure always matches your code.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/terraform-state-drift-automation.jpg" alt="Automating Terraform State Drift Detection and Remediation"></figure>
<h2>Infrastructure Drift</h2>
<p>Infrastructure as Code operates effectively only when registered state reflects live reality. When engineers introduce manual modifications via the AWS Console or execute ad-hoc scripts, the Terraform state file diverges from actual cloud resources. During the subsequent <code>terraform apply</code>, Terraform may unintentionally overwrite those out-of-band updates or cause production service disruptions.</p>
<p>You can proactively prevent drift issues by executing automated drift detection workflows using scheduled GitHub Actions.</p>
<h2>Building the Drift Detection Workflow</h2>
<p>To identify drift automatically, schedule a GitHub Action that executes <code>terraform plan</code> incorporating the <code>-detailed-exitcode</code> flag:</p>
<ul>
<li><code>0</code>: No changes identified (live infrastructure aligns with state).</li>
<li><code>1</code>: Error encountered during execution.</li>
<li><code>2</code>: Resource discrepancies detected (drift confirmed).</li>
</ul>
<p>Below is a configuration that runs hourly and notifies your operations team upon detecting state drift:</p>
<pre><code class="language-yaml">name: Terraform Drift Detection
on:
 schedule:
 - cron: '0 <em> </em> <em> </em>' # Run hourly
<p>jobs:
 detect-drift:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v4</p>
<ul>
<li>name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: terraform_wrapper: false</li>
</ul>
<ul>
<li>name: Terraform Init run: terraform init</li>
</ul>
<ul>
<li>name: Terraform Plan (Detect Drift) id: plan # Catch exitcode so step doesn't fail on code 2 run: | terraform plan -detailed-exitcode -out=tfplan || export exitcode=$? echo "exitcode=$exitcode" &gt;&gt; $GITHUB_OUTPUT if [ $exitcode -eq 1 ]; then echo "Terraform plan failed with an error." exit 1 fi</li>
</ul>
<ul>
<li>name: Alert on Drift if: steps.plan.outputs.exitcode == '2' run: | echo "Drift detected! Changes are required to match the state." curl -X POST -H 'Content-type: application/json' \ --data '{"text":"🚨 Terraform drift detected in production! Please review immediately."}' \ ${{ secrets.SLACK_WEBHOOK_URL }}</code></pre></li>
</ul>
<h2>Automated Remediation Strategies</h2>
<p>Automatically executing <code>terraform apply</code> to overwrite detected drift involves operational risk. If an engineer manually updated a production resource during an incident as a temporary remediation, triggering an unreviewed apply could revert the critical fix and re-introduce downtime.</p>
<p>A more robust pattern involves sending immediate notifications or generating an automated Pull Request that updates the HCL codebase to match the drifted state utilizing tools such as <code>terraformer</code> or evaluating plan JSON output. This guarantees peer review before infrastructure changes are applied.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/terraform-state-drift-automation</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/terraform-state-drift-automation.jpg" length="91637" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/terraform-state-drift-automation.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Writing Idempotent Ansible Playbooks for Kubernetes Clusters]]></title>
      <link>https://anasrhimi.tech/blog/ansible-idempotent-playbooks-kubernetes</link>
      <description><![CDATA[Learn how to write truly idempotent Ansible playbooks for managing Kubernetes clusters, complete with code examples and best practices for declarative state management.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/ansible-idempotent-playbooks-kubernetes.jpg" alt="Writing Idempotent Ansible Playbooks for Kubernetes Clusters"></figure>
<h2>The Challenge: Mixing Ansible with Kubernetes</h2>
<p>Kubernetes handles cluster state declaratively through its API, but relying on Ansible for cluster bootstrap, secret distribution, or external infrastructure configuration can regularly lead to imperative patterns. A common pitfall is falling back on Ansible's <code>command</code> or <code>shell</code> modules to execute raw <code>kubectl apply</code> invocations. Executing arbitrary shell commands breaks idempotency because Ansible runs them on every execution, regardless of whether the target cluster state requires updates.</p>
<h2>Using <code>kubernetes.core.k8s</code> Instead of Shell Commands</h2>
<p>To make playbooks truly idempotent, interact directly with the Kubernetes API using the <code>kubernetes.core.k8s</code> module. Rather than calling external binaries, this module queries the API server to evaluate your target specification against live cluster resources, making modifications only when live state drifts from desired configuration.</p>
<pre><code class="language-yaml">- name: Ensure Nginx deployment is in the desired state
  kubernetes.core.k8s:
    state: present
    definition:
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: nginx-deployment
        namespace: production
      spec:
        replicas: 3
        selector:
          matchLabels:
            app: nginx
        template:
          metadata:
            labels:
              app: nginx
          spec:
            containers:
            - name: nginx
              image: nginx:1.24.0 # Pin specific tag for idempotency
              ports:
              - containerPort: 80</code></pre>
<p>Notice the explicit container image tag: <code>nginx:1.24.0</code>. Avoid using mutable tags such as <code>:latest</code> in idempotent playbooks. Using mutable tags prevents Ansible from verifying whether the underlying container image changed, which can lead to unnecessary workload restarts or unhandled configuration drift.</p>
<h2>Idempotent Secret Management with <code>slurp</code></h2>
<p>Dynamic secret handling frequently causes playbooks to report false positive changes. If you generate or read TLS certificates on every execution, Ansible may re-apply the Kubernetes secret even when values remain identical.</p>
<p>Using <code>ansible.builtin.slurp</code> helps preserve idempotency when syncing local TLS certificates into Kubernetes secrets:</p>
<pre><code class="language-yaml">- name: Read TLS certificate contents
  ansible.builtin.slurp:
    src: /etc/ssl/certs/app.crt
  register: tls_cert
<ul>
<li>name: Read TLS key contents ansible.builtin.slurp: src: /etc/ssl/private/app.key register: tls_key</li>
</ul>
<ul>
<li>name: Apply TLS Secret to Kubernetes kubernetes.core.k8s: state: present definition: apiVersion: v1 kind: Secret type: kubernetes.io/tls metadata: name: app-tls namespace: production data: tls.crt: "{{ tls_cert['content'] }}" # slurp provides base64-encoded strings tls.key: "{{ tls_key['content'] }}"</code></pre></li>
</ul>
<p>The <code>slurp</code> module outputs file contents pre-encoded in Base64, matching what Kubernetes <code>Secret</code> data fields expect. When <code>kubernetes.core.k8s</code> runs, it compares the payload hash with the active secret in the cluster. If local files haven't changed, Ansible reports <code>ok</code> instead of <code>changed</code>, preventing unnecessary object updates.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/ansible-idempotent-playbooks-kubernetes</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/ansible-idempotent-playbooks-kubernetes.jpg" length="92992" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/ansible-idempotent-playbooks-kubernetes.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Data Annotation & AI Workflows: A Freelancer's Guide]]></title>
      <link>https://anasrhimi.tech/blog/data-annotation-ai-workflows-freelancer</link>
      <description><![CDATA[A complete technical walkthrough of migrating from standalone security to a centralized CrowdSec Hub-and-Spoke architecture across Proxmox LXC containers. by Anas Rhimi C, Python, TypeScript, Swift, and more.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/data-annotation-ai-workflows-freelancer.jpg" alt="Data Annotation & AI Workflows: A Freelancer's Guide"></figure>
<p>Demand for high-quality training data continues to grow. For freelancers in the data annotation space, scaling your operations efficiently is not about working longer hours it is about designing automated workflows.</p>
<h2>Automating the Pipeline</h2>
<p>Manually retrieving datasets, reformatting files, and transferring them into annotation platforms like Labelbox or CVAT consumes significant billable time. You can automate much of this operational overhead:</p>
<ul>
<li><strong>n8n Workflows:</strong> Implement n8n to automatically pull raw files from client APIs or cloud storage solutions like Google Drive, normalize the dataset structure via webhooks, and push the processed assets directly into your labeling workspace.</li>
<li><strong>AI Pre-Labeling:</strong> Deploy lightweight models (such as YOLO for computer vision or local LLMs for NLP tasks) to generate preliminary annotations prior to manual review. Your primary task transitions from manual tagging to quality assurance verification, significantly increasing throughput.</li>
</ul>
<p>By packaging automated data ingestion and pre-annotation pipelines with your core annotation services, you transition away from low-margin hourly competition and move toward value-based pricing models.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/data-annotation-ai-workflows-freelancer</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/data-annotation-ai-workflows-freelancer.jpg" length="102868" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/data-annotation-ai-workflows-freelancer.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[DevSecOps in 2026: Automating SBOMs and Policy Checks]]></title>
      <link>https://anasrhimi.tech/blog/devsecops-automating-sboms</link>
      <description><![CDATA[A complete technical walkthrough of migrating from standalone security to a centralized CrowdSec Hub-and-Spoke architecture across Proxmox LXC containers. by Anas Rhimi C, Python, TypeScript, Swift, and more.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/devsecops-automating-sboms.jpg" alt="DevSecOps in 2026: Automating SBOMs and Policy Checks"></figure>
<p>Security compliance should never act as a last-minute barrier introduced right before a production deployment. Contemporary DevSecOps practices embed continuous verification mechanisms directly into automated CI/CD pipelines.</p>
<h2>The Rise of the SBOM</h2>
<p>A Program Bill of Materials (SBOM) functions as a comprehensive inventory cataloging all third-party libraries, dependencies, and system components compiled into an application artifact. When critical vulnerabilities emerge, security operations must identify compromised microservices within seconds rather than spending days executing manual audits across codebase repositories.</p>
<ul>
<li><strong>Automated Generation</strong>: Incorporate security scanners such as Syft or Trivy into CI/CD build scripts to automatically generate updated SBOM artifacts during every release cycle.</li>
<li><strong>Cryptographic Artifact Signing</strong>: Cryptographically sign container images using open-source tools like Sigstore Cosign. Enforce Kubernetes policy engines (such as Kyverno or OPA Gatekeeper) to automatically reject unsigned or non-compliant container images from entering execution environments.</li>
</ul>
<p>Automating dependency inventory tracking and cryptographic signature checks protects application deployments and maintains compliance standards without introducing bottlenecks into developer release velocity.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/devsecops-automating-sboms</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/devsecops-automating-sboms.jpg" length="118066" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/devsecops-automating-sboms.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Automating Bare-Metal Provisioning with Canonical MAAS]]></title>
      <link>https://anasrhimi.tech/blog/bare-metal-provisioning-maas</link>
      <description><![CDATA[Turn physical servers into elastic cloud resources with Canonical MAAS automated bare-metal provisioning.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/bare-metal-provisioning-maas.jpg" alt="Automating Bare-Metal Provisioning with Canonical MAAS"></figure>
<p>Provisioning physical hardware typically requires mounting ISOs, navigating installation wizards, and manually setting up networks. Canonical MAAS (Metal as a Service) automates this entire process, enabling you to discover, commission, and deploy bare-metal servers using PXE, IPMI/Redfish, and cloud-init.</p>
<h2>Deploying Region & Rack Controllers</h2>
<p>MAAS is built on two primary components: a Region Controller (which houses the API and PostgreSQL database) and Rack Controllers (handling DHCP, TFTP/PXE, and BMC power management). Both can be installed on a central management host using snap packages.</p>
<pre><code class="language-bash"># Install MAAS via snap
sudo snap install maas
<p># Initialize the Region and Rack controllers on the same node
sudo maas init --mode all</p>
<p># Create the initial administrative user
sudo maas createadmin \
    --username=admin \
    --password=securepassword \
    --email=admin@homelab.local \
    --ssh-import=gh:yourgithubhandle</code></pre></p>
<p><strong>Note:</strong> Installing through snap ensures MAAS dependencies remain isolated. The <code>--ssh-import</code> flag automatically fetches your public SSH keys from GitHub and configures them for the <code>ubuntu</code> user on every machine provisioned by MAAS.</p>
<h2>Configuring the Fabric and DHCP</h2>
<p>To enable PXE booting for servers, MAAS requires control over DHCP on your provisioning subnet (referred to as a "Fabric" in MAAS). You can activate and configure DHCP directly through the CLI.</p>
<pre><code class="language-bash"># Authenticate the CLI session
maas login admin http://localhost:5240/MAAS/api/2.0/
<p># Identify the VLAN ID for our provisioning subnet (e.g., 10.0.10.0/24)
maas admin subnets read
# Assume VLAN ID is 0</p>
<p># Enable DHCP on the untagged VLAN, setting IP ranges
maas admin vlan update 0 0 \
    dhcp_on=True \
    primary_rack=node-hostname</p>
<p># Define the dynamic IP range for PXE booting (enlistment/commissioning)
maas admin ipranges create \
    type=dynamic \
    start_ip=10.0.10.100 end_ip=10.0.10.200 \
    subnet=10.0.10.0/24</code></pre></p>
<p><strong>Note:</strong> MAAS manages IP addresses in two distinct phases. Dynamic ranges are dedicated to temporary PXE boot environments during initial hardware discovery (enlistment) and validation (commissioning). Fully deployed machines are assigned static IPs outside of this dynamic pool.</p>
<h2>Automated Node Provisioning via Cloud-Init</h2>
<p>Once nodes successfully complete enlistment (PXE discovery) and commissioning (hardware auditing), they transition to a 'Ready' status. You can subsequently reserve a server that meets your criteria and deploy an operating system using a custom cloud-init script for post-installation setup.</p>
<pre><code class="language-yaml"># cloud-init.yaml
#cloud-config
packages:
  - docker.io
  - fail2ban
runcmd:
  - systemctl enable --now docker
  - usermod -aG docker ubuntu</code></pre>
<pre><code class="language-bash"># Allocate a node with specific constraints (e.g., min 8 cores, 32GB RAM)
NODE_ID=$(maas admin machines allocate cores=8 mem=32768 | jq -r '.system_id')
<p># Deploy Ubuntu 22.04 with our custom cloud-init script
maas admin machine deploy $NODE_ID \
    osystem=ubuntu \
    distro_series=jammy \
    user_data=$(base64 -w0 cloud-init.yaml)</code></pre></p>
<p><strong>Note:</strong> The <code>allocate</code> command reserves a server matching your specified resource constraints (8 CPU cores and 32GB of RAM in this scenario). The <code>deploy</code> command leverages IPMI or Redfish to power up the machine, PXE boot the Ubuntu Jammy image, write the operating system to disk, run the base64-encoded cloud-init configuration upon first boot, and reboot the system.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/bare-metal-provisioning-maas</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/bare-metal-provisioning-maas.jpg" length="108846" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/bare-metal-provisioning-maas.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Using AI Agents for Predictive Kubernetes Auto-Scaling]]></title>
      <link>https://anasrhimi.tech/blog/ai-agents-kubernetes-auto-scaling</link>
      <description><![CDATA[Explore how to transition from reactive Kubernetes HPA to predictive auto-scaling by deploying AI agents that analyze real-time telemetry and mutate cluster state.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/ai-agents-kubernetes-auto-scaling.jpg" alt="Using AI Agents for Predictive Kubernetes Auto-Scaling"></figure>
<h2>The Limitations of Reactive HPA</h2>
<p>Standard Kubernetes Horizontal Pod Autoscalers (HPA) operate reactively. By the time CPU usage spikes and triggers a scale-out event, users are already experiencing latency increases or timeouts while new pods are spinning up.</p>
<p>Predictive scaling solves this issue. By deploying an agent that evaluates Prometheus metrics alongside external context such as planned marketing campaigns or recurring traffic patterns you can adjust replica counts before load reaches the cluster.</p>
<h2>Implementing an AI-Driven Operator</h2>
<p>Rather than modifying native HPA, we build a custom Python controller using <code>client-python</code>. The agent processes incoming telemetry, calculates required replicas, and patches the deployment's scale subresource directly.</p>
<pre><code class="language-python">import os
from kubernetes import client, config
from agent_logic import determine_optimal_replicas # Custom LLM/ML prediction logic
<p>def scale_deployment(namespace: str, deployment_name: str, current_metrics: dict):
 # Step 1: Authenticate using the in-cluster ServiceAccount token
 config.load_incluster_config()
 apps_v1 = client.AppsV1Api()</p>
<p># Step 2: Evaluate metrics and calculate target replicas
 # current_metrics payload, e.g. { "cpu_trend": "rising_fast", "queue_depth": 5400 }
 predicted_replicas = determine_optimal_replicas(current_metrics)</p>
<p># Step 3: Check current replica count to avoid unnecessary API calls
 scale = apps_v1.read_namespaced_deployment_scale(name=deployment_name, namespace=namespace)
 current_replicas = scale.spec.replicas</p>
<p>if predicted_replicas == current_replicas:
  print("Cluster is at optimal capacity. No scaling action required.")
  return</p>
<p># Step 4: Patch the scale subresource
 body = {"spec": {"replicas": predicted_replicas}}
 try:
  apps_v1.patch_namespaced_deployment_scale(
  name=deployment_name,
  namespace=namespace,
  body=body
  )
  print(f"Agent scaled {deployment_name} from {current_replicas} to {predicted_replicas}")
 except client.exceptions.ApiException as e:
  print(f"Scaling failed: {e}")</code></pre></p>
<h2>How the Code Works</h2>
<p>1. <strong>In-Cluster Authentication (<code>load_incluster_config</code>)</strong>: The script executes inside a pod using its assigned <code>ServiceAccount</code>. Ensure that your <code>RoleBinding</code> grants <code>patch</code> permissions on <code>deployments/scale</code> resources.
2. <strong>Prediction Logic (<code>determine_optimal_replicas</code>)</strong>: Pass traffic velocity indicators (such as queue depth growth rates) instead of static usage snapshots so the model can project upcoming capacity requirements.
3. <strong>Idempotency Guard</strong>: Retrieving current deployment scale prior to updating prevents reconciliation loops and avoids hitting API server rate limits.
4. <strong>Subresource Patching</strong>: Patching <code>deployments/scale</code> rather than the entire spec avoids overwriting concurrent updates (such as image tag updates from CI/CD pipelines).</p>
<p>Combining metric prediction with Kubernetes scale subresource patching enables you to handle traffic spikes smoothly instead of reacting after performance degrades.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/ai-agents-kubernetes-auto-scaling</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/ai-agents-kubernetes-auto-scaling.jpg" length="89087" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/ai-agents-kubernetes-auto-scaling.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Enforcing Kubernetes Security Policies with OPA Gatekeeper]]></title>
      <link>https://anasrhimi.tech/blog/opa-gatekeeper-kubernetes</link>
      <description><![CDATA[Learn how to enforce Kubernetes security policies at scale using OPA Gatekeeper.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/opa-gatekeeper-kubernetes.jpg" alt="Enforcing Kubernetes Security Policies with OPA Gatekeeper"></figure>
<p>By default, Kubernetes enforces very few security restrictions. Deploying a pod configured with <code>privileged: true</code> grants host-level permissions to the container process, effectively bypassing container isolation and increasing the risk of node compromise. To prevent insecure configurations from reaching production clusters, platform engineering teams leverage Open Policy Agent (OPA) Gatekeeper to enforce Policy as Code at the admission control layer.</p>
<h2>The Risk of Privileged Containers</h2>
<p>Running containers in privileged mode exposes host kernel capabilities directly to the container runtime. Should an application running inside a privileged container be compromised, an attacker could breach the container boundary, access host filesystems, or move laterally across the entire cluster network. Consequently, blocking privileged containers is a foundational security baseline for production environments.</p>
<h2>The Solution: Gatekeeper ConstraintTemplate</h2>
<p>Gatekeeper relies on two core custom resources to enforce operational policies: a <code>ConstraintTemplate</code>, which defines the underlying policy logic in Rego, and a <code>Constraint</code>, which applies that policy logic to specific cluster resources or namespaces.</p>
<p>Below is an example of a <code>ConstraintTemplate</code> configured to reject any pod manifest containing privileged containers:</p>
<pre><code class="language-yaml">apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sdisallowprivileged
spec:
  crd:
    spec:
      names:
        kind: K8sDisallowPrivileged
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sdisallowprivileged
<p>violation[{"msg": msg}] {
          c := input.review.object.spec.containers[_]
          c.securityContext.privileged
          msg := sprintf("Privileged container is not allowed: %v, securityContext: %v", [c.name, c.securityContext])
        }</code></pre></p>
<h2>How the Rego Policy Works</h2>
<p>Here is a step-by-step overview of how Gatekeeper processes admission review requests sent by the Kubernetes API server:</p>
<ul>
<li><strong><code>package k8sdisallowprivileged</code></strong>: Defines the namespace scope for this Rego rule, preventing naming conflicts with other cluster policies.</li>
<li><strong><code>violation[{"msg": msg}] { ... }</code></strong>: Declares the violation evaluation block. If all expressions within this block evaluate to true, Gatekeeper registers a policy violation and denies the admission request.</li>
<li><strong><code>c := input.review.object.spec.containers[_]</code></strong>: Iterates over each container in the <code>spec.containers</code> array within the evaluated pod manifest (<code>input.review.object</code>). The <code>_</code> symbol serves as an implicit loop iterator.</li>
<li><strong><code>c.securityContext.privileged</code></strong>: Evaluates whether <code>privileged</code> is explicitly enabled (<code>true</code>) on the current container (<code>c</code>). If <code>privileged</code> is <code>false</code> or absent, the evaluation condition fails for that item and iteration proceeds.</li>
<li><strong><code>msg := sprintf(...)</code></strong>: Generates a formatted failure message sent back to the user or CI/CD pipeline when a deployment is rejected, specifying the non-compliant container name and security context configuration.</li>
</ul>
<h2>Applying the Constraint</h2>
<p>Deploying the <code>ConstraintTemplate</code> alone does not actively restrict workloads; it only registers the custom resource definition (<code>K8sDisallowPrivileged</code>) and stores the policy logic. To enforce the policy across a cluster, platform engineers must deploy a matching <code>K8sDisallowPrivileged</code> custom resource targeting specific namespaces or resources. Decoupling policy definitions from policy enforcement allows teams to define governance rules once and roll them out flexibly across environments.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/opa-gatekeeper-kubernetes</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/opa-gatekeeper-kubernetes.jpg" length="95170" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/opa-gatekeeper-kubernetes.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Building Lightning-Fast DevOps CLI Tools in Rust]]></title>
      <link>https://anasrhimi.tech/blog/rust-cli-tools-devops</link>
      <description><![CDATA[Replace slow automation scripts with highly concurrent, single-binary Rust CLI tools using Tokio and reqwest.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/rust-cli-tools-devops.jpg" alt="Building Lightning-Fast DevOps CLI Tools in Rust"></figure>
<p>Python and Bash perform well for standard automation, but they hit limitations when scaling infrastructure tasks. Attempting to query thousands of endpoints, parse multi-gigabyte log files, or execute concurrent API calls rapidly exposes the overhead of shell subshells or Python's Global Interpreter Lock (GIL).</p>
<p>Rust is an ideal fit for these workloads. It compiles to a single, static binary with no external runtime dependencies, and handles async I/O efficiently out of the box.</p>
<h2>The Problem: Concurrent Health Checks at Scale</h2>
<p>Consider a scenario where a CLI utility must check <code>/health</code> endpoints across 1,000 internal microservices. Sequential HTTP calls in Bash or Python can take several minutes. With Tokio and <code>reqwest</code>, Rust can process these concurrently in seconds.</p>
<h2>Implementation</h2>
<p>Here is how to structure a health-checker CLI using Tokio's <code>JoinSet</code> to manage task concurrency:</p>
<pre><code class="language-rust">use reqwest::Client;
use std::time::Duration;
use tokio::task::JoinSet;
<p>// Custom result type wrapper for clean error propagation
type Result&lt;T&gt; = std::result::Result&lt;T, Box&lt;dyn std::error::Error + Send + Sync&gt;&gt;;</p>
<p>#[tokio::main]
async fn main() -&gt; Result&lt;()&gt; {
    let endpoints = vec![
        "http://service-a.internal/health",
        "http://service-b.internal/health",
        // ... extend to hundreds or thousands of endpoints
    ];</p>
<p>// Reuse a single client with connection pooling
    let client = Client::builder()
        .timeout(Duration::from_secs(5))
        .pool_idle_timeout(Duration::from_secs(15))
        .build()?;</p>
<p>let mut set = JoinSet::new();</p>
<p>for url in endpoints {
        let client_clone = client.clone(); // Clones an internal Arc pointer</p>
<p>set.spawn(async move {
            let resp = client_clone.get(url).send().await;
            match resp {
                Ok(r) if r.status().is_success() =&gt; println!("✅ {} is UP", url),
                Ok(r) =&gt; println!("⚠️ {} returned {}", url, r.status()),
                Err(e) =&gt; println!("❌ {} FAILED: {}", url, e),
            }
        });
    }</p>
<p>// Collect results as tasks complete
    while let Some(res) = set.join_next().await {
        if let Err(e) = res {
            eprintln!("Task panicked: {:?}", e);
        }
    }</p>
<p>Ok(())
}</code></pre></p>
<h2>Key Architectural Details</h2>
<ul>
<li><strong><code>#[tokio::main]</code></strong>: Configures the multi-threaded Tokio runtime, initializing the async executor behind the scenes.</li>
<li><strong><code>Client::builder()</code></strong>: Constructing <code>reqwest::Client</code> outside the loop ensures connection reuse across requests. <code>Client</code> internally wraps state in an <code>Arc</code>.</li>
<li><strong><code>client.clone()</code></strong>: Efficiently duplicates the <code>Arc</code> handle so each spawned task receives its own client reference without copying underlying connection state.</li>
<li><strong><code>JoinSet</code></strong>: Tokio's collection for managing concurrent tasks. Unlike pushing join handles into a raw <code>Vec</code>, <code>JoinSet</code> simplifies iteration as tasks complete and automatically aborts remaining tasks if dropped.</li>
<li><strong><code>set.spawn(async move { ... })</code></strong>: Tasks execute as lightweight green threads multiplexed across Tokio worker threads. When <code>.await</code> yields on network I/O, worker threads pick up other ready tasks instead of blocking.</li>
</ul>
<h2>Production Benefits</h2>
<p>The compiled binary is self-contained and typically only a few megabytes. It executes without requiring a Python runtime or third-party dependencies installed on target host environments, and total execution time is constrained by network latency rather than interpreter overhead.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/rust-cli-tools-devops</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/rust-cli-tools-devops.jpg" length="73410" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/rust-cli-tools-devops.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Fine-Tuning Open-Source LLMs on Private Enterprise Codebases]]></title>
      <link>https://anasrhimi.tech/blog/fine-tuning-llms-private-codebases</link>
      <description><![CDATA[A technical guide on using QLoRA to parameter-efficiently fine-tune open-source LLMs like Llama-3 on private enterprise codebases for enhanced security and performance.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/fine-tuning-llms-private-codebases.jpg" alt="Fine-Tuning Open-Source LLMs on Private Enterprise Codebases"></figure>
<h2>Privacy vs. Performance</h2>
<p>Off-the-shelf LLMs generate acceptable generic code, but they often struggle with proprietary frameworks, internal design patterns, and custom microservices. RAG helps introduce context into prompts, but it does not modify how a model naturally generates syntax. Fine-tuning an open-source model such as Llama 3 or Mistral on your codebase resolves this issue, though traditional full-parameter fine-tuning requires substantial VRAM. Quantized Low-Rank Adaptation (QLoRA) makes this practical using Parameter-Efficient Fine-Tuning (PEFT).</p>
<h2>Training Setup with QLoRA</h2>
<p>Here is a Python script utilizing Hugging Face's <code>transformers</code>, <code>trl</code>, and <code>peft</code> libraries. It quantizes the base model to 4-bit precision, enabling training on a single GPU (such as an NVIDIA A10G or L4) while fine-tuning adapter weights on your internal codebase.</p>
<pre><code class="language-python">import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer
<p># Line 1: Configure 4-bit quantization to drastically reduce memory footprint
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16
)</p>
<p># Line 2: Load the base model with the quantization configuration
model_id = "meta-llama/Meta-Llama-3-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb_config, device_map="auto")
model = prepare_model_for_kbit_training(model)</p>
<p># Line 3: Define the LoRA configuration targeting attention modules
peft_config = LoraConfig(
    r=16, 
    lora_alpha=32, 
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)
model = get_peft_model(model, peft_config)</p>
<p># Line 4: Initialize the Supervised Fine-Tuning (SFT) Trainer
trainer = SFTTrainer(
    model=model,
    train_dataset=internal_codebase_dataset, # Pre-formatted Dataset object
    dataset_text_field="text",
    max_seq_length=2048,
    args=TrainingArguments(
        per_device_train_batch_size=4,
        gradient_accumulation_steps=4,
        warmup_steps=100,
        learning_rate=2e-4,
        fp16=True,
        output_dir="outputs/enterprise-coder-v1"
    ),
    peft_config=peft_config,
)</p>
<p># Line 5: Execute training and save the adapter weights
trainer.train()
trainer.model.save_pretrained("enterprise-coder-v1-adapter")</code></pre></p>
<h2>How the Configuration Works</h2>
<ul>
<li><strong>Line 1:</strong> <code>bnb_4bit_quant_type="nf4"</code> (Normal Float 4) performs effectively for normally distributed LLM weights. Combined with a <code>bfloat16</code> compute dtype, weights are stored in 4-bit format while matrix multiplications in the forward and backward passes execute in 16-bit to maintain gradient accuracy.</li>
<li><strong>Line 2:</strong> <code>prepare_model_for_kbit_training</code> converts layer norms and the final LM head to <code>fp32</code>. This avoids loss spikes and numerical instability when fine-tuning quantized models.</li>
<li><strong>Line 3:</strong> The LoRA setup targets the query, key, value, and output projection matrices (<code>q_proj</code>, <code>k_proj</code>, <code>v_proj</code>, <code>o_proj</code>). Focusing on attention modules instead of MLP layers balances training speed and syntax adaptation without causing catastrophic forgetting.</li>
<li><strong>Lines 4 & 5:</strong> Gradient accumulation (<code>per_device_train_batch_size=4</code> and <code>gradient_accumulation_steps=4</code>) creates an effective batch size of 16 to reduce VRAM consumption. Saving only the adapter weights produces a compact output (~100MB) rather than saving the full base model again.</li>
</ul>
<p>Utilizing QLoRA allows engineering teams to adapt open-source models to private codebases entirely behind a firewall, eliminating privacy risks while gaining domain-specific coding assistance.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/fine-tuning-llms-private-codebases</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/fine-tuning-llms-private-codebases.jpg" length="86083" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/fine-tuning-llms-private-codebases.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Provisioning the NVIDIA GPU Operator for K8s AI Workloads]]></title>
      <link>https://anasrhimi.tech/blog/gpu-operator-kubernetes-ai</link>
      <description><![CDATA[Automate NVIDIA hardware provisioning and driver management in Kubernetes for deep learning with the GPU Operator.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/gpu-operator-kubernetes-ai.jpg" alt="Provisioning the NVIDIA GPU Operator for K8s AI Workloads"></figure>
<p>Running AI and machine learning workloads on Kubernetes requires exposing host GPUs to pods. Manually installing drivers across every node is inefficient and error-prone. The NVIDIA GPU Operator automates this process by managing drivers, container runtimes, and monitoring tools directly within the cluster as containerized components.</p>
<h2>Prerequisites: Node Preparation</h2>
<p>Worker nodes require minimal host setup. Use a supported Linux distribution (such as Ubuntu 22.04) with <code>containerd</code> configured as the container runtime, and ensure no existing NVIDIA drivers are installed on the host.</p>
<pre><code class="language-bash"># Purge any existing NVIDIA packages to avoid conflicts
sudo apt-get purge -y nvidia<em> libnvidia</em>
sudo apt-get autoremove -y
<p># Verify Nouveau (open-source driver) is loaded temporarily
lsmod | grep nouveau</code></pre></p>
<p><strong>Note:</strong> The GPU Operator deploys the proprietary NVIDIA driver as a containerized DaemonSet. Any existing host-level NVIDIA drivers will conflict with these containers and cause pod initialization failures.</p>
<h2>Deploying Node Feature Discovery (NFD)</h2>
<p>The GPU Operator relies on Node Feature Discovery (NFD) to identify GPU-equipped nodes and label them automatically, ensuring workloads and operator components target the appropriate hardware.</p>
<pre><code class="language-bash"># Add the NVIDIA Helm repository
helm repo add nfd https://kubernetes-sigs.github.io/node-feature-discovery/charts
helm repo update
<p># Install NFD
helm upgrade --install nfd nfd/node-feature-discovery \
 --namespace node-feature-discovery \
 --create-namespace \
 --set worker.tolerations[0].key="node-role.kubernetes.io/master" \
 --set worker.tolerations[0].operator="Exists" \
 --set worker.tolerations[0].effect="NoSchedule"</code></pre></p>
<p><strong>Note:</strong> Tolerations allow NFD workers to run across all nodes, including control plane nodes, to detect hardware capabilities. Once deployed, verify that GPU nodes receive the label <code>feature.node.kubernetes.io/pci-10de.present=true</code> (<code>10de</code> is NVIDIA's PCI vendor ID).</p>
<h2>Installing the GPU Operator</h2>
<p>After labeling GPU nodes, install the GPU Operator using Helm. It orchestrates the Driver, Container Toolkit, Device Plugin, DCGM Exporter, and MIG Manager.</p>
<pre><code class="language-bash">helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update
<p>helm upgrade --install gpu-operator nvidia/gpu-operator \
 -n gpu-operator --create-namespace \
 --set driver.enabled=true \
 --set toolkit.enabled=true \
 --set devicePlugin.enabled=true \
 --set dcgmExporter.enabled=true \
 --set toolkit.env[0].name=CONTAINERD_CONFIG \
 --set toolkit.env[0].value=/etc/containerd/config.toml \
 --set toolkit.env[1].name=CONTAINERD_SOCKET \
 --set toolkit.env[1].value=/run/containerd/containerd.sock</code></pre></p>
<p><strong>Note:</strong> The <code>toolkit.env</code> parameters instruct the operator where to locate containerd's configuration file and socket. This enables it to inject the <code>nvidia-container-runtime</code> into containerd and restart the service automatically.</p>
<h2>Validating the Deployment</h2>
<p>Verify GPU scheduling by deploying a sample CUDA vector addition pod.</p>
<pre><code class="language-bash">cat &lt;&lt;EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
 name: cuda-vector-add
spec:
 restartPolicy: OnFailure
 containers:
 - name: cuda-vector-add
 image: "nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda11.7.1-ubuntu20.04"
 resources:
  limits:
  nvidia.com/gpu: 1
EOF</code></pre>
<p><strong>Note:</strong> When requested (<code>nvidia.com/gpu: 1</code>), the Kubernetes scheduler assigns the pod to a GPU node using the NVIDIA Device Plugin. Operate <code>kubectl logs cuda-vector-add</code> to verify the output receiving <code>Test PASSED</code> confirms proper driver and runtime configuration.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/gpu-operator-kubernetes-ai</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/gpu-operator-kubernetes-ai.jpg" length="89309" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/gpu-operator-kubernetes-ai.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Mastering Remote Work as a DevOps Freelancer in 2026]]></title>
      <link>https://anasrhimi.tech/blog/mastering-remote-work-devops-freelancer</link>
      <description><![CDATA[A complete technical walkthrough of migrating from standalone security to a centralized CrowdSec Hub-and-Spoke architecture across Proxmox LXC containers. by Anas Rhimi C, Python, TypeScript, Swift, and more.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/mastering-remote-work-devops-freelancer.jpg" alt="Mastering Remote Work as a DevOps Freelancer in 2026"></figure>
<p>DevOps engineers possess distinct leverage in today's remote marketplace. However, securing lucrative international contracts requires far more than syntactical mastery of YAML files. Success hinges on positioning your expertise around tangible business outcomes rather than exhaustive lists of technical tools.</p>
<h2>Positioning for Success</h2>
<p>Organizations rarely engage consultants simply for their familiarity with Kubernetes or Terraform. They hire specialists because production deployments fail under pressure, cloud expenditure is spiraling out of control, or internal development teams waste valuable hours waiting for slow CI pipelines.</p>
<ul>
<li><strong>Focus on Business Outcomes over Technical Syntax:</strong> Avoid marketing yourself merely as a "Terraform Specialist." Instead, frame your value as an engineer who "automates infrastructure to reduce deployment cycles by 80% and eliminate configuration drift."</li>
<li><strong>Develop a Tangible Proof-of-Work Portfolio:</strong> A public GitHub repository showcasing modular Ansible playbooks, production-grade Docker Compose configurations, and robust CI/CD pipelines carries significantly more weight than traditional resume bullet points.</li>
<li><strong>Master Asynchronous and Direct Communication:</strong> In remote consulting, clear and proactive communication rapidly builds client confidence. Providing legible architectural diagrams, comprehensive incident post-mortems, and structured documentation immediately distinguishes top-tier consultants from ordinary freelancers.</li>
</ul>
<p>Emphasizing measurable business value and keeping client stakeholders informed transforms short-term projects into ongoing, high-value consulting retainers.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/mastering-remote-work-devops-freelancer</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/mastering-remote-work-devops-freelancer.jpg" length="87794" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/mastering-remote-work-devops-freelancer.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Scaling RAG Architecture with Milvus Vector Databases in Production]]></title>
      <link>https://anasrhimi.tech/blog/rag-architecture-vector-databases</link>
      <description><![CDATA[Master the art of scaling RAG architectures with Milvus vector databases by optimizing HNSW indexes and hybrid search parameters for production-grade throughput.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/rag-architecture-vector-databases.jpg" alt="Scaling RAG Architecture with Milvus Vector Databases in Production"></figure>
<h2>Beyond the Prototype: Vector Database Bottlenecks</h2>
<p>Building a Retrieval-Augmented Generation (RAG) prototype with a few thousand documents using an in-memory store like FAISS works fine. However, when moving to production handling tens of millions of dense embeddings, concurrent read/write queries, and requiring high availability in-memory stores launch out of RAM and fall short on throughput.</p>
<p>Scaling a RAG setup requires a distributed vector database like Milvus. The primary technical challenge is selecting the appropriate index type and tuning search parameters to achieve the optimal balance between recall accuracy and query latency.</p>
<h2>Configuring High-Performance HNSW Indexes in Milvus</h2>
<p>To scale vector search, you need a collection schema that supports metadata filtering alongside an index tuned for your target throughput. The example below uses the <code>pymilvus</code> SDK to define a collection with a Hierarchical Navigable Small World (HNSW) index and run a partitioned hybrid search.</p>
<pre><code class="language-python">from pymilvus import Collection, FieldSchema, CollectionSchema, DataType, connections
<p># Establish gRPC connection to the Milvus cluster
connections.connect("default", host="milvus-cluster.default.svc.cluster.local", port="19530")</p>
<p># Define schema with scalar and vector fields
fields = [
 FieldSchema(name="doc_id", dtype=DataType.INT64, is_primary=True, auto_id=False),
 FieldSchema(name="department", dtype=DataType.VARCHAR, max_length=100),
 FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1536) # e.g., OpenAI embeddings
]
schema = CollectionSchema(fields, description="Enterprise Knowledge Base", enable_dynamic_field=True)
collection = Collection("enterprise_rag", schema)</p>
<p># Configure the HNSW Index
index_params = {
 "metric_type": "COSINE",
 "index_type": "HNSW",
 "params": {"M": 16, "efConstruction": 200}
}
collection.create_index(field_name="embedding", index_params=index_params)
collection.load() # Load collection into memory for querying</p>
<p># Run a hybrid search with runtime EF tuning
search_params = {"metric_type": "COSINE", "params": {"ef": 64}}
results = collection.search(
 data=[query_vector],
 anns_field="embedding",
 param=search_params,
 limit=5,
 expr="department == 'engineering'", # Scalar pre-filtering
 output_fields=["doc_id", "department"]
)</code></pre></p>
<h2>Architectural Tuning Breakdown</h2>
<ul>
<li><strong>Schema Setup & Connection</strong>: We connect to the cluster and define a schema that includes a scalar field (<code>department</code>). Pure vector search often returns semantically close matches that fail business constraints. Adding scalar metadata allows you to filter vectors by exact category matches.</li>
<li><strong>HNSW Index Configuration</strong>: We choose HNSW over standard IVF (Inverted File). The parameter <code>M=16</code> sets the max number of bi-directional links per node. A higher <code>M</code> improves recall, but increases memory overhead. <code>efConstruction=200</code> controls search depth while building the index; setting this higher slows down ingestion but produces a graph structure optimized for fast reads.</li>
<li><strong>Hybrid Search & Query Tuning</strong>: When querying, we filter using <code>expr="department == 'engineering'"</code>. Milvus evaluates scalar filters via bitsets before traversing the graph, limiting vector evaluation strictly to engineering documents. At runtime, <code>ef=64</code> sets the graph search depth, allowing you to dial in the exact trade-off between recall accuracy and latency.</li>
</ul>
<p>Properly tuning HNSW parameters and pre-filtering with scalar metadata keeps query latencies low and predictable, even as knowledge bases expand to millions of vectors.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/rag-architecture-vector-databases</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/rag-architecture-vector-databases.jpg" length="91412" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/rag-architecture-vector-databases.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Implementing Micro-Segmentation in K8s using Cilium Network Policies]]></title>
      <link>https://anasrhimi.tech/blog/network-policies-cilium</link>
      <description><![CDATA[Implement robust micro-segmentation in Kubernetes environments utilizing Cilium and eBPF.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/network-policies-cilium.jpg" alt="Implementing Micro-Segmentation in K8s using Cilium Network Policies"></figure>
<p>By default, flat networks in Kubernetes allow any pod to talk to any other pod. If a public-facing frontend gets compromised, an attacker can move laterally to reach your backend databases.</p>
<p>Standard <code>iptables</code>-based network policies struggle at scale because frequent pod creation and churn force constant kernel rule rewrites. Cilium avoids this by using eBPF (Extended Berkeley Packet Filter) to process network filtering directly in the Linux kernel with minimal overhead.</p>
<h2>Zero-Trust Isolation for Workloads</h2>
<p>A zero-trust model requires blocking all inter-pod traffic by default and explicitly permitting access only where required.</p>
<p>In the manifest below, a <code>CiliumNetworkPolicy</code> (CNP) isolates a PostgreSQL database so it only accepts TCP connections on port 5432 from pods labeled <code>app: backend-api</code>.</p>
<pre><code class="language-yaml">apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "db-micro-segmentation"
  namespace: "production"
spec:
  endpointSelector:
    matchLabels:
      app: postgres-db
      tier: storage
  ingress:
  - fromEndpoints:
    - matchLabels:
        app: backend-api
        tier: application
    toPorts:
    - ports:
      - port: "5432"
        protocol: TCP</code></pre>
<h2>How Cilium Enforces Policy with eBPF</h2>
<p>Instead of filtering by dynamic IP addresses or managing iptables chains, Cilium attaches eBPF programs to the virtual ethernet (<code>veth</code>) interfaces of target pods:</p>
<ul>
<li><strong><code>kind: CiliumNetworkPolicy</code></strong>: Standard Kubernetes <code>NetworkPolicy</code> resources offer basic L3/L4 rules. <code>CiliumNetworkPolicy</code> extends this with features like DNS-based rules and L7 protocol filtering (HTTP, gRPC, Kafka), though this configuration focuses on L4 port restriction.</li>
<li><strong><code>endpointSelector</code></strong>: Node-level Cilium agents assign numeric security identities to pods matching <code>app: postgres-db</code> and <code>tier: storage</code>. The policy rules map directly to the eBPF bytecode attached to those pods' network interfaces.</li>
<li><strong><code>ingress.fromEndpoints</code></strong>: Specifies allowed source workloads by label (<code>app: backend-api</code>, <code>tier: application</code>). Because Cilium evaluates traffic using numeric security IDs rather than IP addresses, pod restarts and IP reassignments don't trigger constant network rule updates.</li>
<li><strong><code>toPorts</code></strong>: Restricts incoming connections strictly to TCP port 5432. If a backend API container is breached, the attacker cannot probe administrative ports (such as SSH on port 22) or unexposed services on the database pod.</li>
</ul>
<p>Adopting a default-deny posture with explicit CNP rules keeps network boundaries tight and limits lateral movement across your cluster.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/network-policies-cilium</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/network-policies-cilium.jpg" length="100960" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/network-policies-cilium.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[FinOps for the Modern DevOps Engineer: Controlling Cloud Costs]]></title>
      <link>https://anasrhimi.tech/blog/finops-controlling-cloud-costs</link>
      <description><![CDATA[A complete technical walkthrough of migrating from standalone security to a centralized CrowdSec Hub-and-Spoke architecture across Proxmox LXC containers. by Anas Rhimi C, Python, TypeScript, Swift, and more.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/finops-controlling-cloud-costs.jpg" alt="FinOps for the Modern DevOps Engineer: Controlling Cloud Costs"></figure>
<p>Cloud providers make provisioning infrastructure dangerously seamless and forgetting about active resources even easier. When the monthly AWS bill reaches five or six figures, Finance does not question product managers; they turn directly to DevOps.</p>
<h2>Shift-Left Cost Management</h2>
<p>FinOps is not merely about analyzing monthly invoices after expenses are incurred. It involves integrating financial accountability directly into programmer workflows before resources are ever provisioned.</p>
<ul>
<li><strong>Infracost in CI/CD</strong>: Integrate tools such as Infracost into your pull request pipelines. When an engineer submits a PR adding three new EC2 instances, Infracost calculates the cost variance and posts an automated comment detailing the projected monthly increase directly on the PR.</li>
<li><strong>Enforce Tagging via IaC</strong>: Utilize Terraform policies or module checks to enforce required resource tags (<code>CostCenter</code>, <code>Owner</code>, <code>Environment</code>). If a pull request attempts to deploy untagged infrastructure, the pipeline immediately fails.</li>
<li><strong>Scheduled Environment Off-Hours</strong>: Implement automated schedules to pause or decommission non-production environments outside standard business hours. Shutting down staging and development environments over weekends significantly reduces idle compute costs.</li>
</ul>
<p>Presenting cost estimates to engineers before code is merged into main prevents unexpected bills prior to accumulation.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/finops-controlling-cloud-costs</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/finops-controlling-cloud-costs.jpg" length="94980" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/finops-controlling-cloud-costs.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Writing Custom Prometheus Exporters in Python for Microservices]]></title>
      <link>https://anasrhimi.tech/blog/prometheus-custom-exporters-python</link>
      <description><![CDATA[A deep dive into creating robust, custom Prometheus exporters using Python. Learn to instrument legacy systems and expose critical domain-specific metrics efficiently.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/prometheus-custom-exporters-python.jpg" alt="Writing Custom Prometheus Exporters in Python for Microservices"></figure>
<h2>Beyond Standard Metrics: The Need for Custom Exporters</h2>
<p>Standard exporters like Node Exporter and cAdvisor provide comprehensive architecture-level observability, capturing CPU usage, memory consumption, and container statistics. However, when application-specific telemetry is required such as tracking pending job queues in legacy databases, monitoring third-party API queue depths, or tracking domain-specific business indicators pre-built exporters are insufficient. Under these circumstances, developing a custom Python exporter offers a straightforward and flexible solution.</p>
<h2>The Custom Collector Pattern</h2>
<p>A frequent architectural anti-pattern when developing custom exporters involves mutating global <code>Gauge</code> or <code>Counter</code> instances inside background worker loops or web request handlers. If an underlying monitored dependency is decommissioned, persistent global metric instances can continue exporting stale, invalid data indefinitely.</p>
<p>To eliminate stale metric exports, official Prometheus client libraries provide support for custom collector classes. Rather than persisting metric state continuously, custom collectors compute and emit metric values dynamically on demand whenever Prometheus scrapes the <code>/metrics</code> endpoint.</p>
<p>Below is an implementation of a custom collector pattern written in Python:</p>
<pre><code class="language-python">import time
import random
from prometheus_client import start_http_server
from prometheus_client.core import GaugeMetricFamily, REGISTRY
<p>class PaymentGatewayCollector(object):
 def __init__(self, endpoint):
  self._endpoint = endpoint</p>
<p>def collect(self):
  # 1. Define the metric family dynamically for each scrape
  metric = GaugeMetricFamily(
  'payment_gateway_queue_size',
  'Number of pending transactions in the gateway queue',
  labels=['gateway_provider']
  )</p>
<p># 2. Fetch live data (simulated here with API or DB queries)
  stripe_queue = self._fetch_queue_size('stripe')
  paypal_queue = self._fetch_queue_size('paypal')</p>
<p># 3. Attach metric values and labels
  metric.add_metric(['stripe'], stripe_queue)
  metric.add_metric(['paypal'], paypal_queue)</p>
<p># 4. Yield the populated metric family
  yield metric</p>
<p>def _fetch_queue_size(self, provider):
  # Simulate API latency
  time.sleep(0.1) 
  return random.randint(0, 100)</p>
<p>if __name__ == '__main__':
 # Register the custom collector instance
 REGISTRY.register(PaymentGatewayCollector(endpoint="api.payments.internal"))</p>
<p># Start HTTP server to serve /metrics
 start_http_server(8000)
 print("Prometheus exporter running on port 8000...")</p>
<p># Keep main thread alive
 while True:
  time.sleep(1)</code></pre></p>
<h2>How It Works</h2>
<p>Each time Prometheus issues an HTTP GET request to <code>http://localhost:8000/metrics</code>, the custom collector's <code>collect()</code> method executes automatically. The <code>GaugeMetricFamily</code> object constructs the metric response payload on the fly. Because telemetry data is gathered at scrape time, stale gauge values are completely avoided if an underlying metric target stops returning data or is removed, the collector simply omits that metric series, ensuring Grafana dashboards and alerting rules reflect true setup state.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/prometheus-custom-exporters-python</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/prometheus-custom-exporters-python.jpg" length="93190" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/prometheus-custom-exporters-python.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[A DevOps Guide to Debugging C Memory Leaks with Valgrind]]></title>
      <link>https://anasrhimi.tech/blog/debugging-memory-leaks-c-valgrind</link>
      <description><![CDATA[Learn how to track down elusive memory leaks in C daemons using Valgrind's Memcheck.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/debugging-memory-leaks-c-valgrind.jpg" alt="A DevOps Guide to Debugging C Memory Leaks with Valgrind"></figure>
<p>Unfreed heap memory in long-running C daemons gradually consumes system RAM until the Linux Out-Of-Memory (OOM) killer intervenes and terminates the process. While modern languages incorporate garbage collection or compile-time ownership semantics, legacy services and high-performance proxies continue to depend on explicit memory management using <code>malloc</code> and <code>free</code>.</p>
<h2>A Common Leak Pattern</h2>
<p>Consider this typical request handler implementation. It dynamically allocates a buffer on the heap but exits early during error handling without releasing the memory back to the OS:</p>
<pre><code class="language-c">#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
<p>void handle_request(const char* payload) {
    // Allocate a 1KB buffer for processing
    char<em> buffer = (char</em>)malloc(1024);
    if (!buffer) return;</p>
<p>strncpy(buffer, payload, 1023);
    buffer[1023] = '\0';</p>
<p>// Simulate an error condition
    if (strstr(buffer, "ERROR")) {
        // LEAK: Function returns without freeing buffer
        fprintf(stderr, "Error encountered\n");
        return; 
    }</p>
<p>printf("Processed: %s\n", buffer);
    free(buffer);
}</p>
<p>int main() {
    handle_request("VALID_PAYLOAD");
    handle_request("ERROR_PAYLOAD"); // This call leaks memory
    return 0;
}</code></pre></p>
<h2>Detecting Leaks with Valgrind</h2>
<p>Valgrind's Memcheck utility monitors dynamic memory management by instrumenting your binary at execution time.</p>
<h3>Step-by-Step Walkthrough</h3>
<p>1. Compile the C application with debug symbols (<code>-g</code>) and disable compiler optimizations (<code>-O0</code>) to preserve precise line numbers in backtraces:</p>
<pre><code class="language-bash">gcc -g -O0 proxy.c -o proxy</code></pre>
<p>2. Execute the application binary through Valgrind with detailed leak verification enabled:</p>
<pre><code class="language-bash">valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./proxy</code></pre>
<h2>Analyzing the Valgrind Output</h2>
<p>Valgrind pinpoints the origin of the unreleased memory:</p>
<pre><code class="language-text">==12345== 1,024 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345==    at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12345==    by 0x10871C: handle_request (proxy.c:7)
==12345==    by 0x108778: main (proxy.c:26)</code></pre>
<h3>Report Breakdown</h3>
<ul>
<li><code>malloc(1024)</code>: Valgrind intercepted this dynamic allocation and captured the associated call stack.</li>
<li><code>proxy.c:7</code>: Identifies the exact source file and line number where the unreleased memory was allocated.</li>
<li><code>definitely lost</code>: Indicates the application terminated with no remaining pointers addressing this memory block.</li>
</ul>
<h2>Fixing the Leak</h2>
<p>Guarantee that all return branches release allocated resources. For complex logic, implementing a <code>goto cleanup;</code> pattern ensures clean resource management. For this compact function, invoking <code>free(buffer)</code> within the error branch fixes the leak:</p>
<pre><code class="language-c">    if (strstr(buffer, "ERROR")) {
        fprintf(stderr, "Error encountered\n");
        free(buffer); // FIXED: Prevent leak
        return; 
    }</code></pre>
<p>Incorporating automated Valgrind scans into your CI build pipeline helps intercept memory vulnerabilities before they trigger OOM incidents in production environments.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/debugging-memory-leaks-c-valgrind</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/debugging-memory-leaks-c-valgrind.jpg" length="91420" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/debugging-memory-leaks-c-valgrind.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Docker vs Podman in 2026: Which Should You Use?]]></title>
      <link>https://anasrhimi.tech/blog/docker-vs-podman-2026</link>
      <description><![CDATA[A complete technical walkthrough of migrating from standalone security to a centralized CrowdSec Hub-and-Spoke architecture across Proxmox LXC containers. by Anas Rhimi C, Python, TypeScript, Swift, and more.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/docker-vs-podman-2026.jpg" alt="Docker vs Podman in 2026: Which Should You Use?"></figure>
<p>For years, Docker served as the default standard for container management. Within modern enterprise Linux infrastructure, Podman has emerged as a leading alternative.</p>
<h2>The Case for Podman</h2>
<p>Podman addresses two primary architectural vulnerabilities inherent to traditional Docker configurations:</p>
<ul>
<li><strong>Daemonless Architecture</strong>: Docker relies on a centralized background daemon running with root privileges. If that daemon fails, every container hosted on the node crashes. Podman launches and manages containers directly via process forks, eliminating a single point of failure.</li>
<li><strong>Rootless Execution by Default</strong>: Podman executes containers under standard, unprivileged user accounts. Should a process escape a rootless container environment, it gains only unprivileged host rights rather than root administrative access.</li>
<li><strong>Systemd Integration</strong>: Podman features native integration with systemd. You can generate service unit files for your containerized workloads and manage them alongside standard Linux system services.</li>
</ul>
<p>Given that its CLI commands mirror Docker syntax enabling adoption as simple as defining <code>alias docker=podman</code> migration requires minimal effort. When container environment security and native operating system integration are top priorities, Podman represents an optimal choice.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/docker-vs-podman-2026</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/docker-vs-podman-2026.jpg" length="81447" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/docker-vs-podman-2026.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Securing Your Supply Chain with Container Image Signing (Cosign)]]></title>
      <link>https://anasrhimi.tech/blog/container-image-signing-cosign</link>
      <description><![CDATA[Protect your software supply chain by implementing container image signing with Cosign.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/container-image-signing-cosign.jpg" alt="Securing Your Supply Chain with Container Image Signing (Cosign)"></figure>
<p>Container supply chain attacks represent a major security risk today. Deploying container images without verifying their authenticity leaves clusters vulnerable to image spoofing and malicious tampering. Cosign, developed as part of the Sigstore project, allows teams to sign container images and store cryptographic signatures directly within standard OCI registries.</p>
<h2>Why Keyless Signing?</h2>
<p>Traditional PKI implementations, such as Docker Content Trust, usually present administrative overhead because they require managing offline root keys and maintaining long-term credentials. Cosign simplifies this by offering keyless signing powered by OpenID Connect (OIDC). Instead of generating and protecting permanent key pairs, your CI/CD runner requests temporary identity certificates to automatically sign images during the build process.</p>
<h2>GitHub Actions Workflow Example</h2>
<p>Below is a continuous integration workflow configuration that builds, signs, and pushes a container image utilizing GitHub's OIDC identity provider:</p>
<pre><code class="language-yaml">jobs:
  build-and-sign:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write # Required for keyless signing
    steps:
      - name: Install Cosign
        uses: sigstore/cosign-installer@v3
<ul>
<li>name: Build and Push id: docker_build uses: docker/build-push-action@v4 with: push: true tags: ghcr.io/${{ github.repository }}:${{ github.sha }}</li>
</ul>
<ul>
<li>name: Sign the image env: TAGS: ghcr.io/${{ github.repository }}:${{ github.sha }} DIGEST: ${{ steps.docker_build.outputs.digest }} run: | cosign sign --yes \ -a "repo=${{ github.repository }}" \ -a "workflow=${{ github.workflow }}" \ -a "sha=${{ github.sha }}" \ ghcr.io/${{ github.repository }}@${DIGEST}</code></pre></li>
</ul>
<h2>Workflow Breakdown</h2>
<ul>
<li><strong><code>id-token: write</code></strong>: Grants the workflow permission to request an OIDC JWT token from GitHub. Sigstore's Fulcio Certificate Authority uses this token to issue a short-lived signing certificate linked exclusively to this specific workflow run.</li>
<li><strong><code>DIGEST: ${{ steps.docker_build.outputs.digest }}</code></strong>: Retrieves the immutable SHA256 digest of the compiled image. Signing via digest instead of mutable tags (such as <code>latest</code>) eliminates potential race conditions where a tag might be reassigned between build and signature steps.</li>
<li><strong><code>cosign sign --yes</code></strong>: Executes the signing operation in non-interactive mode, making it suitable for automated CI runners.</li>
<li><strong><code>-a "repo=..."</code></strong>: Embeds custom metadata annotations into the signature payload. When enforcing security policies, you can verify that the image originated from your specific repository and pipeline rather than an arbitrary Sigstore user.</li>
<li><strong><code>ghcr.io/...@${DIGEST}</code></strong>: Stores the signature artifact directly within the target OCI registry associated with the image digest.</li>
</ul>
<p>Adopting this strategy within your CI/CD pipeline ensures that every container image deployed to your environment carries a verifiable, keyless signature.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/container-image-signing-cosign</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/container-image-signing-cosign.jpg" length="125995" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/container-image-signing-cosign.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Taming Tool Sprawl: Consolidating CI/CD, Kubernetes, and Terraform]]></title>
      <link>https://anasrhimi.tech/blog/taming-tool-sprawl-cicd-kubernetes</link>
      <description><![CDATA[A complete technical walkthrough of migrating from standalone security to a centralized CrowdSec Hub-and-Spoke architecture across Proxmox LXC containers. by Anas Rhimi C, Python, TypeScript, Swift, and more.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/taming-tool-sprawl-cicd-kubernetes.jpg" alt="Taming Tool Sprawl: Consolidating CI/CD, Kubernetes, and Terraform"></figure>
<p>Running Jenkins for legacy jobs, GitHub Actions for new services, ArgoCD for application delivery, Terraform for infrastructure, and Ansible for server configuration introduces substantial maintenance overhead. When individual teams adopt disparate stacks, context switching increases, developer onboarding slows, and diagnosing cross-tool pipeline failures becomes increasingly complex.</p>
<p>Tool sprawl rarely stems from poor planning it occurs organically as organizations adopt modern tools over time without decommissioning legacy systems. However, consolidating your engineering toolchain does not require identifying a single monolithic tool that manages every task. Instead, it involves establishing clear default standards and unifying workflows.</p>
<h2>Practical Steps to Consolidate Your Toolchain</h2>
<ul>
<li><strong>Adopt GitOps for Kubernetes</strong>: Transition state management into Git using declarative delivery tools such as ArgoCD or FluxCD. Maintaining version-controlled manifests that synchronize automatically eliminates the requirement for manual <code>kubectl</code> updates across environments.</li>
<li><strong>Build reusable Terraform modules</strong>: Rather than writing bespoke Terraform configurations for each service, maintain a centralized internal module registry. This ensures cloud infrastructure definitions remain consistent while minimizing configuration drift.</li>
<li><strong>Unify your CI/CD pipelines</strong>: Standardize pipeline definitions across a single primary platform (such as GitHub Actions). Migrating fragmented Jenkins scripts or legacy GitLab pipelines into shared reusable workflows simplifies maintenance and ensures predictable build steps across engineering teams.</li>
</ul>
<p>Reducing tool overlap minimizes context switching, enhances deployment reliability, and enables engineering teams to focus on core feature development.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/taming-tool-sprawl-cicd-kubernetes</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/taming-tool-sprawl-cicd-kubernetes.jpg" length="114674" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/taming-tool-sprawl-cicd-kubernetes.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Optimizing Multi-Architecture Docker Builds with Buildx]]></title>
      <link>https://anasrhimi.tech/blog/optimizing-docker-buildx-arm64</link>
      <description><![CDATA[Accelerate multi-architecture CI/CD pipelines using Docker Buildx, remote native ARM64 nodes, and registry caching.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/optimizing-docker-buildx-arm64.jpg" alt="Optimizing Multi-Architecture Docker Builds with Buildx"></figure>
<p>If you construct container images targeting both ARM64 (such as Apple Silicon or AWS Graviton) and x86_64 server architectures, you have likely experienced how slow <code>docker buildx</code> performs under QEMU emulation. Compiling ARM binaries on an x86 CI runner via emulation frequently turns a 2-minute build into a 20-minute bottleneck.</p>
<p>Here is how to accelerate multi-architecture builds by leveraging native build nodes, cross-compilation techniques, and remote build caching.</p>
<h2>1. Using a Remote Native ARM64 Builder</h2>
<p>While application emulation (QEMU) works out of the box, it incurs significant CPU overhead. The most performant solution is to dispatch ARM build stages directly to an ARM server over SSH while processing x86 tasks on your local CI runner.</p>
<pre><code class="language-bash"># Create a new builder instance
docker buildx create --name multi-arch-builder --use
<p># Add the local x86_64 node
docker buildx create --name multi-arch-builder \
  --append \
  --node x86_node \
  --platform linux/amd64</p>
<p># Add a remote ARM64 node over SSH
docker buildx create --name multi-arch-builder \
  --append \
  --node arm64_node \
  --platform linux/arm64 \
  ssh://ci-user@arm-builder.local</code></pre></p>
<blockquote><strong>Note:</strong> When attaching a remote SSH node, BuildKit automatically routes <code>linux/arm64</code> workloads to native ARM hardware and <code>linux/amd64</code> tasks to the local host machine. Ensure that your CI runner's SSH key is configured for passwordless authentication to the remote ARM server.</blockquote>
<h2>2. Cross-Compiling Inside the Dockerfile</h2>
<p>When dedicated ARM build hardware is unavailable, cross-compilation serves as an effective alternative. Compiled languages such as Go, Rust, and C/C++ support native cross-compilation without requiring an emulated CPU architecture.</p>
<p>You can leverage Buildx automatic platform arguments (<code>$BUILDPLATFORM</code>, <code>$TARGETOS</code>, <code>$TARGETARCH</code>) directly within your Dockerfile:</p>
<pre><code class="language-dockerfile"># Run the compiler on the host runner's native CPU architecture
FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder
<p># Automatically injected by Buildx
ARG TARGETPLATFORM
ARG TARGETOS
ARG TARGETARCH</p>
<p>WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download</p>
<p>COPY . .
# Cross-compile to the target architecture natively
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
    go build -o /api-server main.go</p>
<p># Final runtime image matches target platform
FROM --platform=$TARGETPLATFORM alpine:latest
COPY --from=builder /api-server /api-server
ENTRYPOINT ["/api-server"]</code></pre></p>
<blockquote><strong>Why this works:</strong> Specifying <code>FROM --platform=$BUILDPLATFORM</code> ensures that the compiler toolchain runs natively on the host runner's architecture. The compiler itself outputs binaries targeting <code>GOARCH=${TARGETARCH}</code>. Because the compilation process bypasses QEMU execution entirely, build speeds match those of native compilation.</blockquote>
<h2>3. Remote Registry Build Caching</h2>
<p>On ephemeral CI runners (such as GitHub Actions or GitLab CI), local Docker build caches are cleared between execution jobs. BuildKit allows you to export build cache manifests directly to your container registry so subsequent pipeline runs reuse cached layers across multiple architectures.</p>
<pre><code class="language-bash">docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --tag myrepo/api-server:latest \
  --cache-from type=registry,ref=myrepo/api-server:buildcache \
  --cache-to type=registry,ref=myrepo/api-server:buildcache,mode=max \
  --push \
  .</code></pre>
<blockquote><strong>Tip:</strong> The <code>mode=max</code> option instructs BuildKit to cache all intermediate compilation stages (including multi-stage build layers), rather than caching only final output image layers. Storing build caches within a remote registry decouples cache persistence from temporary runner storage.</blockquote>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/optimizing-docker-buildx-arm64</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/optimizing-docker-buildx-arm64.jpg" length="105142" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/optimizing-docker-buildx-arm64.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Orchestrating Zero-Downtime PostgreSQL Migrations at Scale]]></title>
      <link>https://anasrhimi.tech/blog/zero-downtime-database-migrations</link>
      <description><![CDATA[Performing database migrations on a live PostgreSQL cluster serving high-throughput traffic is one of the most high-stakes operations in backend engineering.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/zero-downtime-database-migrations.jpg" alt="Orchestrating Zero-Downtime PostgreSQL Migrations at Scale"></figure>
<p>Executing schema migrations on high-traffic production PostgreSQL databases demands careful engineering. Obtaining an exclusive table lock on heavily queried tables can freeze incoming SQL operations, exhaust application connection pools, and degrade service availability.</p>
<p>Zero-downtime database migrations follow one fundamental rule: maintain continuous backward compatibility. Both the currently active application version and the incoming release candidate must be capable of reading from and writing to the database schema throughout the deployment process.</p>
<h2>1. Adding Columns Without Blocking Writes</h2>
<p>In older PostgreSQL versions (pre-11), adding a column with a default value triggered a full table rewrite, holding an <code>ACCESS EXCLUSIVE</code> lock until completion. While modern PostgreSQL handles static default values instantly, dynamic defaults (such as <code>NOW()</code> or <code>gen_random_uuid()</code>) or populating existing rows still necessitate a phased migration plan.</p>
<p>Here is the pattern for adding and populating a new column safely:</p>
<pre><code class="language-sql">-- Step 1: Add the column as NULLable.
-- Acquires an ACCESS EXCLUSIVE lock for only a few milliseconds to update metadata.
ALTER TABLE users ADD COLUMN last_login_at TIMESTAMP WITH TIME ZONE NULL;
<p>-- Application Step:
-- Deploy application code that writes to last_login_at for new or updated records,
-- while handling NULL values gracefully on reads.</p>
<p>-- Step 2: Backfill existing rows in small batches.
-- Avoid a single UPDATE statement across millions of rows.
DO $$
DECLARE
    batch_size INT := 5000;
    max_id INT;
    current_id INT := 0;
BEGIN
    SELECT max(id) INTO max_id FROM users;
    WHILE current_id &lt; max_id LOOP
        UPDATE users
        SET last_login_at = COALESCE(last_login_at, NOW())
        WHERE id &gt; current_id AND id &lt;= current_id + batch_size;</p>
<p>current_id := current_id + batch_size;
        PERFORM pg_sleep(0.05); -- Optional throttle to limit I/O impact
    END LOOP;
END $$;</code></pre></p>
<p><strong>Key Implementation Principles:</strong>
* <strong>NULLable Column Addition:</strong> Adding a <code>NULL</code> column updates only PostgreSQL catalog metadata, completing almost instantly without table scans.
* <strong>Batched Row Updates:</strong> Iterating through primary key ranges in chunks releases row-level locks after each commit, minimizing write contention and avoiding autovacuum table bloat.
* <strong>I/O Throttling:</strong> Micro-sleep pauses between batch executions allocate sufficient I/O bandwidth for ongoing production read/write traffic and mitigate replication lag.</p>
<h2>2. Creating Indexes and Constraints Safely</h2>
<p>A standard <code>CREATE INDEX</code> statement takes a <code>SHARE</code> lock on the target table, blocking all <code>INSERT</code>, <code>UPDATE</code>, and <code>DELETE</code> operations until index construction finishes. On multi-gigabyte tables, this blocking window can extend for hours.</p>
<pre><code class="language-sql">-- Step 3: Build the index concurrently.
-- Note: Migration tools like Flyway or Liquibase must run this outside transaction blocks.
CREATE INDEX CONCURRENTLY idx_users_last_login ON users (last_login_at);
<p>-- Step 4: Enforce NOT NULL constraints in two steps.
-- 4a. Add the constraint as NOT VALID to skip scanning existing rows.
ALTER TABLE users ADD CONSTRAINT users_last_login_not_null 
    CHECK (last_login_at IS NOT NULL) NOT VALID;</p>
<p>-- 4b. Validate existing data in the background.
-- Uses a SHARE UPDATE EXCLUSIVE lock, allowing concurrent reads and writes.
ALTER TABLE users VALIDATE CONSTRAINT users_last_login_not_null;</code></pre></p>
<p><strong>Key Implementation Principles:</strong>
* <strong><code>CONCURRENTLY</code> Indexing:</strong> Directs PostgreSQL to build index structures using multiple table scans without blocking concurrent write operations. If the execution encounters an error, PostgreSQL retains an <code>INVALID</code> index state that should be dropped prior to re-execution.
* <strong><code>NOT VALID</code> Constraints:</strong> Enforces validation rules on new incoming rows immediately, requiring only a brief lock without verifying pre-existing data rows.
* <strong><code>VALIDATE CONSTRAINT</code>:</strong> Verifies historical table rows in the background to confirm constraint compliance without blocking concurrent application operations.</p>
<p>Deconstructing schema migrations into non-blocking, multi-phase steps requires additional deployment orchestration, but it provides the only dependable method for updating large-scale PostgreSQL tables without incurring downtime.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/zero-downtime-database-migrations</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/zero-downtime-database-migrations.jpg" length="94594" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/zero-downtime-database-migrations.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Building Custom LangChain Tools for CI/CD Pipeline Interrogation]]></title>
      <link>https://anasrhimi.tech/blog/langchain-custom-tools-cicd</link>
      <description><![CDATA[Discover how to build custom LangChain tools for CI/CD pipeline observability, empowering AI agents to debug GitHub Actions failures automatically.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/langchain-custom-tools-cicd.jpg" alt="Building Custom LangChain Tools for CI/CD Pipeline Interrogation"></figure>
<h2>Handling Failed Deployments</h2>
<p>When a GitHub Actions workflow fails across a microservices architecture, debugging typically requires switching between execution logs, commit histories, and build artifacts. Standard observability alerts signal that a build has broken, but an AI agent equipped with custom tool capabilities can retrieve specific log files and accurately pinpoint the root cause. The primary objective is to supply clear, deterministic access to CI/CD APIs so that the language model avoids hallucinating commands or requesting missing data.</p>
<h2>Designing the Pipeline Interrogator Tool</h2>
<p>To grant an LLM direct access to build logs, we can extend LangChain's <code>BaseTool</code>. This tool queries the GitHub Actions API for workflow run logs, parses the failed step, and extracts relevant tracebacks. Using Pydantic for input validation ensures parameters remain well-formed requiring an explicit repository name and run ID before issuing requests.</p>
<pre><code class="language-python">from langchain.tools import BaseTool
from pydantic import BaseModel, Field
import requests
import re
<p>class GitHubActionLogInput(BaseModel):
 repo: str = Field(..., description="The repository name in format owner/repo")
 run_id: int = Field(..., description="The unique ID of the failed GitHub Action run")</p>
<p>class GitHubActionLogTool(BaseTool):
 name = "fetch_failed_action_logs"
 description = "Fetches and extracts the error traceback from a failed GitHub Actions run."
 args_schema = GitHubActionLogInput
 github_token: str</p>
<p>def _run(self, repo: str, run_id: int) -&gt; str:
  # Line 1: Construct the API endpoint for workflow run logs
  url = f"https://api.github.com/repos/{repo}/actions/runs/{run_id}/logs"</p>
<p># Line 2: Set authorization and accept headers for zip download
  headers = {
  "Authorization": f"Bearer {self.github_token}",
  "Accept": "application/vnd.github+json"
  }</p>
<p># Line 3: Execute request, handling redirects for log archives
  response = requests.get(url, headers=headers, allow_redirects=True)
  if response.status_code != 200:
  return f"Error fetching logs: HTTP {response.status_code}"</p>
<p># Line 4: Extract the core error traceback using regex over the raw logs
  raw_logs = response.text
  error_pattern = re.compile(r'(?i)(error|exception|failed|traceback).*?(?=\n\n|\Z)', re.DOTALL)
  match = error_pattern.search(raw_logs)</p>
<p># Line 5: Return the isolated context to the agent, avoiding token limits
  return match.group(0) if match else "No explicit error traceback found in logs."</code></pre></p>
<h2>Code Breakdown & Implementation Choices</h2>
<ul>
<li><strong>Lines 1 & 2:</strong> We target GitHub's <code>/logs</code> endpoint with the appropriate <code>Accept</code> header to signal a request for log archives. The API token is passed during tool initialization rather than embedding sensitive credentials in model prompts.</li>
<li><strong>Line 3:</strong> <code>allow_redirects=True</code> is mandatory because GitHub redirects log download requests (302) to signed Amazon S3 URLs. Disabling redirect handling yields an empty response.</li>
<li><strong>Line 4:</strong> Build logs often exceed context limits (100k+ tokens for extensive build suites). Rather than inserting raw logs into the prompt, regular expressions extract and isolate the primary error traceback.</li>
<li><strong>Line 5:</strong> Returning only the relevant error snippet keeps prompt size low and allows the model to concentrate on diagnosing the issue, such as a missing dependency or failing unit test.</li>
</ul>
<p>Combining API retrieval with targeted regex filtering enables custom tools to supply models with precise context without exceeding token constraints or requiring manual log extraction during incidents.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/langchain-custom-tools-cicd</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/langchain-custom-tools-cicd.jpg" length="96412" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/langchain-custom-tools-cicd.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Wrapping Legacy C Libraries in Rust for Safe Infrastructure Tooling]]></title>
      <link>https://anasrhimi.tech/blog/rust-ffi-wrapping-c-libraries</link>
      <description><![CDATA[Step-by-step tutorial on leveraging Rust FFI to safely encapsulate legacy C code and eliminate memory bugs in DevOps tools.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/rust-ffi-wrapping-c-libraries.jpg" alt="Wrapping Legacy C Libraries in Rust for Safe Infrastructure Tooling"></figure>
<p>As infrastructure teams adopt Rust, they encounter a common challenge: critical business logic or protocol parsers remain locked in legacy C libraries. Rewriting everything from scratch is rarely practical. Instead, you can wrap unsafe C code in safe Rust abstractions using Rust's Foreign Function Interface (FFI).</p>
<h2>The Problem: Managing Unsafe C Pointers in Rust</h2>
<p>Suppose you have a C library that parses custom network packets. Its header (<code>libparser.h</code>) looks like this:</p>
<pre><code class="language-c">typedef struct Packet {
    int id;
    char* payload;
} Packet;
<p>// Allocates and parses a packet. Returns NULL on failure.
Packet<em> parse_packet(const uint8_t</em> data, size_t len);</p>
<p>// Must be called to prevent memory leaks.
void free_packet(Packet* pkt);</code></pre></p>
<p>Calling these functions directly forces users to manage memory manually, missing the safety benefits of Rust.</p>
<h2>The Safe Rust Wrapper Solution</h2>
<p>You can use the <code>Drop</code> trait to clean up memory automatically, and lifetimes to prevent payload access after deallocation.</p>
<pre><code class="language-rust">use std::ffi::CStr;
use std::os::raw::{c_char, c_int, c_uchar};
<p>// Bindings typically generated by bindgen
#[repr(C)]
struct CPacket {
    id: c_int,
    payload: *mut c_char,
}</p>
<p>extern "C" {
    fn parse_packet(data: <em>const c_uchar, len: usize) -&gt; </em>mut CPacket;
    fn free_packet(pkt: *mut CPacket);
}</p>
<p>// Our Safe Abstraction
pub struct Packet {
    inner: *mut CPacket,
}</p>
<p>impl Packet {
    pub fn parse(data: &amp;[u8]) -&gt; Result&lt;Self, &amp;'static str&gt; {
        // SAFETY: We pass a valid pointer and length. 
        // We check the return value for NULL.
        let ptr = unsafe { parse_packet(data.as_ptr(), data.len()) };
        if ptr.is_null() {
            Err("Failed to parse packet")
        } else {
            Ok(Packet { inner: ptr })
        }
    }</p>
<p>pub fn id(&amp;self) -&gt; i32 {
        // SAFETY: inner is guaranteed to be non-null and valid.
        unsafe { (*self.inner).id }
    }</p>
<p>pub fn payload(&amp;self) -&gt; &amp;str {
        // SAFETY: C strings must be null-terminated. We tie the lifetime
        // of the returned &amp;str to <code>&amp;self</code>, ensuring it doesn't outlive the struct.
        unsafe {
            let c_str = CStr::from_ptr((*self.inner).payload);
            c_str.to_str().unwrap_or("Invalid UTF-8")
        }
    }
}</p>
<p>// Implement Drop to automatically free the C memory
impl Drop for Packet {
    fn drop(&amp;mut self) {
        // SAFETY: The pointer is valid and this is only called once.
        unsafe {
            free_packet(self.inner);
        }
    }
}</code></pre></p>
<h2>Key Safety Guarantees</h2>
<ul>
<li><strong><code>#[repr(C)]</code></strong>: Instructs Rust to align with C's memory layout. Without it, Rust might reorder fields and break binary compatibility.</li>
<li><strong><code>*mut CPacket</code> vs <code>&mut CPacket</code></strong>: Raw pointers mark the FFI boundary. Wrapping the raw pointer inside <code>Packet</code> hides unsafe operations from consumers.</li>
<li><strong><code>CStr::from_ptr</code></strong>: Converts null-terminated C strings into Rust string slices. Lifetime elision ties the <code>&str</code> lifetime to <code>&self</code>, preventing employ-after-free bugs.</li>
<li><strong><code>Drop</code> implementation</strong>: When <code>Packet</code> goes out of scope, Rust invokes <code>drop</code> to execute <code>free_packet</code>. This prevents memory leaks on the Rust side.</li>
</ul>
<p>Wrapping unsafe C interfaces offers an incremental path to modernize infrastructure. You preserve proven C logic while building safe interfaces in Rust.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/rust-ffi-wrapping-c-libraries</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/rust-ffi-wrapping-c-libraries.jpg" length="121625" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/rust-ffi-wrapping-c-libraries.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Implementing Chaos Engineering in CI/CD with Gremlin]]></title>
      <link>https://anasrhimi.tech/blog/chaos-engineering-gremlin</link>
      <description><![CDATA[Historically, Chaos Engineering - the practice of intentionally injecting failures into systems to validate resilience - was an activity reserved for staging or production environments.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/chaos-engineering-gremlin.jpg" alt="Implementing Chaos Engineering in CI/CD with Gremlin"></figure>
<h2>Shifting Chaos Left</h2>
<p>Historically, chaos engineering was restricted to staging or production environments often right after a cascading failure ruined someone's weekend. That is no longer the case. Discovering that a downstream payment service crashes your entire checkout pipeline at 2 AM on a Sunday is an expensive and painful way to discover missing timeout configurations. Shifting left is logically imperative.</p>
<p>The core idea is simple: Gremlin provides an API alongside lightweight agents that allow you to automate failure injection directly within pull requests. You need to determine whether your microservices gracefully handle unexpected performance degradation such as an upstream API taking an extra 500ms to respond or if they freeze and crash. Do not wait for your users to report these issues.</p>
<h2>Designing the Automated Chaos Experiment</h2>
<p>Eliminate guesswork. This pipeline stage spins up an ephemeral test environment, triggers a targeted latency attack against a selected dependency, executes your integration test suite during the simulated disruption, and cleans up the attack afterward. If your application cannot withstand a slight network delay without failing, the build fails immediately.</p>
<pre><code class="language-yaml">name: Resilience Validation
on: [push]
jobs:
 chaos-test:
 runs-on: ubuntu-latest
 steps:
 - name: Deploy ephemeral environment
  run: ./scripts/deploy_test_env.sh
<ul>
<li>name: Trigger Gremlin Latency Attack id: gremlin_attack env: GREMLIN_API_KEY: ${{ secrets.GREMLIN_API_KEY }} GREMLIN_TEAM_ID: ${{ secrets.GREMLIN_TEAM_ID }} run: | # Inject 500ms latency to all traffic destined for the Payment Service API ATTACK_ID=$(curl -s -X POST https://api.gremlin.com/v1/attacks/new \ -H "Authorization: Key $GREMLIN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "command": { "type": "latency", "args": ["-l", "500"] }, "target": { "type": "Exact", "exact": { "tags": { "service": ["checkout-api"], "env": ["ci-test-env"] } } } }' | jq -r '.uid')</li>
</ul>
<p>echo "Started Attack ID: $ATTACK_ID"
  echo "ATTACK_ID=$ATTACK_ID" &gt;&gt; $GITHUB_ENV</p>
<p># Wait for attack to reach 'Running' state
  sleep 15</p>
<ul>
<li>name: Run Integration Tests (Resilience Validation) run: | # Run tests expecting degraded performance but NOT failure. # The application MUST handle the latency via retries/circuit breakers. pytest tests/integration/test_checkout_flow.py --timeout=30</li>
</ul>
<ul>
<li>name: Halt Gremlin Attack (Always run) if: always() env: GREMLIN_API_KEY: ${{ secrets.GREMLIN_API_KEY }} run: | if [ -n "$ATTACK_ID" ]; then echo "Halting attack: $ATTACK_ID" curl -s -X DELETE https://api.gremlin.com/v1/attacks/$ATTACK_ID \ -H "Authorization: Key $GREMLIN_API_KEY" fi</code></pre></li>
</ul>
<h3>How the Workflow Works</h3>
<p>Here is a breakdown of the key steps:</p>
<ul>
<li><strong><code>POST /v1/attacks/new</code></strong>: Directly invokes Gremlin's REST endpoint to programmatically launch the attack from within the check runner, eliminating manual web UI interactions.</li>
<li><strong><code>"type": "latency", "args": ["-l", "500"]</code></strong>: Introduces a 500ms latency delay to network packets, effectively simulating a slow downstream API or database connection without severing network connectivity.</li>
<li><strong><code>"target": { "tags": ... }</code></strong>: Restricts the attack scope. Gremlin agents only target containers labeled with <code>service=checkout-api</code> within the <code>ci-test-env</code> environment, preventing unintended side effects on shared infrastructure.</li>
<li><strong><code>pytest ...</code></strong>: Executes the test suite while latency is actively injected. This validates that circuit breakers and retry policies respond properly; if the application crashes or exceeds the 30-second timeout, pytest exits with an error and blocks the pull request.</li>
<li><strong><code>if: always() ... DELETE</code></strong>: Guarantees cleanup. Even if the integration tests fail, GitHub Actions executes this teardown block to send a DELETE request to Gremlin, stopping the active attack before destroying the environment.</li>
</ul>
<h2>Building Continuous Resilience</h2>
<p>Executing this workflow on every commit provides immediate feedback on your application's resilience prior to staging deployments. It transforms chaos engineering from a complex quarterly exercise into a continuous, automated verification step ensuring circuit breakers, retries, and fallback mechanisms actually work as designed when subjected to stress.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/chaos-engineering-gremlin</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/chaos-engineering-gremlin.jpg" length="79485" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/chaos-engineering-gremlin.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Writing eBPF Probes in C for Advanced Kubernetes Observability]]></title>
      <link>https://anasrhimi.tech/blog/ebpf-probes-kubernetes-observability</link>
      <description><![CDATA[A hands-on guide to writing and deploying custom eBPF probes for tracking network latency in Kubernetes without sidecar overhead.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/ebpf-probes-kubernetes-observability.jpg" alt="Writing eBPF Probes in C for Advanced Kubernetes Observability"></figure>
<p>Traditional Kubernetes observability usually relies on sidecar proxies or application-level tracing, both of which introduce CPU and memory overhead. eBPF avoids this by running sandboxed C programs directly inside the Linux kernel, enabling zero-instrumentation tracing for syscalls, network events, and file platform operations.</p>
<h3>Tracing TCP Connect Latency</h3>
<p>Standard pod metrics usually miss what happens deep in the networking stack. By tracing <code>tcp_v4_connect</code>, we can measure exact TCP connection setup times across nodes without modifying application code or container images.</p>
<h3>The eBPF C Program</h3>
<p>The probe below is written in restricted C and compiled into eBPF bytecode using Clang/LLVM.</p>
<pre><code class="language-c">#include &lt;vmlinux.h&gt;
#include &lt;bpf/bpf_helpers.h&gt;
#include &lt;bpf/bpf_tracing.h&gt;
<p>// Define a BPF map to store the start time of the connect call, keyed by PID
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 10240);
    __type(key, u32);
    __type(value, u64);
} start SEC(".maps");</p>
<p>// Kprobe triggered on entry to tcp_v4_connect
SEC("kprobe/tcp_v4_connect")
int BPF_KPROBE(tcp_v4_connect_enter, struct sock *sk)
{
    u64 ts = bpf_ktime_get_ns();
    u32 pid = bpf_get_current_pid_tgid() &gt;&gt; 32;</p>
<p>bpf_map_update_elem(&amp;start, &amp;pid, &amp;ts, BPF_ANY);
    return 0;
}</p>
<p>// Kretprobe triggered on exit of tcp_v4_connect
SEC("kretprobe/tcp_v4_connect")
int BPF_KRETPROBE(tcp_v4_connect_exit, int ret)
{
    u32 pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
    u64 *tsp, delta_us;</p>
<p>// Lookup the start time
    tsp = bpf_map_lookup_elem(&amp;start, &amp;pid);
    if (!tsp) {
        return 0; // Missed entry
    }</p>
<p>// Calculate latency in microseconds
    delta_us = (bpf_ktime_get_ns() - *tsp) / 1000;</p>
<p>// bpf_printk logs to /sys/kernel/debug/tracing/trace_pipe
    bpf_printk("PID %d TCP connect took %llu us, ret = %d\n", pid, delta_us, ret);</p>
<p>bpf_map_delete_elem(&amp;start, &amp;pid);
    return 0;
}</p>
<p>char LICENSE[] SEC("license") = "GPL";</code></pre></p>
<h3>Key Mechanics & Kernel Integration</h3>
<ul>
<li><strong><code>#include <vmlinux.h></code></strong>: Auto-generated from kernel BTF (BPF Type Format). It provides definitions for internal kernel structs without needing multiple kernel header files.</li>
<li><strong><code>BPF_MAP_TYPE_HASH</code></strong>: eBPF programs cannot leverage global variables for dynamic state. Hash maps allow entry and exit probes to share timestamps across process IDs.</li>
<li><strong><code>bpf_ktime_get_ns()</code></strong>: Fetches high-resolution monotonic timestamps from the kernel clock.</li>
<li><strong><code>bpf_get_current_pid_tgid()</code></strong>: Returns the thread group ID in the upper 32 bits and thread ID in the lower 32 bits. Right-shifting by 32 extracts the user-space PID.</li>
<li><strong><code>SEC("kprobe/...")</code> / <code>SEC("kretprobe/...")</code></strong>: Macros defining ELF section names. Loaders like <code>libbpf</code> read these to attach probes to kernel functions.</li>
</ul>
<p>Deploying this probe via a Kubernetes DaemonSet alongside a user-space agent (for example, written in Go using <code>cilium/ebpf</code>) provides low-overhead network latency tracing across all pods on a node.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/ebpf-probes-kubernetes-observability</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/ebpf-probes-kubernetes-observability.jpg" length="94334" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/ebpf-probes-kubernetes-observability.jpg" medium="image" type="image/jpeg" />
    </item>

    <item>
      <title><![CDATA[Configuring PCIe GPU Passthrough in Proxmox for Local LLMs]]></title>
      <link>https://anasrhimi.tech/blog/proxmox-pci-passthrough-gpu</link>
      <description><![CDATA[Configure Proxmox VE for PCIe GPU passthrough to achieve near-native performance for self-hosting local LLMs.]]></description>
      <content:encoded><![CDATA[<figure><img src="https://anasrhimi.tech/assets/og/proxmox-pci-passthrough-gpu.jpg" alt="Configuring PCIe GPU Passthrough in Proxmox for Local LLMs"></figure>
<p>Running LLMs locally with tools like Ollama or vLLM requires real GPU performance. But dedicating an entire physical machine to a single GPU is wasteful in a homelab environment. Proxmox VE lets you run multiple VMs on a single host and pass PCIe GPUs straight to your guest VMs using VFIO, giving you near-native performance.</p>
<h2>Host IOMMU Configuration</h2>
<p>Before passing hardware to a VM, Proxmox must isolate the PCI devices. You need to enable IOMMU in your BIOS/UEFI and configure the kernel.</p>
<pre><code class="language-bash"># Edit GRUB to enable IOMMU for Intel or AMD
nano /etc/default/grub
# For Intel: GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt"
# For AMD:   GRUB_CMDLINE_LINUX_DEFAULT="quiet amd_iommu=on iommu=pt"
<p>update-grub</p>
<p># Ensure VFIO modules load on boot
cat &lt;&lt;EOF &gt;&gt; /etc/modules
vfio
vfio_iommu_type1
vfio_pci
vfio_virqfd
EOF</p>
<p>update-initramfs -u -k all
reboot</code></pre></p>
<p><strong>Note:</strong> Setting <code>iommu=pt</code> (passthrough) prevents Linux from translating DMA requests for host devices you are not passing through, which improves host performance. The VFIO modules handle detaching the card from host drivers and safely exposing it to QEMU.</p>
<h2>Isolating the GPU with VFIO-PCI</h2>
<p>Next, prevent Proxmox from loading default open-source drivers like <code>nouveau</code> or <code>amdgpu</code> for the GPU. Bind the hardware to the <code>vfio-pci</code> driver instead.</p>
<pre><code class="language-bash"># Find PCI IDs for the GPU and its audio controller
lspci -nn | grep -i nvidia
# Example output:
# 01:00.0 VGA compatible controller [0300]: NVIDIA ... [10de:2204]
# 01:00.1 Audio device [0403]: NVIDIA ... [10de:1aef]
<p># Bind vendor:device IDs to vfio-pci
echo "options vfio-pci ids=10de:2204,10de:1aef disable_vga=1" &gt; /etc/modprobe.d/vfio.conf</p>
<p># Blacklist host drivers
cat &lt;&lt;EOF &gt; /etc/modprobe.d/blacklist.conf
blacklist radeon
blacklist nouveau
blacklist nvidia
EOF</p>
<p>update-initramfs -u -k all
reboot</code></pre></p>
<p><strong>Note:</strong> Adding <code>disable_vga=1</code> is crucial if you are passing the primary GPU. It prevents the host kernel from configuring a display framebuffer on it. Blacklisting host drivers ensures VFIO claims the card first during boot. Make sure to pass both the VGA and Audio functions together, as they usually share an IOMMU group.</p>
<h2>VM Configuration for PCIe Passthrough</h2>
<p>With the host configured, update the VM config file to attach the GPU.</p>
<pre><code class="language-ini"># Edit VM config (e.g., VM ID 100)
nano /etc/pve/qemu-server/100.conf
<p># Add the following lines:
machine: q35
hostpci0: 0000:01:00,pcie=1,x-vga=1
# For modern NVIDIA GPUs, hide hypervisor flags to prevent Code 43 errors
cpu: host,hidden=1,flags=+pcid</code></pre></p>
<p><strong>Note:</strong> The VM requires the <code>q35</code> machine type, which provides a native PCIe bus (<code>i440fx</code> only emulates legacy PCI). <code>pcie=1</code> exposes the card as a PCIe device, while <code>x-vga=1</code> marks it as the primary display. Setting <code>hidden=1</code> hides KVM hypervisor signatures from CPUID to bypass NVIDIA driver Code 43 errors.</p>]]></content:encoded>
      <pubDate>Sat, 08 Aug 2026 00:00:00 -0000</pubDate>
      <guid isPermaLink="true">https://anasrhimi.tech/blog/proxmox-pci-passthrough-gpu</guid>
      <enclosure url="https://anasrhimi.tech/assets/og/proxmox-pci-passthrough-gpu.jpg" length="91794" type="image/jpeg" />
      <media:content url="https://anasrhimi.tech/assets/og/proxmox-pci-passthrough-gpu.jpg" medium="image" type="image/jpeg" />
    </item>

  </channel>
</rss>
