<?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:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Waruna Udara Sampath — Developer Articles</title>
    <link>https://www.warunaudara.me/blog</link>
    <description>Deep dives into agentic AI, cloud infrastructure, microservices and frontend architecture.</description>
    <language>en</language>
    <lastBuildDate>Tue, 27 Oct 2026 00:00:00 GMT</lastBuildDate>
    <atom:link href="https://www.warunaudara.me/rss.xml" rel="self" type="application/rss+xml" />
    <generator>scripts/blog-manifest.mjs</generator>
    <item>
      <title>Building StudEd: A Cloud-Native Microservices Platform for Learning</title>
      <link>https://www.warunaudara.me/blog/studed</link>
      <guid isPermaLink="true">https://www.warunaudara.me/blog/studed</guid>
      <pubDate>Tue, 28 Jul 2026 09:00:00 GMT</pubDate>
      <dc:creator>Waruna Udara Sampath</dc:creator>
      <category>SYSTEM ARCHITECTURE</category>
      <description>A practical breakdown of StudEd — a cloud-native educational platform built with Go microservices, GraphQL, gRPC, Kubernetes, OpenTofu, and GitOps.</description>
      <content:encoded><![CDATA[<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Building StudEd started as an educational platform project, but it quickly became much more than just building a web application.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">As the system grew, I found myself working with <strong class="font-semibold text-white">Go microservices, GraphQL, gRPC, Kubernetes, OpenTofu, GitOps, observability, cloud security, and local cloud emulation</strong>.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This article is a look at how those technologies come together to power StudEd and, more importantly, what I learned while building it.</p>
<hr class="my-10 border-white/10"/>
<h2 id="what-is-studed" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">What is StudEd?</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">StudEd</strong> is a cloud-native educational platform designed to provide a modern learning experience while keeping the underlying system scalable and maintainable.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Instead of building everything inside one large backend, StudEd is divided into independent services, each responsible for a specific part of the platform.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The architecture was designed around an important idea:</p>
<blockquote class="my-8 border-l border-white/15 pl-5 text-base italic leading-relaxed text-neutral-300">
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">The application should be easy to develop, easy to deploy, and easy to observe as it grows.</strong></p>
</blockquote>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">That naturally led me toward a microservices architecture.</p>
<hr class="my-10 border-white/10"/>
<h2 id="the-architecture" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">The Architecture</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">At a high level, the system looks like this:</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">Clients → Edge Layer → API Gateway → Microservices → Data Layer</strong></p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Around that core flow sits the platform infrastructure responsible for deployment, security, secrets, infrastructure provisioning, and observability.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><img src="https://www.warunaudara.me/images/projects/architecture-diagram.png" alt="StudEd Master Microservices Architecture Diagram" loading="lazy" class="my-8 h-auto w-full rounded-xl border border-white/10"/></p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The diagram above shows how the different layers connect and how requests move through the platform.</p>
<hr class="my-10 border-white/10"/>
<h2 id="go-microservices-at-the-core" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Go Microservices at the Core</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The backend is built with <strong class="font-semibold text-white">Go (Golang)</strong> and divided into domain-focused microservices.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Some of the main services include:</p>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">API Gateway</strong> — provides the main GraphQL interface for clients.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Auth Service</strong> — handles authentication, tokens, and permissions.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Course Service</strong> — manages courses, curriculum, and learning content.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Progress Service</strong> — tracks student progress and completion.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Gamification Service</strong> — handles XP, achievements, and leaderboards.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">AI Service</strong> — provides intelligent learning and content-related features.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Payment Service</strong> — manages payment workflows.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Notification Service</strong> — handles application notifications.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Upload Service</strong> — manages media and document uploads.</li>
</ul>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Each service has its own responsibility rather than everything being handled by one large codebase.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This makes the platform easier to evolve because individual services can be developed and deployed independently.</p>
<hr class="my-10 border-white/10"/>
<h2 id="graphql-for-the-client-grpc-between-services" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">GraphQL for the Client, gRPC Between Services</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">One of the architectural decisions I found particularly interesting was using <strong class="font-semibold text-white">two different communication patterns</strong>.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">For the frontend, StudEd exposes a unified <strong class="font-semibold text-white">GraphQL API</strong>.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">For communication between backend services, it uses <strong class="font-semibold text-white">gRPC with Protocol Buffers</strong>.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">So the flow is essentially:</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">Frontend → GraphQL → API Gateway → gRPC → Microservices</strong></p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">GraphQL gives the client a flexible API, while gRPC provides strongly typed and efficient communication between internal services.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Using <strong class="font-semibold text-white">Protocol Buffers</strong> also gives the services explicit contracts, making the boundaries between them much clearer.</p>
<hr class="my-10 border-white/10"/>
<h2 id="choosing-the-right-data-store" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Choosing the Right Data Store</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Not everything belongs in the same database.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">StudEd uses different technologies for different workloads.</p>
<h3 id="postgresql" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">PostgreSQL</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">Neon Serverless PostgreSQL</strong> is used for relational application data that needs strong transactional consistency.</p>
<h3 id="redis" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Redis</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">Redis</strong> handles fast-changing state such as caching, token-related operations, sessions, and rate limiting.</p>
<h3 id="elasticsearch" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Elasticsearch</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">Elasticsearch</strong> powers advanced search across courses, instructors, lessons, and other content, including support for fuzzy and internationalized text search.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This approach lets each component use a storage system that fits its job instead of forcing the entire platform into one database.</p>
<hr class="my-10 border-white/10"/>
<h2 id="kubernetes-and-platform-engineering" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Kubernetes and Platform Engineering</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Once you have multiple services, deployment becomes a problem of its own.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">StudEd runs on <strong class="font-semibold text-white">Kubernetes</strong>, with the production architecture built around <strong class="font-semibold text-white">Google Kubernetes Engine (GKE)</strong>.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The platform also uses several tools around Kubernetes:</p>
<h3 id="argocd" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">ArgoCD</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">ArgoCD</strong> provides the GitOps workflow.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Instead of manually changing cluster resources, the desired state lives in Git and ArgoCD continuously reconciles the cluster with that state.</p>
<h3 id="kyverno" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Kyverno</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">Kyverno</strong> is used to enforce Kubernetes policies such as secure container configurations, immutable image references, and resource limits.</p>
<h3 id="external-secrets-operator" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">External Secrets Operator</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Secrets are kept outside application manifests and synchronized into Kubernetes through <strong class="font-semibold text-white">External Secrets Operator</strong> and Google Secret Manager.</p>
<h3 id="network-policies" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Network Policies</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The cluster follows a <strong class="font-semibold text-white">default-deny, zero-trust approach</strong>, controlling which services are allowed to communicate with each other and where workloads can send traffic.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This is where StudEd moved from simply being a backend project into something much closer to a real platform engineering environment.</p>
<hr class="my-10 border-white/10"/>
<h2 id="opentofu-infrastructure-as-code" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">OpenTofu: Infrastructure as Code</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">I didn&#x27;t want the infrastructure to depend on manual setup.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">That is where <strong class="font-semibold text-white">OpenTofu</strong> comes in.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The infrastructure is defined declaratively so networking, compute, security, storage, and other cloud resources can be reproduced consistently.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The same infrastructure approach can also target local cloud emulation environments during development.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">That makes the infrastructure part of the project itself rather than something that exists separately from the code.</p>
<hr class="my-10 border-white/10"/>
<h2 id="floci-building-the-cloud-locally" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Floci: Building the Cloud Locally</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">One of the tools I particularly wanted to experiment with was <strong class="font-semibold text-white">Floci</strong>.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Cloud development can get expensive and slow when every small test requires real cloud resources.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Floci gives the project local emulation for cloud services such as:</p>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5">Cloud Storage</li>
<li class="flex items-start gap-2.5">Secret Manager</li>
<li class="flex items-start gap-2.5">Pub/Sub</li>
</ul>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This means I can test cloud-dependent functionality locally without constantly deploying to a real environment.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">For me, this became especially useful for the developer inner loop: build locally, test locally, then move toward the actual cloud environment.</p>
<hr class="my-10 border-white/10"/>
<h2 id="containers-everywhere" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Containers Everywhere</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">StudEd is containerized using <strong class="font-semibold text-white">Docker and Podman</strong>.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The services use multi-stage builds so development dependencies and compiler tooling don&#x27;t have to end up inside the final runtime image.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This keeps the containers smaller and reduces unnecessary components in production images.</p>
<hr class="my-10 border-white/10"/>
<h2 id="observability-is-not-an-afterthought" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Observability Is Not an Afterthought</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">When you have a single application, finding a problem can be relatively straightforward.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">With multiple microservices, it gets much harder.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">A request can travel through the API Gateway, several services, a database, Redis, and external systems before the user sees a response.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">That is why StudEd includes a dedicated observability stack built around:</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">OpenTelemetry + Prometheus + Grafana + Tempo</strong></p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">OpenTelemetry provides distributed tracing across services.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Prometheus collects application and infrastructure metrics.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Grafana turns those metrics into operational dashboards.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Tempo stores and visualizes distributed traces.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Together, they make it possible to move from:</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">&quot;Something is slow.&quot;</strong></p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">to:</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">&quot;This specific service or database operation caused the slowdown.&quot;</strong></p>
<hr class="my-10 border-white/10"/>
<h2 id="the-frontend-and-delivery-pipeline" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">The Frontend and Delivery Pipeline</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">On the client side, StudEd uses <strong class="font-semibold text-white">TypeScript, Vite, and Bun</strong>, with the frontend deployed through <strong class="font-semibold text-white">Cloudflare Pages</strong>.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The delivery pipeline is built around <strong class="font-semibold text-white">GitHub Actions</strong>, container registries, automated validation, and GitOps deployment.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This means the development workflow is not simply:</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">write code → manually deploy</strong></p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Instead, code moves through a controlled pipeline before reaching the running environment.</p>
<hr class="my-10 border-white/10"/>
<h2 id="the-technology-stack" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">The Technology Stack</h2>
<div class="my-8 w-full overflow-x-auto rounded-2xl border border-white/10 bg-neutral-950/60"><table class="w-full border-collapse text-left text-sm text-neutral-300"><thead class="border-b border-white/10 font-mono text-[10px] uppercase tracking-[0.14em] text-neutral-400"><tr><th class="border-r border-white/5 px-4 py-3.5 text-left font-semibold text-neutral-200 last:border-r-0">Area</th><th class="border-r border-white/5 px-4 py-3.5 text-left font-semibold text-neutral-200 last:border-r-0">Technologies</th></tr></thead><tbody class="divide-y divide-white/10"><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Backend</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Go</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">API</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">GraphQL</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Internal Communication</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">gRPC + Protobuf</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Database</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Neon PostgreSQL</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Cache</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Redis</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Search</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Elasticsearch</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Containers</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Docker + Podman</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Orchestration</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Kubernetes / GKE</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Infrastructure</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">OpenTofu</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Local Cloud</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Floci</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">GitOps</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">ArgoCD</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Policy</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Kyverno</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Secrets</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Google Secret Manager + ESO</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Observability</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">OpenTelemetry, Prometheus, Grafana, Tempo</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Frontend</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">TypeScript, Vite, Bun</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Edge Deployment</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Cloudflare Pages</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">CI/CD</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">GitHub Actions</td></tr></tbody></table></div>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The important part isn&#x27;t simply the number of technologies.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">It&#x27;s how they work together to solve different problems across the platform.</p>
<hr class="my-10 border-white/10"/>
<h2 id="what-i-learned-building-studed" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">What I Learned Building StudEd</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">StudEd taught me that building a distributed system is very different from simply building an application.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Writing a microservice is one thing.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Deciding <strong class="font-semibold text-white">where that service belongs, how it communicates, how it is deployed, how its secrets are managed, how it is observed, and how developers can reproduce its environment locally</strong> is a completely different challenge.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The biggest lesson for me was that technologies such as <strong class="font-semibold text-white">Kubernetes, OpenTofu, ArgoCD, OpenTelemetry, and Floci aren&#x27;t isolated tools</strong>.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">They become useful when they work together as part of a platform.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">And that is the part of StudEd I enjoyed the most: taking an educational product idea and using it as a practical playground for learning <strong class="font-semibold text-white">cloud-native engineering and platform engineering</strong>.</p>
<hr class="my-10 border-white/10"/>
<h2 id="final-thoughts" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Final Thoughts</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">StudEd is still evolving, but the architecture has already given me hands-on experience with many of the ideas behind modern cloud-native systems.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">From <strong class="font-semibold text-white">Go microservices and gRPC</strong> to <strong class="font-semibold text-white">Kubernetes, GitOps, Infrastructure as Code, local cloud emulation, and distributed observability</strong>, the project has become much more than an educational application.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">It has become a way for me to understand what happens <strong class="font-semibold text-white">behind the application</strong>.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">And honestly, that&#x27;s where things get interesting.</p>]]></content:encoded>
    </item>
    <item>
      <title>HTTP QUERY Method (RFC 10008): Solving GET &amp; POST API Limitations</title>
      <link>https://www.warunaudara.me/blog/http-query</link>
      <guid isPermaLink="true">https://www.warunaudara.me/blog/http-query</guid>
      <pubDate>Sat, 04 Jul 2026 09:00:00 GMT</pubDate>
      <dc:creator>Waruna Udara Sampath</dc:creator>
      <category>API ARCHITECTURE</category>
      <description>An architectural breakdown of the new HTTP QUERY method (RFC 10008), how it eliminates GET URL limits and POST semantic anti-patterns in modern web APIs.</description>
      <content:encoded><![CDATA[<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">For over two decades, web developers and API architects have built RESTful services around a fundamental binary choice: use <code>GET</code> to fetch data and <code>POST</code> to send or mutate data.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">For simple web applications, this boundary was sufficient. But as modern enterprise software evolved to demand complex multi-attribute filtering, nested JSON search payloads, dynamic analytics aggregation, and fine-grained pagination, <code>GET</code> began breaking under its own constraints — while abusing <code>POST</code> created architectural anti-patterns.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Enter the <strong class="font-semibold text-white">HTTP QUERY method (RFC 10008)</strong>: a standardized HTTP verb designed explicitly to execute safe, idempotent, and cacheable queries with rich request bodies.</p>
<hr class="my-10 border-white/10"/>
<h2 id="the-dilemma-why-get-and-post-fail-for-complex-search" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">The Dilemma: Why GET and POST Fail for Complex Search</h2>
<h3 id="the-problem-with-get" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">The Problem with GET</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">By HTTP specification, <code>GET</code> requests carry their intent inside the Request-URI and query parameters:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-http">GET /api/v1/products?category=electronics&amp;page=2&amp;sort=price
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This works cleanly for simple lookups. However, when building complex enterprise search engines (such as product filters, log aggregators, or telemetry dashboards), query strings become chaotic:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-http">GET /api/v1/products?filter%5Bcategory%5D%5B%5D=electronics&amp;filter%5Bcategory%5D%5B%5D=gaming&amp;filter%5Bprice%5D%5Bmin%5D=100&amp;filter%5Bprice%5D%5Bmax%5D=1000&amp;sort%5B0%5D%5Bfield%5D=rating&amp;sort%5B0%5D%5Bdirection%5D=desc
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This approach presents severe production risks:</p>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">URL Truncation</strong>: Browsers, reverse proxies (Nginx, HAProxy), and Cloudflare edge nodes enforce strict URL length limits (often 2KB–8KB).</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Encoding Hell</strong>: Nested arrays and objects require heavy URL encoding, destroying readability and developer experience.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Security &amp; Privacy Leakage</strong>: Search terms, tokens, or PII passed in URL parameters are routinely recorded in plain text inside access logs, CDN logs, and browser histories.</li>
</ul>
<h3 id="the-problem-with-post-as-a-search-fallback" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">The Problem with POST as a Search Fallback</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">To circumvent URL limits, developers started wrapping search queries inside <code>POST</code> bodies:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-http">POST /api/v1/products/search
Content-Type: application/json

{
  &quot;categories&quot;: [&quot;electronics&quot;, &quot;gaming&quot;],
  &quot;price&quot;: { &quot;min&quot;: 100, &quot;max&quot;: 1000 },
  &quot;sort&quot;: [{ &quot;field&quot;: &quot;rating&quot;, &quot;direction&quot;: &quot;desc&quot; }]
}
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">While this solves payload size issues, it violates HTTP semantics:</p>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white"><code>POST</code> is Non-Safe and Non-Idempotent</strong>: HTTP clients and proxies assume <code>POST</code> modifies server-side state. Dropped connections cannot be safely retried automatically by resilient network clients.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Broken Caching</strong>: Edge CDNs, HTTP caches, and browser caches ignore <code>POST</code> responses by default.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Semantic Anti-Pattern</strong>: Creating endpoint hacks like <code>POST /products/search</code> or <code>POST /products/filter</code> misrepresents a pure read operation as a mutation.</li>
</ul>
<hr class="my-10 border-white/10"/>
<h2 id="the-solution-http-query-rfc-10008" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">The Solution: HTTP QUERY (RFC 10008)</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Published as a Proposed Standard by the IETF HTTP Working Group (authored by Julian Reschke, James M. Snell, and Mike Bishop), <strong class="font-semibold text-white">RFC 10008</strong> introduces the <code>QUERY</code> method.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><code>QUERY</code> combines the <strong class="font-semibold text-white">safety and idempotency of <code>GET</code></strong> with the <strong class="font-semibold text-white">structured payload capabilities of <code>POST</code></strong>:</p>
<figure class="my-8 rounded-2xl border border-white/10 bg-neutral-950 px-6 py-10 text-center"><figcaption class="font-mono text-[10px] uppercase tracking-[0.18em] text-neutral-500">Diagram</figcaption></figure>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-http">QUERY /api/v1/products HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json

{
  &quot;categories&quot;: [&quot;electronics&quot;, &quot;gaming&quot;],
  &quot;price&quot;: { &quot;min&quot;: 100, &quot;max&quot;: 1000 },
  &quot;features&quot;: [&quot;Bluetooth&quot;, &quot;5G&quot;, &quot;OLED&quot;],
  &quot;sort&quot;: [{ &quot;field&quot;: &quot;rating&quot;, &quot;direction&quot;: &quot;desc&quot; }]
}
</code></pre>
<h3 id="protocol-comparison-matrix" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Protocol Comparison Matrix</h3>
<div class="my-8 w-full overflow-x-auto rounded-2xl border border-white/10 bg-neutral-950/60"><table class="w-full border-collapse text-left text-sm text-neutral-300"><thead class="border-b border-white/10 font-mono text-[10px] uppercase tracking-[0.14em] text-neutral-400"><tr><th class="border-r border-white/5 px-4 py-3.5 text-left font-semibold text-neutral-200 last:border-r-0">Property</th><th class="border-r border-white/5 px-4 py-3.5 text-left font-semibold text-neutral-200 last:border-r-0"><code>GET</code></th><th class="border-r border-white/5 px-4 py-3.5 text-left font-semibold text-neutral-200 last:border-r-0"><code>POST</code></th><th class="border-r border-white/5 px-4 py-3.5 text-left font-semibold text-neutral-200 last:border-r-0"><code>QUERY</code> (RFC 10008)</th></tr></thead><tbody class="divide-y divide-white/10"><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">Safe (Read-Only)</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">✅ Yes</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">❌ No</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">✅ Yes</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">Idempotent</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">✅ Yes</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">❌ No</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">✅ Yes</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">Request Body Allowed</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">❌ Undefined / Avoid</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">✅ Yes</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">✅ Defined &amp; Required</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">Edge Cacheable</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">✅ Yes (Default)</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">❌ No</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">✅ Yes (With Cache Headers)</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">Automatic Client Retries</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">✅ Safe</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">❌ Unsafe</td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">✅ Safe</td></tr></tbody></table></div>
<hr class="my-10 border-white/10"/>
<h2 id="protocol-semantics--header-standards" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Protocol Semantics &amp; Header Standards</h2>
<ol class="my-6 list-decimal space-y-3 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Mandatory <code>Content-Type</code></strong>: The server requires a valid <code>Content-Type</code> header (e.g., <code>application/json</code>, <code>application/graphql</code>, or <code>application/sql</code>). If missing, the server returns <code>400 Bad Request</code> or <code>415 Unsupported Media Type</code>.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white"><code>Content-Location</code> Header Response</strong>: Servers can return a <code>Content-Location</code> header in the response pointing to a persistent resource URL.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">HTTP Status Codes</strong>:
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><code>200 OK</code>: Query executed successfully.</li>
<li class="flex items-start gap-2.5"><code>400 Bad Request</code>: Missing or malformed query payload.</li>
<li class="flex items-start gap-2.5"><code>415 Unsupported Media Type</code>: Query format not recognized by backend.</li>
<li class="flex items-start gap-2.5"><code>422 Unprocessable Entity</code>: Query syntax valid, but logical parameters invalid.</li>
</ul>
</li>
</ol>
<hr class="my-10 border-white/10"/>
<h2 id="ecosystem-support--practical-implementation" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Ecosystem Support &amp; Practical Implementation</h2>
<h3 id="client--framework-adoption" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Client &amp; Framework Adoption</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Language ecosystems and frameworks are rolling out native <code>QUERY</code> support:</p>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">.NET 10 (LTS)</strong>: Native support via <code>HttpMethod.Query</code> in <code>HttpClient</code> and <code>HttpMethods.IsQuery</code> in ASP.NET Core Kestrel.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Node.js &amp; Go</strong>: HTTP client drivers allow registering <code>QUERY</code> as a custom verb across backend microservices.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">API Gateways &amp; CDNs</strong>: Cloudflare and Akamai are building edge caching rules for RFC 10008 <code>QUERY</code> responses.</li>
</ul>
<hr class="my-10 border-white/10"/>
<h2 id="strategic-adoption-strategy" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Strategic Adoption Strategy</h2>
<ol class="my-6 list-decimal space-y-3 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Service-to-Service (gRPC / Internal REST Microservices)</strong>: Adopt <code>QUERY</code> for microservices communicating internally where both client and server are controlled by your platform team.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Public REST APIs</strong>: Provide <code>QUERY</code> endpoints while offering a <code>POST /search</code> fallback for legacy web clients until browser <code>fetch()</code> APIs universally expose the verb.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Platform Engineering &amp; Monitoring</strong>: Use <code>QUERY</code> for log aggregation, Elasticsearch queries, metrics telemetry, and complex reporting endpoints.</li>
</ol>
<hr class="my-10 border-white/10"/>
<h2 id="conclusion" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Conclusion</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The HTTP <code>QUERY</code> method represents a crucial evolution in Web and Platform Engineering. By granting developers a safe, idempotent method equipped with a structured request body, RFC 10008 finally solves a two-decade-old API design trade-off.</p>]]></content:encoded>
    </item>
    <item>
      <title>Building Turboship: Agentic AI Deployment Harness for Multi-Cloud</title>
      <link>https://www.warunaudara.me/blog/turboship</link>
      <guid isPermaLink="true">https://www.warunaudara.me/blog/turboship</guid>
      <pubDate>Mon, 18 May 2026 09:00:00 GMT</pubDate>
      <dc:creator>Waruna Udara Sampath</dc:creator>
      <category>AI &amp; DEVOPS</category>
      <description>How we engineered an AI-powered Internal Developer Platform during the Cursor Sri Lanka 24H Buildathon, securing 2nd Place.</description>
      <content:encoded><![CDATA[<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">During the <strong class="font-semibold text-white">Cursor Sri Lanka 24H Buildathon</strong>, our team set out to solve a real-world developer bottleneck: the friction between containerizing a application and provisioning secure multi-cloud production infrastructure.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The result of our 24-hour sprint was <strong class="font-semibold text-white">Turboship</strong>, an AI-powered Internal Developer Platform (IDP) control plane that achieved <strong class="font-semibold text-white">2nd Place (n8n Track)</strong> and <strong class="font-semibold text-white">14th Place Overall</strong> out of 100+ competing teams.</p>
<hr class="my-10 border-white/10"/>
<h2 id="the-problem-we-set-out-to-solve" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">The Problem We Set Out to Solve</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Deploying containerized microservices to cloud providers like AWS ECS or Azure Container Apps usually requires writing verbose Infrastructure-as-Code (Terraform, Bicep, CDK), configuring IAM policies, managing secrets, and running security vulnerability scans manually.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">For small teams and fast-moving developers, this overhead slows down ship velocity.</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash"># Traditional manual deployment flow
docker build -t username/app:latest .
docker push username/app:latest
aws ecs create-cluster --cluster-name production
aws ecs register-task-definition --cli-input-json file://task-def.json
aws ecs create-service --cluster-name production --service-name app-service
</code></pre>
<hr class="my-10 border-white/10"/>
<h2 id="the-turboship-architecture" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">The Turboship Architecture</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Turboship automates the entire control plane using an <strong class="font-semibold text-white">agentic AI orchestration pipeline</strong>. Users simply submit a public Docker Hub image URL, and Turboship&#x27;s AI workflow takes over.</p>
<h3 id="architecture-highlights" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Architecture Highlights:</h3>
<ol class="my-6 list-decimal space-y-3 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Runtime Requirements Analysis</strong>: GPT-5.5 inspects image metadata and recommends optimal vCPU/RAM allocations.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Automated Vulnerability Scanning</strong>: Integrated <strong class="font-semibold text-white">Trivy</strong> vulnerability scanner checks image layers for CVEs before deployment.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Multi-Cloud Provisioning</strong>: Executes secure AWS SigV4 signed requests to deploy directly to <strong class="font-semibold text-white">AWS ECS Fargate</strong> and <strong class="font-semibold text-white">Azure Container Apps</strong>.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Human-in-the-Loop Safeguards</strong>: Displays proposed infrastructure costs and security findings for developer approval before executing cloud mutations.</li>
</ol>
<figure class="my-8 rounded-2xl border border-white/10 bg-neutral-950 px-6 py-10 text-center"><figcaption class="font-mono text-[10px] uppercase tracking-[0.18em] text-neutral-500">Diagram</figcaption></figure>
<hr class="my-10 border-white/10"/>
<h2 id="tech-stack--key-takeaways" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Tech Stack &amp; Key Takeaways</h2>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Frontend Control Plane</strong>: Next.js 15 &amp; Tailwind CSS with WebGL status monitors.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Agentic Orchestration Engine</strong>: n8n workflows integrated with OpenAI GPT-5.5.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Security &amp; Vulnerability Analysis</strong>: Trivy scanner CLI &amp; AWS SigV4 request signatures.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Cloud Targets</strong>: AWS ECS Fargate &amp; Azure Container Apps.</li>
</ul>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Building Turboship in 24 hours proved the power of coupling AI agents with structured workflow orchestration engines like n8n.</p>]]></content:encoded>
    </item>
    <item>
      <title>Architecting Technical Excellence: React 19, TanStack Router, GSAP Physics &amp; Web Audio Engine</title>
      <link>https://www.warunaudara.me/blog/react-19</link>
      <guid isPermaLink="true">https://www.warunaudara.me/blog/react-19</guid>
      <pubDate>Fri, 10 Apr 2026 09:00:00 GMT</pubDate>
      <dc:creator>Waruna Udara Sampath</dc:creator>
      <category>WEB ARCHITECTURE</category>
      <description>An in-depth technical architectural postmortem of Waruna Udara&apos;s developer portfolio — showcasing React 19, TanStack Router, GSAP ScrollTrigger physics, Bun runtime, zero-asset Web Audio synthesis, and glassmorphic UI engineering.</description>
      <content:encoded><![CDATA[<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This is the portfolio you are reading. The code lives at <a href="https://github.com/WarunaUdara/my-dev-portfolio" class="text-white underline decoration-white/30 underline-offset-[3px]">github.com/WarunaUdara/my-dev-portfolio</a>.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Building a modern developer portfolio should never be a generic template exercise. It is a flagship showcase of <strong class="font-semibold text-white">technical discipline, design aesthetics, performance optimization, and creative engineering</strong>.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">In this article, I break down the engineering decisions, performance benchmarks, and interactive systems behind my personal portfolio—built with <strong class="font-semibold text-white">React 19, TanStack Router, GSAP ScrollTrigger, Bun runtime, and Tailwind CSS v4</strong>.</p>
<hr class="my-10 border-white/10"/>
<h2 id="1-high-performance-architecture-overview" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">1. High-Performance Architecture Overview</h2>
<figure class="my-8 rounded-2xl border border-white/10 bg-neutral-950 px-6 py-10 text-center"><figcaption class="font-mono text-[10px] uppercase tracking-[0.18em] text-neutral-500">Diagram</figcaption></figure>
<hr class="my-10 border-white/10"/>
<h2 id="2-100-type-safe-routing--intent-preloading" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">2. 100% Type-Safe Routing &amp; Intent Preloading</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Traditional string-based routing (<code>/blog/my-post</code>) is prone to broken link bugs and unhandled parameters. Using <strong class="font-semibold text-white">TanStack Router</strong>, every route path, search parameter, and loader function is generated into static TypeScript types (<code>routeTree.gen.ts</code>).</p>
<h3 id="key-performance-benefits" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Key Performance Benefits:</h3>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Intent Preloading</strong>: Hovering a navigation link or project card triggers async code-splitting downloads in the background <strong class="font-semibold text-white">before the user even clicks</strong>.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Zero Layout Shift</strong>: Route loaders resolve data prior to view transition, eliminating loading spinner flashes.</li>
</ul>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-tsx">import { createFileRoute } from &#x27;@tanstack/react-router&#x27;;

export const Route = createFileRoute(&#x27;/blog/$slug&#x27;)({
  component: BlogPostPage,
});

function BlogPostPage() {
  const { slug } = Route.useParams();
  return &lt;BlogPostView slug={slug} /&gt;;
}
</code></pre>
<figure class="my-8 rounded-2xl border border-white/10 bg-neutral-950 px-6 py-10 text-center"><figcaption class="font-mono text-[10px] uppercase tracking-[0.18em] text-neutral-500">Diagram</figcaption></figure>
<hr class="my-10 border-white/10"/>
<h2 id="3-gsap-physics--interactive-bouncecards--engine" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">3. GSAP Physics &amp; Interactive <code>&lt;BounceCards /&gt;</code> Engine</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Rather than relying on basic CSS transitions, complex interactive sections (such as Volunteering experience cards) utilize <strong class="font-semibold text-white">GSAP (GreenSock) animation contexts</strong> combined with spring physics (<code>elastic.out(1, 0.7)</code>).</p>
<h3 id="sibling-push-hover-mechanics" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Sibling Push Hover Mechanics</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">When hovering over an event photo card inside the <strong class="font-semibold text-white">ReactBits <code>&lt;BounceCards /&gt;</code></strong> component:</p>
<ol class="my-6 list-decimal space-y-3 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5">The hovered card smoothly rotates upright (<code>rotate(0deg)</code>), scales up (<code>scale(1.08)</code>), and elevates z-index (<code>z-30</code>).</li>
<li class="flex items-start gap-2.5">Sibling cards push outward dynamically based on index distance (<code>offsetX = i &lt; hoveredIdx ? -55px : 55px</code>) with staggered spring easing (<code>back.out(1.4)</code>).</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Natural Aspect Ratio</strong>: Configured to preserve native landscape ratios (<code>aspect-[16/10]</code>), ensuring zero photo cropping for event galleries.</li>
</ol>
<hr class="my-10 border-white/10"/>
<h2 id="4-premium-dark-black-silver--obsidian-design-system" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">4. Premium Dark Black, Silver &amp; Obsidian Design System</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The design follows strict premium aesthetic guidelines:</p>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Color Palette</strong>: Obsidian black backgrounds (<code>bg-black/90</code>), subtle silver borders (<code>border-neutral-800/80</code>), and high-contrast typography.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Glassmorphism</strong>: Backdrop blur filters (<code>backdrop-blur-xl</code>), subtle radial glows, and white/silver borders (<code>hover:border-neutral-700</code>).</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Typography Hierarchy</strong>: Main hero titles leverage <strong class="font-semibold text-white">Instrument Serif</strong> (<code>font-serif</code>), while technical subtopics, tags, and code blocks use bold high-contrast <strong class="font-semibold text-white">Sans-Serif</strong> and <strong class="font-semibold text-white">Monospace</strong>.</li>
</ul>
<hr class="my-10 border-white/10"/>
<h2 id="5-zero-asset-audio-synthesis-web-audio-api" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">5. Zero-Asset Audio Synthesis (Web Audio API)</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">To maintain a lightweight build footprint, all interactive UI sounds (button clicks, hover ticks, level-up chimes) are generated programmatically using Web Audio API oscillators — <strong class="font-semibold text-white">0 KB audio files downloaded over the network</strong>.</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-typescript">// Zero-Asset Button Click Sound Synthesizer
export function playClickSound() {
  const ctx = new (window.AudioContext || (window as any).webkitAudioContext)();
  const osc = ctx.createOscillator();
  const gain = ctx.createGain();
  
  osc.type = &#x27;sine&#x27;;
  osc.frequency.setValueAtTime(800, ctx.currentTime);
  osc.frequency.exponentialRampToValueAtTime(200, ctx.currentTime + 0.05);
  
  gain.gain.setValueAtTime(0.08, ctx.currentTime);
  gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.05);
  
  osc.connect(gain);
  gain.connect(ctx.destination);
  
  osc.start();
  osc.stop(ctx.currentTime + 0.05);
}
</code></pre>
<hr class="my-10 border-white/10"/>
<h2 id="6-performance-benchmarks--build-metrics" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">6. Performance Benchmarks &amp; Build Metrics</h2>
<div class="my-8 w-full overflow-x-auto rounded-2xl border border-white/10 bg-neutral-950/60"><table class="w-full border-collapse text-left text-sm text-neutral-300"><thead class="border-b border-white/10 font-mono text-[10px] uppercase tracking-[0.14em] text-neutral-400"><tr><th class="border-r border-white/5 px-4 py-3.5 text-left font-semibold text-neutral-200 last:border-r-0">Benchmark Metric</th><th class="border-r border-white/5 px-4 py-3.5 text-left font-semibold text-neutral-200 last:border-r-0">Result Achieved</th><th class="border-r border-white/5 px-4 py-3.5 text-left font-semibold text-neutral-200 last:border-r-0">Engineering Strategy</th></tr></thead><tbody class="divide-y divide-white/10"><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">Production Build Time</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">&lt; 1.3s</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Powered by Vite 8 &amp; Bun package manager.</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">Lighthouse Performance</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">99 - 100</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Client-side bundle splitting &amp; asset optimization.</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">Asset Download Size</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">Minimal</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Web Audio API sound synthesis &amp; WebP next-gen image compression.</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">Type Coverage</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">100%</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">Strict TypeScript 5.x compiler configuration.</td></tr><tr><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">Route Preloading Speed</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0"><strong class="font-semibold text-white">&lt; 20ms</strong></td><td class="whitespace-normal border-r border-white/5 px-4 py-3.5 leading-relaxed last:border-r-0">TanStack Router intent preloading on hover.</td></tr></tbody></table></div>
<hr class="my-10 border-white/10"/>
<h2 id="conclusion" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Conclusion</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This portfolio demonstrates that client-side React applications, when architected with <strong class="font-semibold text-white">type-safe routing, physics-driven animations, disciplined design tokens, and fast build tools</strong>, can deliver desktop-class responsiveness and aesthetic perfection.</p>]]></content:encoded>
    </item>
    <item>
      <title>Kyverno Tutorial: How to Use a Policy Engine on Kubernetes</title>
      <link>https://www.warunaudara.me/blog/kyverno</link>
      <guid isPermaLink="true">https://www.warunaudara.me/blog/kyverno</guid>
      <pubDate>Fri, 20 Feb 2026 09:00:00 GMT</pubDate>
      <dc:creator>Waruna Udara Sampath</dc:creator>
      <category>DEVOPS &amp; SECURITY</category>
      <description>Learn how to validate, mutate, and generate Kubernetes resources using plain YAML, no new languages required.</description>
      <content:encoded><![CDATA[<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Kubernetes does not stop you from making mistakes.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">You can deploy a container that runs as the root user, giving it administrator-level access to the machine it runs on. You can run a pod with no CPU or memory limits, letting it consume an entire node and slow down every other workload. You can create namespaces with no labels, making your cluster impossible to manage at scale.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Kubernetes accepts all of this without a single warning. That is not a bug. Kubernetes is built to be flexible. But that flexibility means the responsibility of setting boundaries falls entirely on you.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">That is what a policy engine does. And Kyverno is the simplest one built for Kubernetes today.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This guide covers what Kyverno is, why it is the right choice for most teams, how it works inside Kubernetes, and how to write your first three policies from scratch. Every command is verified against the official Kyverno documentation (v1.17.1, released February 2026).</p>
<h2 id="what-is-kyverno" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">What Is Kyverno?</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Kyverno is a policy engine built specifically for Kubernetes. The name comes from the Greek word for “govern.”</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Its defining feature: Kyverno policies are Kubernetes resources written in plain YAML. You do not need to learn a new language or a new syntax. If you can read a Deployment manifest, you can read a Kyverno policy.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Kyverno is a CNCF Incubating project under the Cloud Native Computing Foundation (CNCF), the same organization that oversees Kubernetes, Prometheus, and Argo.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">It does three things that cover the vast majority of real-world policy needs:</p>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Validate</strong> — check a resource against your rules, and reject it if it does not comply</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Mutate</strong> — automatically add or correct fields in a resource before it is stored</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Generate</strong> — automatically create a new resource when another resource is created</li>
</ul>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Each of these is covered with a working example below.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Official documentation: <a href="https://kyverno.io/docs/" class="text-white underline decoration-white/30 underline-offset-[3px]">kyverno.io/docs</a></p>
<h2 id="why-kyverno-and-not-opa-gatekeeper" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Why Kyverno and Not OPA Gatekeeper?</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">If you have researched Kubernetes policy engines, you have likely seen two names: Kyverno and Open Policy Agent (OPA) Gatekeeper. Both are CNCF projects. Both solve the same core problem. The difference is how you write policies.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">OPA Gatekeeper uses a language called Rego, a purpose-built query language for policy logic. It is powerful and works across platforms beyond Kubernetes. But Rego has a genuine learning curve, and most platform teams need dedicated time to become productive with it.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Kyverno uses YAML, the same format your team already uses for everything in the cluster.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">For a team working primarily in Kubernetes, Kyverno is the faster choice to get running and the easier one to maintain. Your team can read, review, and update policies without learning anything new. That matters when a policy needs to change quickly.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">OPA Gatekeeper is the better choice if you need policy enforcement that spans multiple platforms beyond Kubernetes. If your scope is Kubernetes, Kyverno is the more practical option.</p>
<h2 id="how-kyverno-works" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">How Kyverno Works</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The short version: Kyverno intercepts your <code>kubectl apply</code> before the resource is saved. If it breaks a rule, it never gets created.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Every time you run <code>kubectl apply</code>, the request travels to the Kubernetes API server. Before that request is written to the cluster&#x27;s database (etcd), the API server passes it through admission controllers — a chain of plugins that can inspect, modify, or reject the request.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Kyverno registers itself as two of these:</p>
<ol class="my-6 list-decimal space-y-3 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">A MutatingAdmissionWebhook</strong> — receives the request first and can change it. This is how mutation works.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">A ValidatingAdmissionWebhook</strong> — receives the request after mutation and can approve or reject it. This is how validation works.</li>
</ol>
<figure class="my-8 rounded-2xl border border-white/10 bg-neutral-950 px-6 py-10 text-center"><figcaption class="font-mono text-[10px] uppercase tracking-[0.18em] text-neutral-500">Diagram</figcaption></figure>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This happens before the resource exists. A pod that violates a policy is not created and then flagged — it is never created at all. The error comes back to your terminal immediately, with the exact message you wrote in your policy.</p>
<h2 id="installing-kyverno" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Installing Kyverno</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Helm is the recommended installation method. Run these three commands:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash"># Step 1 — Add the Kyverno Helm repository
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update

# Step 2 - Install Kyverno into its own namespace
helm install kyverno kyverno/kyverno \
  -n kyverno \
  --create-namespace

# Step 3 - Verify the installation
kubectl get pods -n kyverno
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">You should see four pods running:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-text">kyverno-admission-controller-xxx      1/1   Running
kyverno-background-controller-xxx     1/1   Running
kyverno-cleanup-controller-xxx        1/1   Running
kyverno-reports-controller-xxx        1/1   Running
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">Important</strong>: Always install Kyverno in a dedicated namespace. Never place it in <code>kube-system</code> or alongside other workloads. Kyverno automatically excludes its own namespace from policy enforcement, any workload sharing that namespace is excluded too.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Full installation guide: <a href="https://kyverno.io/docs/installation/methods/" class="text-white underline decoration-white/30 underline-offset-[3px]">kyverno.io/docs/installation/methods</a></p>
<h2 id="clusterpolicy-vs-policy" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">ClusterPolicy vs Policy</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Before writing a policy, you need to choose its scope.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Policies can be defined as cluster-wide resources using <code>ClusterPolicy</code> or as namespaced resources using <code>Policy</code>. Namespaced policies will only apply to resources within the namespace in which they are defined, while cluster-wide policies apply to matching resources across all namespaces. Otherwise there is no difference between the two types.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Use <code>ClusterPolicy</code> for rules that every team must follow, like no root containers or required labels.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Use <code>Policy</code> when a single team needs their own rules inside their own namespace.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">All examples in this guide use <code>ClusterPolicy</code>.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]"><strong class="font-semibold text-white">Note on ClusterPolicy</strong>: Kyverno 1.17 officially marks <code>ClusterPolicy</code> as deprecated. While it remains fully functional in this release, the Kyverno team recommends new policies be based on the new CEL APIs going forward. However, <code>ClusterPolicy</code> is still the right starting point for learning Kyverno. Once you understand the basics here, you can explore the newer <code>ValidatingPolicy</code> and <code>MutatingPolicy</code> types in the official docs.</p>
<h2 id="validate-policies-check-resources-before-they-are-created" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Validate Policies: Check Resources Before They Are Created</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">A validate policy checks whether a resource follows your rules. If it does not, Kubernetes rejects the request and shows your error message to the developer.</p>
<h3 id="two-modes-audit-and-enforce" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Two modes: Audit and Enforce</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Every validate rule has a <code>failureAction</code> field.</p>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Audit</strong> — the resource is allowed through, but the violation is recorded in a Policy Report. Nothing breaks. You can see what is non-compliant before you start blocking anything.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Enforce</strong> — the resource is rejected immediately. The developer sees your error message in their terminal.</li>
</ul>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Start with Audit. Then switch to Enforce once you are confident.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This is the most important habit when using Kyverno. If you apply a new policy in Enforce mode without checking your cluster first, you may accidentally block applications that are already running.</p>
<h3 id="example-1-require-a-label-on-every-pod" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Example 1: Require a Label on Every Pod</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Labels are how Kubernetes resources find each other. A Service finds its Pods using labels. A NetworkPolicy selects targets using labels. Without consistent labels, your cluster becomes harder to manage.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The <code>failureAction</code> tells Kyverno if the resource being validated should be allowed but reported (Audit) or blocked (Enforce). The <code>pattern</code> object defines what pattern will be checked in the resource.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This policy checks that every Pod has an <code>app.kubernetes.io/name</code> label:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-yaml"># require-labels.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-labels
spec:
  rules:
    - name: check-for-labels
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        failureAction: Audit
        message: &quot;Every Pod must have an &#x27;app.kubernetes.io/name&#x27; label.&quot;
        pattern:
          metadata:
            labels:
              app.kubernetes.io/name: &quot;?*&quot;
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Apply it:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash">kubectl apply -f require-labels.yaml
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">What the fields mean:</p>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><code>match.any</code> — which resources this rule applies to. Here, all Pods.</li>
<li class="flex items-start gap-2.5"><code>validate.failureAction</code> — set to Audit so violations are logged but not blocked.</li>
<li class="flex items-start gap-2.5"><code>validate.message</code> — the error message shown to the developer when the rule fails.</li>
<li class="flex items-start gap-2.5"><code>validate.pattern</code> — the shape the resource must match. <code>&quot;?*&quot;</code> means any non-empty string.</li>
</ul>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Test with a pod that has no label:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash">kubectl run test-pod --image=nginx:1.27 --restart=Never
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">In Audit mode the pod is created. Check the violation report:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash">kubectl get policyreport -A
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Now change <code>failureAction</code> to Enforce in the YAML, apply it again, and try the same command. The pod is rejected immediately.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">A pod that passes the policy:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash">kubectl run labeled-pod \
  --image=nginx:1.27 \
  --restart=Never \
  --labels=&quot;app.kubernetes.io/name=web&quot;
</code></pre>
<h3 id="example-2-block-containers-that-run-as-root" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Example 2: Block Containers That Run as Root</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">In Linux, user ID 0 is root. A container running as root has full administrator access on the host machine it runs on. If that container is compromised, the attacker can cause much more damage than they should be able to.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This policy blocks any Pod that does not explicitly set <code>runAsNonRoot: true</code>:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-yaml"># disallow-root.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-root
spec:
  rules:
    - name: check-runasnonroot
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        failureAction: Enforce
        message: &quot;Containers must not run as root. Set spec.securityContext.runAsNonRoot: true.&quot;
        pattern:
          spec:
            securityContext:
              runAsNonRoot: true
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Apply and test:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash">kubectl apply -f disallow-root.yaml

# This will be rejected
kubectl run root-pod --image=nginx:1.27 --restart=Never

# This will be allowed
kubectl run safe-pod \
  --image=nginx:1.27 \
  --restart=Never \
  --overrides=&#x27;{&quot;spec&quot;:{&quot;securityContext&quot;:{&quot;runAsNonRoot&quot;:true,&quot;runAsUser&quot;:1000}}}&#x27;
</code></pre>
<h3 id="example-3-block-the-latest-image-tag" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Example 3: Block the latest Image Tag</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The <code>latest</code> tag means &quot;whatever the registry currently considers latest.&quot; It changes without notice, which makes deployments unpredictable and rollbacks unreliable. This policy blocks any container using the <code>latest</code> tag:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-yaml"># disallow-latest-tag.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-latest-tag
spec:
  rules:
    - name: check-image-tag
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        failureAction: Enforce
        message: &quot;The &#x27;latest&#x27; tag is not allowed. Use an explicit version tag like nginx:1.27.&quot;
        pattern:
          spec:
            containers:
              - image: &quot;!*:latest&quot;
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Apply and test:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash">kubectl apply -f disallow-latest-tag.yaml

# Rejected
kubectl run bad-pod --image=nginx:latest --restart=Never

# Allowed
kubectl run good-pod --image=nginx:1.27 --restart=Never
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Browse hundreds of ready-to-use policies: <a href="https://kyverno.io/policies" class="text-white underline decoration-white/30 underline-offset-[3px]">kyverno.io/policies</a></p>
<h2 id="mutate-policies-fix-resources-automatically" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Mutate Policies: Fix Resources Automatically</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">A mutate policy changes a resource before it is saved. The developer submits it, Kyverno adjusts it in the background, and the corrected version is what gets stored.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This is friendlier than validation. Instead of rejecting a pod and sending a developer back to fix their YAML, Kyverno quietly adds what is missing and lets the work continue.</p>
<h3 id="the-noisy-neighbor-problem" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">The noisy neighbor problem</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">One of the most common production issues in Kubernetes is the “noisy neighbor.” A pod with no resource limits gradually takes all the CPU or memory on a node, slowing down every other application sharing that machine. The developer did not do this deliberately. They just forgot to set limits.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">A validate policy would reject their pod. A mutate policy adds safe defaults and lets the pod through.</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-yaml"># add-default-resource-limits.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-default-resource-limits
spec:
  rules:
    - name: set-resource-limits
      match:
        any:
          - resources:
              kinds:
                - Pod
      mutate:
        patchStrategicMerge:
          spec:
            containers:
              - (name): &quot;*&quot;
                resources:
                  limits:
                    +(memory): &quot;256Mi&quot;
                    +(cpu): &quot;500m&quot;
                  requests:
                    +(memory): &quot;128Mi&quot;
                    +(cpu): &quot;250m&quot;
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Apply and check the result:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash">kubectl apply -f add-default-resource-limits.yaml

# Create a pod with no resource limits
kubectl run test-pod --image=nginx:1.27 --restart=Never

# See what Kyverno added automatically
kubectl get pod test-pod -o jsonpath=&#x27;{.spec.containers[0].resources}&#x27;
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Two pieces of Kyverno syntax used here:</p>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><code>(name): &quot;*&quot;</code> — the parentheses make this a conditional anchor. It matches every container by name and applies the mutation to each one.</li>
<li class="flex items-start gap-2.5"><code>+(memory)</code> — the plus sign means &quot;only add this field if it does not already exist.&quot; Pods that already have resource limits set by the developer are not changed.</li>
</ul>
<h2 id="generate-policies-create-resources-automatically" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Generate Policies: Create Resources Automatically</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">A generate policy creates a new Kubernetes resource automatically when you create another resource. It turns manual steps into automatic ones.</p>
<h3 id="example-create-a-networkpolicy-for-every-new-namespace" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Example: Create a NetworkPolicy for every new Namespace</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The safest default for network traffic between pods is to block everything and only allow what you explicitly permit. But creating a NetworkPolicy in every new namespace is easy to forget.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This policy does it automatically:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-yaml"># generate-default-networkpolicy.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: generate-default-networkpolicy
spec:
  rules:
    - name: create-deny-all-networkpolicy
      match:
        any:
          - resources:
              kinds:
                - Namespace
      generate:
        apiVersion: networking.k8s.io/v1
        kind: NetworkPolicy
        name: deny-all-ingress
        namespace: &quot;{{request.object.metadata.name}}&quot;
        synchronize: true
        data:
          spec:
            podSelector: {}
            policyTypes:
              - Ingress
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Apply and test:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash">kubectl apply -f generate-default-networkpolicy.yaml

# Create a new namespace
kubectl create namespace my-team

# Kyverno created this automatically
kubectl get networkpolicy -n my-team
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The <code>synchronize: true</code> field means if the generated resource is ever deleted manually, Kyverno will recreate it. It is an ongoing guarantee, not a one-time action.</p>
<h2 id="testing-policies-before-you-apply-them" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Testing Policies Before You Apply Them</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The Kyverno CLI lets you test a policy against a YAML file on your own machine. Nothing touches the cluster. This is the safest way to develop policies and the foundation of a “shift left” approach, catching policy violations on your laptop or in a CI pipeline, before anything reaches the cluster.</p>
<h3 id="install-the-cli" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Install the CLI</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">macOS:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash">brew install kyverno
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Linux (via Krew):</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash">kubectl krew install kyverno
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Check it is working:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash">kubectl kyverno version
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">CLI documentation: <a href="https://kyverno.io/docs/kyverno-cli" class="text-white underline decoration-white/30 underline-offset-[3px]">kyverno.io/docs/kyverno-cli</a></p>
<h3 id="test-a-validation-policy" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Test a validation policy</h3>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Create a test pod manifest:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-yaml"># test-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
    - name: nginx
      image: nginx:1.27
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Run the test:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash">kubectl kyverno apply require-labels.yaml --resource test-pod.yaml
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">You will see:</p>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-text">policy require-labels -&gt; resource default/Pod/test-pod
  FAILED: Every Pod must have an &#x27;app.kubernetes.io/name&#x27; label.
pass: 0, fail: 1, warn: 0, error: 0, skip: 0
</code></pre>
<h3 id="test-a-mutation-policy" class="mt-10 mb-3 text-lg font-semibold text-neutral-100 sm:text-xl">Test a mutation policy</h3>
<pre class="my-6 overflow-x-auto rounded-xl border border-white/10 bg-neutral-950 p-4 text-sm leading-6"><code class="language-bash">kubectl kyverno apply add-default-resource-limits.yaml --resource test-pod.yaml
</code></pre>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">This prints the mutated resource so you can see exactly what Kyverno would add.</p>
<h2 id="best-practices" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Best Practices</h2>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Start with Audit, move to Enforce.</strong> Apply every new policy in Audit mode first. Check the policy report. Fix non-compliant resources. Then switch to Enforce.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Use +(field) for mutation defaults.</strong> The plus sign only fills a field when it is absent. Without it, your mutation will overwrite values that developers have already set deliberately.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Store policies in Git.</strong> Treat your ClusterPolicy files like application code. Review changes in pull requests. Apply them through a GitOps pipeline so your cluster’s rules are always in a known, tracked state.</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Test with the CLI in CI.</strong> Add <code>kubectl kyverno apply</code> to your pipeline after generating manifests. If any manifest fails a policy, the pipeline stops. Policy violations are caught in code review, not after deployment.</li>
</ul>
<h2 id="summary" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">Summary</h2>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Kubernetes is flexible by design. Kyverno adds the rules your team needs, without requiring a new language.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The three policy types cover most real-world needs:</p>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Validate</strong> rejects resources that break your rules before they are ever created</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Mutate</strong> quietly fills in missing fields so developers do not have to remember everything</li>
<li class="flex items-start gap-2.5"><strong class="font-semibold text-white">Generate</strong> replaces manual steps with automatic guarantees</li>
</ul>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">The safest way to roll out any new policy is: write it, test it with the CLI, apply it in Audit mode, check the policy report, fix any issues, then switch to Enforce.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Start with the require-labels example. It is simple, it immediately improves how your cluster is managed, and it teaches you the full Kyverno YAML structure in one small policy.</p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">Policy library: <a href="https://kyverno.io/policies" class="text-white underline decoration-white/30 underline-offset-[3px]">kyverno.io/policies</a></p>
<p class="mb-6 text-[17px] leading-[1.75] text-neutral-200 sm:text-[19px]">If this guide helped you get Kyverno running, consider sharing it with your team. If you are curious about the next step, whether writing policies for multi-environment clusters or exploring the new CEL-based policy types in Kyverno, let me know in the comments. I will cover it in the next article.</p>
<h2 id="references" class="mt-14 mb-5 border-t border-white/10 pt-8 text-xl font-semibold text-white sm:text-2xl">References</h2>
<ul class="my-6 list-none space-y-3 border-l border-white/10 pl-5 text-[17px] leading-[1.7] text-neutral-200">
<li class="flex items-start gap-2.5">Kyverno documentation: <a href="https://kyverno.io/docs/introduction/" class="text-white underline decoration-white/30 underline-offset-[3px]">kyverno.io/docs/introduction/</a></li>
<li class="flex items-start gap-2.5">How Kyverno works: <a href="https://kyverno.io/docs/introduction/how-kyverno-works" class="text-white underline decoration-white/30 underline-offset-[3px]">kyverno.io/docs/introduction/how-kyverno-works</a></li>
<li class="flex items-start gap-2.5">Quick start guide: <a href="https://kyverno.io/docs/introduction/quick-start" class="text-white underline decoration-white/30 underline-offset-[3px]">kyverno.io/docs/introduction/quick-start</a></li>
<li class="flex items-start gap-2.5">Validate rules reference: <a href="https://kyverno.io/docs/policy-types/cluster-policy/validate" class="text-white underline decoration-white/30 underline-offset-[3px]">kyverno.io/docs/policy-types/cluster-policy/validate</a></li>
<li class="flex items-start gap-2.5">Mutate rules reference: <a href="https://kyverno.io/docs/policy-types/cluster-policy/mutate" class="text-white underline decoration-white/30 underline-offset-[3px]">kyverno.io/docs/policy-types/cluster-policy/mutate</a></li>
<li class="flex items-start gap-2.5">Generate rules reference: <a href="https://kyverno.io/docs/policy-types/cluster-policy/generate" class="text-white underline decoration-white/30 underline-offset-[3px]">kyverno.io/docs/policy-types/cluster-policy/generate</a></li>
<li class="flex items-start gap-2.5">Installation methods: <a href="https://kyverno.io/docs/installation/methods" class="text-white underline decoration-white/30 underline-offset-[3px]">kyverno.io/docs/installation/methods</a></li>
<li class="flex items-start gap-2.5">Kyverno CLI: <a href="https://kyverno.io/docs/kyverno-cli" class="text-white underline decoration-white/30 underline-offset-[3px]">kyverno.io/docs/kyverno-cli</a></li>
<li class="flex items-start gap-2.5">Kyverno policy library: <a href="https://kyverno.io/policies" class="text-white underline decoration-white/30 underline-offset-[3px]">kyverno.io/policies</a></li>
<li class="flex items-start gap-2.5">Kyverno version referenced: v1.17.0, released February 2026</li>
</ul>]]></content:encoded>
    </item>
  </channel>
</rss>
