<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sohana Akbar</title>
    <description>The latest articles on DEV Community by Sohana Akbar (@sohanaakbar7).</description>
    <link>https://dev.to/sohanaakbar7</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3878706%2Fb026acb6-e832-44ba-9999-2c0e44f18218.jpg</url>
      <title>DEV Community: Sohana Akbar</title>
      <link>https://dev.to/sohanaakbar7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sohanaakbar7"/>
    <language>en</language>
    <item>
      <title>Optimizing Core Web Vitals Using CDN + Caching Headers</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Thu, 18 Jun 2026 18:22:35 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/optimizing-core-web-vitals-using-cdn-caching-headers-2lfd</link>
      <guid>https://dev.to/sohanaakbar7/optimizing-core-web-vitals-using-cdn-caching-headers-2lfd</guid>
      <description>&lt;p&gt;If you care about SEO, user experience, and conversions, you need to optimize Core Web Vitals. These are Google's real-world performance metrics that measure how fast and stable your website feels to users. Let's explore how a CDN paired with smart caching headers can dramatically improve these scores.&lt;/p&gt;

&lt;p&gt;Understanding Core Web Vitals&lt;br&gt;
Core Web Vitals consist of three key metrics that directly impact user experience and search rankings:&lt;/p&gt;

&lt;p&gt;Largest Contentful Paint (LCP): Loading performance, target under 2.5 seconds&lt;/p&gt;

&lt;p&gt;Interaction to Next Paint (INP): Responsiveness, target under 200ms&lt;/p&gt;

&lt;p&gt;Cumulative Layout Shift (CLS): Visual stability, target under 0.1&lt;/p&gt;

&lt;p&gt;A modern CDN is one of the fastest and most effective ways to improve these metrics, particularly LCP. According to HTTP Archive data, origins using CDN services show significantly higher percentages of "good" Core Web Vitals scores.&lt;/p&gt;

&lt;p&gt;Why CDN + Caching Headers Work Together&lt;br&gt;
The CDN Advantage&lt;br&gt;
A CDN creates a distributed network of edge servers that cache and deliver content from locations closest to your users. This global infrastructure dramatically reduces latency while handling traffic spikes without overloading origin servers.&lt;/p&gt;

&lt;p&gt;CDN integration helps reduce latency for geographically distributed users, especially those far from the origin server, which directly improves metrics like First Contentful Paint and Largest Contentful Paint.&lt;/p&gt;

&lt;p&gt;Modern CDNs can also:&lt;/p&gt;

&lt;p&gt;Cache API responses at the edge, protecting your origin from excessive requests&lt;/p&gt;

&lt;p&gt;Serve "stale" content when your origin is slow or down, maintaining user experience&lt;/p&gt;

&lt;p&gt;Dynamically optimize images at the edge—resizing, compressing, or converting based on device and screen size&lt;/p&gt;

&lt;p&gt;Handle high-volume crawler requests without sacrificing performance&lt;/p&gt;

&lt;p&gt;The Caching Headers Strategy&lt;br&gt;
Caching headers tell browsers and CDNs how long to store resources. When properly configured, they reduce server load and deliver content faster.&lt;/p&gt;

&lt;p&gt;For static assets with file hashes:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
Cache-Control: public, max-age=31536000&lt;br&gt;
This allows long-term caching. When you update the file, the hash changes, and users automatically get the new version.&lt;/p&gt;

&lt;p&gt;For HTML pages:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
Cache-Control: public, max-age=3600&lt;br&gt;
Differentiate between marketing content (longer cache) and personalized content (private/no cache).&lt;/p&gt;

&lt;p&gt;For API responses (headless CMS example):&lt;/p&gt;

&lt;p&gt;json&lt;br&gt;
{&lt;br&gt;
  "PathPattern": "/api/*",&lt;br&gt;
  "DefaultTTL": 3600,&lt;br&gt;
  "MaxTTL": 86400,&lt;br&gt;
  "MinTTL": 300&lt;br&gt;
}&lt;br&gt;
This configuration provides 1-hour caching for API responses, 24-hour maximum, and 5-minute minimum.&lt;/p&gt;

&lt;p&gt;Implementation Strategies&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Static Site Generation + Edge Caching
Headless CMS architecture enables Static Site Generation, which eliminates JavaScript rendering delays and reduces LCP through pre-rendered HTML. Combined with edge caching, SSG delivers fast TTFB, fast FCP, and lower Total Blocking Time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;API responses from headless CMS systems enable aggressive caching strategies because content separates cleanly from presentation logic. This creates caching patterns that would cause stale page issues in traditional systems.&lt;/p&gt;

&lt;p&gt;When content updates occur, invalidate specific API endpoint caches through webhooks while leaving static assets untouched, or trigger incremental rebuilds that update only affected pages.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Image Optimization at the Edge
LCP is usually a hero image, large heading, or background banner. A CDN can dramatically optimize images by storing copies across distributed servers and serving from the closest location.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Implementation example:&lt;/p&gt;

&lt;p&gt;html&lt;br&gt;
&lt;a href="" class="article-body-image-wrapper"&gt;&lt;img&gt;&lt;/a&gt;
  src="hero.webp" &lt;br&gt;
  width="1200" &lt;br&gt;
  height="600" &lt;br&gt;
  loading="eager" &lt;br&gt;
  fetchpriority="high" &lt;br&gt;
  alt="Hero Banner"&lt;br&gt;
/&amp;gt;&lt;br&gt;
Modern image formats like WebP and AVIF, combined with explicit dimensions, prevent layout shifts and speed loading. CDNs can handle this transformation automatically.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Smart Cache Invalidation
One of the biggest challenges is updating cached content. Modern CDNs handle this through:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Webhook-triggered cache purging (Cloudflare example):&lt;/p&gt;

&lt;p&gt;json&lt;br&gt;
{&lt;br&gt;
  "url": "&lt;a href="https://api.cloudflare.com/client/v4/zones/%7Bzone_id%7D/purge_cache" rel="noopener noreferrer"&gt;https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache&lt;/a&gt;",&lt;br&gt;
  "headers": {&lt;br&gt;
    "Authorization": "Bearer YOUR_API_TOKEN",&lt;br&gt;
    "Content-Type": "application/json"&lt;br&gt;
  },&lt;br&gt;
  "body": {&lt;br&gt;
    "files": [&lt;br&gt;
      "&lt;a href="https://example.com/api/posts/*" rel="noopener noreferrer"&gt;https://example.com/api/posts/*&lt;/a&gt;"&lt;br&gt;
    ]&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
This approach allows selective cache clearing while keeping static assets cached.&lt;/p&gt;

&lt;p&gt;Measuring Impact&lt;br&gt;
Monitor Real User Data&lt;br&gt;
Use Google PageSpeed Insights, Lighthouse, and Search Console&lt;/p&gt;

&lt;p&gt;Track LCP, INP, and CLS from real users&lt;/p&gt;

&lt;p&gt;Monitor caching hit ratios to verify effectiveness&lt;/p&gt;

&lt;p&gt;Expected Improvements&lt;br&gt;
40-60% latency reduction through edge caching&lt;/p&gt;

&lt;p&gt;60-70% mobile page weight reduction through image optimization&lt;/p&gt;

&lt;p&gt;10x faster navigation for back/forward cache (bfcache) restores&lt;/p&gt;

&lt;p&gt;Advanced Considerations&lt;br&gt;
Beware the no-store Pitfall&lt;br&gt;
Some CMS platforms set Cache-Control: no-store on all responses, which prevents browsers from using the back-forward cache (bfcache). Since bfcache restores are on average 10x faster than normal navigation, this is a significant performance loss. Review your platform's caching settings to ensure no-store isn't blocking bfcache for cacheable pages.&lt;/p&gt;

&lt;p&gt;Cache API Responses&lt;br&gt;
API traffic is often mistakenly considered "too dynamic" to benefit from CDN caching. However, caching API responses at the edge is one of the easiest performance wins—reducing origin load and improving reliability for partner services that depend on your APIs.&lt;/p&gt;

&lt;p&gt;Security and SEO&lt;br&gt;
CDNs with built-in DDoS protection, bot protection, and TLS encryption improve both security and SEO rankings. Google's algorithm updates have repeatedly made it clear that website security heavily influences ranking factors.&lt;/p&gt;

&lt;p&gt;Conclusion&lt;br&gt;
Optimizing Core Web Vitals with CDN and caching headers isn't about chasing scores—it's about building fast, stable, delightful experiences for users and search engines.&lt;/p&gt;

&lt;p&gt;Key takeaways:&lt;/p&gt;

&lt;p&gt;Use a modern CDN with edge computing capabilities&lt;/p&gt;

&lt;p&gt;Set appropriate cache-control headers for different resource types&lt;/p&gt;

&lt;p&gt;Optimize images at the edge using WebP/AVIF&lt;/p&gt;

&lt;p&gt;Cache API responses to reduce origin load&lt;/p&gt;

&lt;p&gt;Implement webhook-based cache purging for updates&lt;/p&gt;

&lt;p&gt;Monitor real-user metrics and iterate&lt;/p&gt;

&lt;p&gt;Small optimizations can lead to massive ranking and conversion improvements. Start measuring today, implement these techniques, and watch your Core Web Vitals scores improve.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Build-Time vs Runtime Environment Variables in Vite and Next.js: The Hidden Trap That Catches Everyone</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Wed, 17 Jun 2026 06:38:55 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/build-time-vs-runtime-environment-variables-in-vite-and-nextjs-the-hidden-trap-that-catches-15mi</link>
      <guid>https://dev.to/sohanaakbar7/build-time-vs-runtime-environment-variables-in-vite-and-nextjs-the-hidden-trap-that-catches-15mi</guid>
      <description>&lt;p&gt;Picture this: you've built a beautiful web app, shipped it to production, and everything works perfectly. Then you realize—you need to change your API URL for staging. Simple, right? Just update the .env file and restart.&lt;/p&gt;

&lt;p&gt;Wrong. Your frontend is still pointing to the old URL, and you're staring at a broken app wondering what went wrong. I've been there, and it's not fun.&lt;/p&gt;

&lt;p&gt;The culprit? A fundamental misunderstanding of when environment variables get read in your build process. Let's fix that.&lt;/p&gt;

&lt;p&gt;The Core Distinction&lt;br&gt;
Environment variables in frontend frameworks fall into two categories, and confusing them will either leak your secrets to the browser or lock you into rebuild hell:&lt;/p&gt;

&lt;p&gt;Build-time variables are injected into your JavaScript bundle during npm run build. They become static strings burned into your output files. Change them? You'll need a new build and deploy .&lt;/p&gt;

&lt;p&gt;Runtime variables are read by server-side code at request time. They're never shipped to the browser, and changing them takes effect on the next request—no rebuild required .&lt;/p&gt;

&lt;p&gt;The Prefix Convention Trap&lt;br&gt;
Both Vite and Next.js use a prefix convention to decide what gets exposed to the browser. And this is where most developers slip up.&lt;/p&gt;

&lt;p&gt;Vite: The VITE_ Rule&lt;br&gt;
Vite only exposes variables prefixed with VITE_ to your client-side code. Everything else stays server-side only .&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
// .env&lt;br&gt;
VITE_API_URL=&lt;a href="https://api.example.com" rel="noopener noreferrer"&gt;https://api.example.com&lt;/a&gt;&lt;br&gt;
DB_PASSWORD=supersecret&lt;/p&gt;

&lt;p&gt;// In your code&lt;br&gt;
console.log(import.meta.env.VITE_API_URL) // "&lt;a href="https://api.example.com" rel="noopener noreferrer"&gt;https://api.example.com&lt;/a&gt;" ✅&lt;br&gt;
console.log(import.meta.env.DB_PASSWORD) // undefined ❌&lt;br&gt;
The catch? Once you build your Vite app, these VITE_ variables are hardcoded into the bundle. That VITE_API_URL is now a permanent string in your JavaScript files .&lt;/p&gt;

&lt;p&gt;Next.js: The NEXT_PUBLIC_ Rule&lt;br&gt;
Next.js follows a similar pattern with NEXT_PUBLIC_. Variables without this prefix are only available on the server .&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
// .env&lt;br&gt;
NEXT_PUBLIC_ANALYTICS_ID=abc123&lt;br&gt;
DATABASE_URL=postgres://localhost:5432/myapp&lt;/p&gt;

&lt;p&gt;// In your component&lt;br&gt;
process.env.NEXT_PUBLIC_ANALYTICS_ID // "abc123" ✅ Available client-side&lt;br&gt;
process.env.DATABASE_URL // undefined in browser ❌&lt;br&gt;
The Big Problem: Single Docker Image, Multiple Environments&lt;br&gt;
Here's where this gets really painful. Modern deployment practices favor building once and promoting the same artifact through multiple environments (staging, QA, production).&lt;/p&gt;

&lt;p&gt;But with VITE_ and NEXT_PUBLIC_ variables, you can't do this. They're baked in at build time . If you build with staging values and promote that same build to production, you're still pointing to staging.&lt;/p&gt;

&lt;p&gt;This is why the official Next.js docs explicitly warn: "We do not recommend using the runtimeConfig option, as this does not work with the standalone output mode" .&lt;/p&gt;

&lt;p&gt;The Server-Side Escape Hatch&lt;br&gt;
Next.js offers a way out if you're using the App Router. By reading environment variables in server components, you can access runtime values :&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
import { connection } from 'next/server'&lt;/p&gt;

&lt;p&gt;export default async function Component() {&lt;br&gt;
  await connection() // Opt into dynamic rendering&lt;br&gt;
  const value = process.env.MY_VALUE // Evaluated at runtime!&lt;br&gt;
  // ...&lt;br&gt;
}&lt;br&gt;
This approach lets you use a single Docker image across multiple environments. The value is read when the request comes in, not when the app was built.&lt;/p&gt;

&lt;p&gt;Beyond the Prefix: Workarounds for Runtime Variables&lt;br&gt;
The vite-envs Plugin&lt;br&gt;
A clever plugin called vite-envs flips the script entirely. Instead of baking variables at build time, it generates a script that injects environment variables when the container starts .&lt;/p&gt;

&lt;p&gt;The Dockerfile becomes:&lt;/p&gt;

&lt;p&gt;dockerfile&lt;br&gt;
ENTRYPOINT sh -c "./vite-envs.sh &amp;amp;&amp;amp; nginx -g 'daemon off;'"&lt;br&gt;
Now your Vite app can respond to environment variables at runtime, just like a backend would .&lt;/p&gt;

&lt;p&gt;The HTML Injection Pattern&lt;br&gt;
Another approach involves generating a preprocessed.js file that exposes environment variables on window.process.env and loading it before your main bundle .&lt;/p&gt;

&lt;p&gt;html&lt;/p&gt;

&lt;p&gt;This pattern works across frameworks and gives you complete control over what gets exposed.&lt;/p&gt;

&lt;p&gt;Security: The Elephant in the Room&lt;br&gt;
Here's the rule you can't break: Never put secrets in build-time variables.&lt;/p&gt;

&lt;p&gt;Build-time variables are inlined into your JavaScript bundle. Anyone can open DevTools and read them. VITE_ and NEXT_PUBLIC_ variables are visible to anyone who inspects your site .&lt;/p&gt;

&lt;p&gt;Database passwords, API secrets, admin keys—these belong on the server, period.&lt;/p&gt;

&lt;p&gt;Which Approach Should You Choose?&lt;br&gt;
Use build-time variables for:&lt;/p&gt;

&lt;p&gt;Public API keys&lt;/p&gt;

&lt;p&gt;Public analytics IDs&lt;/p&gt;

&lt;p&gt;Feature flags that don't change per environment&lt;/p&gt;

&lt;p&gt;Anything you'd be comfortable sharing publicly&lt;/p&gt;

&lt;p&gt;Use runtime variables (server-side) for:&lt;/p&gt;

&lt;p&gt;Database credentials&lt;/p&gt;

&lt;p&gt;Private API tokens&lt;/p&gt;

&lt;p&gt;Dynamic values that differ across environments&lt;/p&gt;

&lt;p&gt;Configuration you want to change without rebuilding&lt;/p&gt;

&lt;p&gt;The Bottom Line&lt;br&gt;
The build-time vs runtime distinction isn't just theoretical—it's the difference between smooth deployments and "why is my staging URL in production?" panic.&lt;/p&gt;

&lt;p&gt;If you're building a single Docker image to promote across environments, you need to think carefully about how you handle environment variables. The default prefix-based approach won't work for runtime configuration.&lt;/p&gt;

&lt;p&gt;Plan your architecture around when variables are read, not just how they're named. Your future self—and your team—will thank you.&lt;/p&gt;

&lt;p&gt;What's your approach to environment variables in frontend builds? Have you run into the single-image, multiple-environments problem? Let's discuss in the comments.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>K9s — The Terminal UI That Made Me Love K8s Again</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Mon, 15 Jun 2026 14:58:20 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/k9s-the-terminal-ui-that-made-me-love-k8s-again-2kgp</link>
      <guid>https://dev.to/sohanaakbar7/k9s-the-terminal-ui-that-made-me-love-k8s-again-2kgp</guid>
      <description>&lt;p&gt;Let me be honest with you.&lt;/p&gt;

&lt;p&gt;For the first two years of my Kubernetes journey, I was that person. You know the one. The person who says, “Kubernetes is amazing, but the CLI friction kills my flow.”&lt;/p&gt;

&lt;p&gt;I had aliases for my aliases. I used kubectx and kubens religiously. I even built a messy bash script to tail logs from five pods at once. And still, debugging a rollout felt like doing surgery with oven mitts.&lt;/p&gt;

&lt;p&gt;Then I found K9s.&lt;/p&gt;

&lt;p&gt;And I swear to you: within ten minutes, I actually enjoyed managing my cluster again.&lt;/p&gt;

&lt;p&gt;The Problem: Too Much Typing, Not Enough Seeing&lt;br&gt;
Here’s the dirty secret of kubectl: it’s a single-purpose knife in a multi-course meal. Want to check pod status? kubectl get pods. Want to see logs? kubectl logs -f pod-name. Want to describe a deployment? New command. Scale it? Another command. Jump into a shell? You get the idea.&lt;/p&gt;

&lt;p&gt;The cognitive load isn't the cluster—it’s the context switching between commands and namespaces.&lt;/p&gt;

&lt;p&gt;K9s solves that by turning your terminal into a live, interactive cockpit.&lt;/p&gt;

&lt;p&gt;What Is K9s, Really?&lt;br&gt;
K9s is a Terminal UI (TUI) that watches your cluster in real time. It’s not a dashboard you open in a browser. It lives inside your terminal, respects your SSH config, uses your existing kubeconfig, and weighs essentially nothing.&lt;/p&gt;

&lt;p&gt;But the magic isn't the tech specs. The magic is the ergonomics.&lt;/p&gt;

&lt;p&gt;When you launch k9s, you don’t type --namespace. You don’t remember flags. You press : (colon) and type /pods, /deploy, /svc, or /ctx to switch contexts instantly.&lt;/p&gt;

&lt;p&gt;Let me show you how my workflow changed.&lt;/p&gt;

&lt;p&gt;Before vs. After K9s&lt;br&gt;
Before (pure kubectl)&lt;br&gt;
bash&lt;br&gt;
kubectl get pods -n myapp&lt;/p&gt;

&lt;h1&gt;
  
  
  oh, pod is crashing
&lt;/h1&gt;

&lt;p&gt;kubectl logs myapp-pod-7d8f9-abc -n myapp --tail=50&lt;/p&gt;

&lt;h1&gt;
  
  
  hmm, need to see events
&lt;/h1&gt;

&lt;p&gt;kubectl describe pod myapp-pod-7d8f9-abc -n myapp | grep -A 5 Events&lt;/p&gt;

&lt;h1&gt;
  
  
  ok scale down
&lt;/h1&gt;

&lt;p&gt;kubectl scale deploy/myapp -n myapp --replicas=0&lt;br&gt;
That’s 4 commands, 2 copy-pastes, and one grep.&lt;/p&gt;

&lt;p&gt;After (K9s)&lt;br&gt;
k9s (enter)&lt;/p&gt;

&lt;p&gt;: then ns (switch namespace to myapp)&lt;/p&gt;

&lt;p&gt;Arrow keys to the crashing pod → press l (logs automatically stream)&lt;/p&gt;

&lt;p&gt;Press d (describe) – reads like a man page, but instant&lt;/p&gt;

&lt;p&gt;Press /deploy → select deployment → press s (scale) → type 0 → enter&lt;/p&gt;

&lt;p&gt;No typing resource names. No looking up pod hashes. No --tail. No namespace typos.&lt;/p&gt;

&lt;p&gt;That’s the difference between “managing YAML” and “piloting a cluster.”&lt;/p&gt;

&lt;p&gt;The Features That Stole My Heart&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Vim-like navigation (if you know, you know)&lt;br&gt;
j/k to scroll, / to filter, : for commands, ? for help. It feels like home for anyone who lives in the terminal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Logs with infinite scroll and live tail&lt;br&gt;
Press l on any pod and watch logs update in real time. Press ctrl-s to save them to a file. Press esc to go back. No ctrl-c | kubectl logs dance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Popeye integration for cluster health&lt;br&gt;
K9s includes a built-in “Popeye” sanitizer. Press : then popeye and it scans your cluster for misconfigurations, deprecated APIs, and resource waste. It’s like a linter for your entire K8s setup.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Command mode for everything&lt;br&gt;
Want to restart a deployment? Select it, press r. Port-forward? Select pod, press shift-f. Shell into a container? Select pod, press s. Everything is two keystrokes away.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Screens and skins&lt;br&gt;
You can save multiple screens (pods + logs + events side by side) and customize the color theme. I use a purple-and-cyan theme that makes CrashLoopBackOff stand out like a warning light.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Real Talk: Is It Perfect?&lt;br&gt;
No. Nothing is.&lt;/p&gt;

&lt;p&gt;It’s not for scripting – obviously. You’ll still need kubectl in CI/CD.&lt;/p&gt;

&lt;p&gt;Large clusters can lag – if you have 2,000 pods, initial load takes a few seconds.&lt;/p&gt;

&lt;p&gt;The shortcut list is daunting at first. But after a week, you’ll be pressing ctrl-d to kill a pod without thinking.&lt;/p&gt;

&lt;p&gt;But here’s the thing: I don’t want it to replace kubectl. I want it to replace fatigue.&lt;/p&gt;

&lt;p&gt;How to Start (in 60 seconds)&lt;br&gt;
bash&lt;/p&gt;

&lt;h1&gt;
  
  
  macOS
&lt;/h1&gt;

&lt;p&gt;brew install k9s&lt;/p&gt;

&lt;h1&gt;
  
  
  Linux (via snap)
&lt;/h1&gt;

&lt;p&gt;sudo snap install k9s&lt;/p&gt;

&lt;h1&gt;
  
  
  Or download from GitHub releases
&lt;/h1&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://github.com/derailed/k9s/releases" rel="noopener noreferrer"&gt;https://github.com/derailed/k9s/releases&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Then just run:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
k9s&lt;br&gt;
That’s it. It reads your ~/.kube/config. It respects your current context. It just works.&lt;/p&gt;

&lt;p&gt;The Moment I Knew I Was Hooked&lt;br&gt;
We had a production incident. Three microservices failing. A ConfigMap typo. I needed to check logs from service A, events from namespace B, and scale down service C simultaneously.&lt;/p&gt;

&lt;p&gt;Normally, I’d have five terminal tabs open, each with a different kubectl command running.&lt;/p&gt;

&lt;p&gt;With K9s, I opened two splits in the same TUI: logs on the right, pod list on the left. I watched the error appear, fixed the ConfigMap in another window, and saw the pods restart in real time without refreshing anything.&lt;/p&gt;

&lt;p&gt;My lead engineer looked over and said, “What the hell is that? Install it on my machine right now.”&lt;/p&gt;

&lt;p&gt;That’s K9s.&lt;/p&gt;

&lt;p&gt;Final Verdict&lt;br&gt;
Kubernetes is powerful, but power without visibility is just chaos.&lt;/p&gt;

&lt;p&gt;K9s doesn’t make K8s easier — it makes it visible, tactile, and fast. It turns a firehose of YAML and API calls into a dashboard that fits inside a single terminal window.&lt;/p&gt;

&lt;p&gt;If you’re tired of typing kubectl get pods --all-namespaces | grep Pending, do yourself a favor.&lt;/p&gt;

&lt;p&gt;Try K9s for one day.&lt;/p&gt;

&lt;p&gt;You might just fall in love with K8s all over again.&lt;/p&gt;

&lt;p&gt;Bonus tip: Add alias kk='k9s' to your .zshrc and thank me later.&lt;/p&gt;

&lt;p&gt;Have you used K9s? What’s your favorite shortcut? Drop a comment below — I’ll trade you my skin config for yours. 🚀&lt;/p&gt;

</description>
      <category>cli</category>
      <category>kubernetes</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Ingress with CloudFlare + cert-manager: A Frontend-Friendly Tutorial</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Sun, 14 Jun 2026 17:56:11 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/ingress-with-cloudflare-cert-manager-a-frontend-friendly-tutorial-1g0m</link>
      <guid>https://dev.to/sohanaakbar7/ingress-with-cloudflare-cert-manager-a-frontend-friendly-tutorial-1g0m</guid>
      <description>&lt;p&gt;TL;DR: You’ve built an awesome frontend app. You have a Kubernetes cluster. But setting up HTTPS with Cloudflare proxy and automatic SSL certificates feels like backend black magic. Let’s fix that — no deep network engineering degree required.&lt;/p&gt;

&lt;p&gt;By the end of this, you’ll have:&lt;/p&gt;

&lt;p&gt;A Kubernetes Ingress routing traffic to your frontend service.&lt;/p&gt;

&lt;p&gt;Cloudflare as your DNS + proxy (your domain, e.g., app.example.com).&lt;/p&gt;

&lt;p&gt;cert-manager issuing free, auto-renewing Let’s Encrypt certificates.&lt;/p&gt;

&lt;p&gt;Full HTTPS + Cloudflare’s CDN/caching benefits.&lt;/p&gt;

&lt;p&gt;🧠 Mental model (the "what &amp;amp; why")&lt;br&gt;
Component   Job description (in plain English)&lt;br&gt;
Ingress The "traffic cop" that says: "Requests for app.example.com go to my frontend service"&lt;br&gt;
Cloudflare  The "front desk": hides your real server IP, provides SSL between user and Cloudflare, caches static assets&lt;br&gt;
cert-manager    The "notary": gets trusted SSL certificates from Let's Encrypt so browsers trust your site&lt;br&gt;
Origin CA certificate   A special cert Cloudflare gives you for the encrypted tunnel between Cloudflare and your Kubernetes cluster&lt;br&gt;
⚠️ Important: We'll use Cloudflare's Origin CA certificate (trusted only by Cloudflare) + Let's Encrypt via cert-manager for actual user-facing TLS. This ensures full end-to-end encryption.&lt;/p&gt;

&lt;p&gt;📋 Prerequisites (don't skip this)&lt;br&gt;
A domain name managed in Cloudflare (e.g., example.com)&lt;/p&gt;

&lt;p&gt;A Kubernetes cluster (local: minikube/k3d, cloud: EKS/GKE/AKS, or even Docker Desktop)&lt;/p&gt;

&lt;p&gt;kubectl configured to talk to your cluster&lt;/p&gt;

&lt;p&gt;helm installed (we'll use it to install cert-manager)&lt;/p&gt;

&lt;p&gt;Your frontend app deployed as a Kubernetes Service (type: ClusterIP)&lt;/p&gt;

&lt;p&gt;If you don’t have a frontend service yet, here’s a minimal example to test with:&lt;/p&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  frontend-service.yaml
&lt;/h1&gt;

&lt;p&gt;apiVersion: v1&lt;br&gt;
kind: Service&lt;br&gt;
metadata:&lt;br&gt;
  name: my-frontend-svc&lt;br&gt;
spec:&lt;br&gt;
  selector:&lt;br&gt;
    app: my-frontend&lt;br&gt;
  ports:&lt;br&gt;
    - port: 80&lt;br&gt;
      targetPort: 80&lt;br&gt;
🚀 Step 1: Prepare Cloudflare — get the Origin Certificate&lt;br&gt;
We need a certificate that Cloudflare will trust when talking to your cluster.&lt;/p&gt;

&lt;p&gt;Log into Cloudflare → your domain → SSL/TLS → Origin Server.&lt;/p&gt;

&lt;p&gt;Click Create Certificate.&lt;/p&gt;

&lt;p&gt;Leave defaults (Let Cloudflare generate a private key + CSR).&lt;/p&gt;

&lt;p&gt;In Hostnames, add:&lt;/p&gt;

&lt;p&gt;*.example.com (if you have multiple subdomains)&lt;/p&gt;

&lt;p&gt;app.example.com (your specific frontend domain)&lt;/p&gt;

&lt;p&gt;Choose RSA (universal compatibility) → Create.&lt;/p&gt;

&lt;p&gt;You’ll see two blocks:&lt;/p&gt;

&lt;p&gt;Origin Certificate (long PEM block)&lt;/p&gt;

&lt;p&gt;Private Key (starts with -----BEGIN PRIVATE KEY-----)&lt;/p&gt;

&lt;p&gt;Save both as text files somewhere safe.&lt;br&gt;
👉 These are your cluster’s credentials to prove identity to Cloudflare.&lt;/p&gt;

&lt;p&gt;🔐 Step 2: Store the cert + key as a Kubernetes Secret&lt;br&gt;
Create a Kubernetes Secret containing the Cloudflare Origin certificate.&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
kubectl create secret tls cloudflare-origin-cert \&lt;br&gt;
  --cert=origin_cert.pem \&lt;br&gt;
  --key=origin_private_key.pem \&lt;br&gt;
  --namespace default&lt;br&gt;
Check it exists:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
kubectl get secret cloudflare-origin-cert&lt;br&gt;
🔍 The name (cloudflare-origin-cert) is arbitrary — pick something you’ll remember.&lt;/p&gt;

&lt;p&gt;🌐 Step 3: Deploy an Ingress Controller (if you don’t have one)&lt;br&gt;
We need an actual Ingress controller (like NGINX or Traefik). Let’s use NGINX Ingress — it’s widely used and well-documented.&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
helm repo add ingress-nginx &lt;a href="https://kubernetes.github.io/ingress-nginx" rel="noopener noreferrer"&gt;https://kubernetes.github.io/ingress-nginx&lt;/a&gt;&lt;br&gt;
helm repo update&lt;br&gt;
helm install ingress-nginx ingress-nginx/ingress-nginx \&lt;br&gt;
  --set controller.publishService.enabled=true&lt;br&gt;
Wait for the external IP (or hostname if using minikube tunnel):&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
kubectl get svc ingress-nginx-controller&lt;br&gt;
Copy the EXTERNAL-IP or hostname. You’ll need it for DNS.&lt;/p&gt;

&lt;p&gt;🧭 Step 4: Point Cloudflare DNS to your cluster&lt;br&gt;
We have to tell Cloudflare: “Hey, requests for app.example.com should go to my Ingress controller’s IP.”&lt;/p&gt;

&lt;p&gt;In Cloudflare Dashboard → your domain → DNS → Add record:&lt;/p&gt;

&lt;p&gt;Type: A (or CNAME if you have a hostname)&lt;/p&gt;

&lt;p&gt;Name: app&lt;/p&gt;

&lt;p&gt;IPv4 address: your EXTERNAL-IP from step 3&lt;/p&gt;

&lt;p&gt;Proxy status: ✅ Proxied (orange cloud) — this is crucial!&lt;/p&gt;

&lt;p&gt;Save.&lt;/p&gt;

&lt;p&gt;Wait a few minutes for DNS propagation (or test with dig app.example.com).&lt;/p&gt;

&lt;p&gt;🧩 Step 5: Install cert-manager (automatic SSL wizard)&lt;br&gt;
cert-manager will get Let’s Encrypt certificates for your domain and renew them automatically.&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
helm repo add jetstack &lt;a href="https://charts.jetstack.io" rel="noopener noreferrer"&gt;https://charts.jetstack.io&lt;/a&gt;&lt;br&gt;
helm repo update&lt;br&gt;
helm install cert-manager jetstack/cert-manager \&lt;br&gt;
  --namespace cert-manager \&lt;br&gt;
  --create-namespace \&lt;br&gt;
  --set installCRDs=true&lt;br&gt;
Wait until all pods are running:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
kubectl get pods -n cert-manager&lt;br&gt;
🌍 Step 6: Create a ClusterIssuer (Let's Encrypt "boss")&lt;br&gt;
A ClusterIssuer tells cert-manager which CA to use. We'll use Let's Encrypt Production (no staging for real apps).&lt;/p&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  cluster-issuer.yaml
&lt;/h1&gt;

&lt;p&gt;apiVersion: cert-manager.io/v1&lt;br&gt;
kind: ClusterIssuer&lt;br&gt;
metadata:&lt;br&gt;
  name: letsencrypt-prod&lt;br&gt;
spec:&lt;br&gt;
  acme:&lt;br&gt;
    server: &lt;a href="https://acme-v02.api.letsencrypt.org/directory" rel="noopener noreferrer"&gt;https://acme-v02.api.letsencrypt.org/directory&lt;/a&gt;&lt;br&gt;
    email: &lt;a href="mailto:your-email@example.com"&gt;your-email@example.com&lt;/a&gt;   # &amp;lt;-- change this&lt;br&gt;
    privateKeySecretRef:&lt;br&gt;
      name: letsencrypt-prod&lt;br&gt;
    solvers:&lt;br&gt;
      - http01:&lt;br&gt;
          ingress:&lt;br&gt;
            class: nginx&lt;br&gt;
Apply it:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
kubectl apply -f cluster-issuer.yaml&lt;br&gt;
Check status:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
kubectl get clusterissuer letsencrypt-prod&lt;/p&gt;

&lt;h1&gt;
  
  
  Should show READY=True
&lt;/h1&gt;

&lt;p&gt;✨ Step 7: Write the Ingress YAML (the "frontend-friendly" part)&lt;br&gt;
Here’s the magic — one file that ties everything together.&lt;/p&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  ingress.yaml
&lt;/h1&gt;

&lt;p&gt;apiVersion: networking.k8s.io/v1&lt;br&gt;
kind: Ingress&lt;br&gt;
metadata:&lt;br&gt;
  name: frontend-ingress&lt;br&gt;
  annotations:&lt;br&gt;
    # Tell NGINX to trust the Cloudflare Origin cert&lt;br&gt;
    nginx.ingress.kubernetes.io/auth-tls-secret: "default/cloudflare-origin-cert"&lt;br&gt;
    nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"&lt;br&gt;
    nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1"&lt;br&gt;
    nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true"&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# cert-manager annotations: get a Let's Encrypt cert for this domain
cert-manager.io/cluster-issuer: "letsencrypt-prod"

# Optional: force HTTPS redirect
nginx.ingress.kubernetes.io/ssl-redirect: "true"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;spec:&lt;br&gt;
  ingressClassName: nginx&lt;br&gt;
  tls:&lt;br&gt;
    - hosts:&lt;br&gt;
        - app.example.com          # &amp;lt;-- your actual domain&lt;br&gt;
      secretName: frontend-tls     # cert-manager will create this secret&lt;br&gt;
  rules:&lt;br&gt;
    - host: app.example.com&lt;br&gt;
      http:&lt;br&gt;
        paths:&lt;br&gt;
          - path: /&lt;br&gt;
            pathType: Prefix&lt;br&gt;
            backend:&lt;br&gt;
              service:&lt;br&gt;
                name: my-frontend-svc&lt;br&gt;
                port:&lt;br&gt;
                  number: 80&lt;br&gt;
Deploy it:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
kubectl apply -f ingress.yaml&lt;br&gt;
Wait ~30–60 seconds for cert-manager to do its job.&lt;/p&gt;

&lt;p&gt;Check certificate status:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
kubectl get certificate&lt;br&gt;
kubectl describe certificate frontend-tls&lt;br&gt;
Once READY=True, you're golden.&lt;/p&gt;

&lt;p&gt;🧪 Step 8: Test it!&lt;br&gt;
Open your browser → &lt;a href="https://app.example.com" rel="noopener noreferrer"&gt;https://app.example.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ Green lock icon (Let's Encrypt cert)&lt;/p&gt;

&lt;p&gt;✅ Cloudflare logo in the network tab&lt;/p&gt;

&lt;p&gt;✅ Your frontend loads&lt;/p&gt;

&lt;p&gt;Check the chain:&lt;br&gt;
Browser → Cloudflare (HTTPS) → your cluster (Cloudflare Origin cert) → your frontend pod&lt;/p&gt;

&lt;p&gt;🛠️ Troubleshooting (common frontend-person problems)&lt;br&gt;
Problem Most likely fix&lt;br&gt;
ERR_SSL_PROTOCOL_ERROR  Cloudflare SSL/TLS setting must be Full (strict)&lt;br&gt;
Certificate stuck in Issuing    Wrong email or http01 solver can't reach Ingress&lt;br&gt;
502 Bad Gateway from Cloudflare Your Ingress controller is unreachable — check DNS points to the correct external IP&lt;br&gt;
Cloudflare Origin cert not recognized   Wrong secret name in auth-tls-secret annotation&lt;br&gt;
cert-manager says challenge pending Your Ingress controller needs to be reachable from the internet (Let's Encrypt calls back to validate)&lt;br&gt;
🔧 Cloudflare SSL/TLS setting:&lt;br&gt;
Go to Cloudflare → SSL/TLS → Full (strict) — not Flexible, not Off.&lt;/p&gt;

&lt;p&gt;🧠 Why this approach is great for frontend developers&lt;br&gt;
No manual cert renewal — cert-manager handles it.&lt;/p&gt;

&lt;p&gt;Cloudflare CDN — your static assets (JS, CSS, images) fly fast.&lt;/p&gt;

&lt;p&gt;No exposing your cluster IP — Cloudflare proxies everything.&lt;/p&gt;

&lt;p&gt;Easy to add more frontends — just add more host rules in Ingress.&lt;/p&gt;

&lt;p&gt;Works with SPAs — you can add custom nginx.ingress.kubernetes.io/rewrite-target if needed.&lt;/p&gt;

&lt;p&gt;📦 Bonus: Deploy a frontend + this setup in one script (for testing)&lt;br&gt;
bash&lt;/p&gt;

&lt;h1&gt;
  
  
  !/bin/bash
&lt;/h1&gt;

&lt;h1&gt;
  
  
  deploy-frontend.sh
&lt;/h1&gt;

&lt;p&gt;kubectl create deployment my-frontend --image=nginx --port=80&lt;br&gt;
kubectl expose deployment my-frontend --port=80 --target-port=80 --name=my-frontend-svc&lt;br&gt;
kubectl apply -f cluster-issuer.yaml&lt;br&gt;
kubectl apply -f ingress.yaml&lt;br&gt;
echo "Done! Wait 1 min then visit &lt;a href="https://app.example.com" rel="noopener noreferrer"&gt;https://app.example.com&lt;/a&gt;"&lt;br&gt;
🧹 Cleanup&lt;br&gt;
bash&lt;br&gt;
kubectl delete ingress frontend-ingress&lt;br&gt;
kubectl delete clusterissuer letsencrypt-prod&lt;br&gt;
helm uninstall cert-manager -n cert-manager&lt;br&gt;
kubectl delete secret cloudflare-origin-cert&lt;br&gt;
🎯 Final checklist&lt;br&gt;
Cloudflare DNS has app.example.com proxied (orange cloud)&lt;/p&gt;

&lt;p&gt;Cloudflare SSL/TLS mode is Full (strict)&lt;/p&gt;

&lt;p&gt;Origin certificate + key saved as Kubernetes secret&lt;/p&gt;

&lt;p&gt;cert-manager installed&lt;/p&gt;

&lt;p&gt;ClusterIssuer shows READY=True&lt;/p&gt;

&lt;p&gt;Ingress uses both cert-manager.io/cluster-issuer and Cloudflare auth-tls annotations&lt;/p&gt;

&lt;p&gt;kubectl get certificate shows READY=True&lt;/p&gt;

&lt;p&gt;When all of these are ✅, you’ve built a production-grade HTTPS setup that would make your frontend — and your users — very happy.&lt;/p&gt;

&lt;p&gt;Found this helpful?&lt;br&gt;
Follow me on dev.to for more frontend-friendly Kubernetes and cloud native tutorials.&lt;br&gt;
Got stuck? Drop a comment — I read every one.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Debugging OOMKilled errors and fixing memory leaks in serverless-like environments.</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Sat, 13 Jun 2026 14:53:18 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/debugging-oomkilled-errors-and-fixing-memory-leaks-in-serverless-like-environments-24d</link>
      <guid>https://dev.to/sohanaakbar7/debugging-oomkilled-errors-and-fixing-memory-leaks-in-serverless-like-environments-24d</guid>
      <description>&lt;p&gt;You’ve just containerized your shiny Next.js application. You’ve set resource limits in Kubernetes (say, memory: 512Mi). Everything works fine locally.&lt;/p&gt;

&lt;p&gt;But in production, after a few hours—or sometimes under the first real load—your pod dies.&lt;/p&gt;

&lt;p&gt;kubectl describe pod shows the dreaded:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
Last State: Terminated&lt;br&gt;
Reason: OOMKilled&lt;br&gt;
Exit Code: 137&lt;br&gt;
You check your code. No infinite loops. No obvious leaks. So what’s happening?&lt;/p&gt;

&lt;p&gt;The short answer: Next.js is not a static binary. Under the hood, it caches aggressively, and Kubernetes enforces limits that Next.js was never designed to respect.&lt;/p&gt;

&lt;p&gt;Let’s break down why this happens and how to fix it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The 3 Memory Hogs in Next.js
Most people think Next.js is just React on the server. But in standalone mode, it runs three distinct subsystems, each with its own memory profile.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A) The Node.js Runtime (App Router &amp;amp; Pages Router)&lt;br&gt;
Each request creates a render context.&lt;/p&gt;

&lt;p&gt;React Server Components (RSC) payloads are kept in memory longer than you think.&lt;/p&gt;

&lt;p&gt;Streaming responses hold buffers.&lt;/p&gt;

&lt;p&gt;B) The Built-in Cache System&lt;br&gt;
Next.js caches aggressively by default:&lt;/p&gt;

&lt;p&gt;Data Cache: fetch() results with force-cache (infinite TTL by default).&lt;/p&gt;

&lt;p&gt;Full Route Cache: Rendered page payloads (for static/dynamic segments).&lt;/p&gt;

&lt;p&gt;Router Cache: Client-side (but that’s the browser).&lt;/p&gt;

&lt;p&gt;On the server, the Data Cache and Full Route Cache live in the Node.js heap. With many unique pages or cache keys, memory grows without bound.&lt;/p&gt;

&lt;p&gt;C) Incremental Static Regeneration (ISR)&lt;br&gt;
ISR stores generated pages in memory (or on disk, but default to memory in many setups). Every revalidated page adds another version until garbage collected.&lt;/p&gt;

&lt;p&gt;Result: In a Kubernetes pod with a 512Mi memory limit, your Next.js app may need 1Gi+ after a few hundred unique cache entries.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Why K8s Makes It Worse
Unlike a VM, Kubernetes doesn’t swap. It uses hard memory limits via cgroups. When Node.js tries to allocate more than the limit, the kernel’s OOM killer instantly terminates the process.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Node.js has its own garbage collector (GC). It sees memory pressure and tries to free it—but here’s the catch: Node.js’s heap limit often ignores cgroup limits on older versions.&lt;/p&gt;

&lt;p&gt;Node.js &amp;lt; v14: Doesn’t respect --max-old-space-size relative to cgroup limits.&lt;/p&gt;

&lt;p&gt;Node.js v16+ with NODE_OPTIONS=--max-old-space-size=: You can cap it, but Next.js’s cache can still leak beyond that because of native addons and external buffers.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reproduce It Yourself (Locally with Docker)
Try this to see the crash in action:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dockerfile:&lt;/p&gt;

&lt;p&gt;dockerfile&lt;br&gt;
FROM node:18-alpine&lt;br&gt;
WORKDIR /app&lt;br&gt;
COPY package*.json ./&lt;br&gt;
RUN npm ci&lt;br&gt;
COPY . .&lt;br&gt;
RUN npm run build&lt;br&gt;
EXPOSE 3000&lt;br&gt;
CMD ["npm", "start"]&lt;br&gt;
Run with memory limit:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
docker run --memory=256m --memory-swap=256m -p 3000:3000 my-nextjs-app&lt;br&gt;
Now bombard it:&lt;/p&gt;

&lt;p&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Simulate many unique cache keys
&lt;/h1&gt;

&lt;p&gt;for i in {1..1000}; do&lt;br&gt;
  curl "&lt;a href="http://localhost:3000/blog/$i?nocache=$i" rel="noopener noreferrer"&gt;http://localhost:3000/blog/$i?nocache=$i&lt;/a&gt;"&lt;br&gt;
done&lt;br&gt;
Watch docker stats — memory climbs until OOM.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Real Fixes (Not Just "Add More RAM")
You can throw 2Gi at the pod, but that just delays the crash. Fix the root causes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fix #1: Correct Node.js Heap Limit&lt;br&gt;
Set NODE_OPTIONS to leave room for non-heap memory (buffers, C++ objects, etc.):&lt;/p&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  deployment.yaml
&lt;/h1&gt;

&lt;p&gt;spec:&lt;br&gt;
  containers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name: nextjs
image: my-nextjs-app
resources:
  limits:
    memory: "1024Mi"
  requests:
    memory: "512Mi"
env:

&lt;ul&gt;
&lt;li&gt;name: NODE_OPTIONS
value: "--max-old-space-size=768" # 75% of memory limit
Formula: max-old-space-size = (memory limit in MB) * 0.75&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fix #2: Disable or Limit Caching (App Router)&lt;br&gt;
In next.config.js:&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
module.exports = {&lt;br&gt;
  // Disable in-memory caching for ISR&lt;br&gt;
  staticPageGenerationTimeout: 120,&lt;/p&gt;

&lt;p&gt;// For App Router – control fetch cache&lt;br&gt;
  experimental: {&lt;br&gt;
    // Keep ISR pages on disk, not memory&lt;br&gt;
    incrementalCacheHandlerPath: require.resolve('./cache-handler.js'),&lt;br&gt;
  },&lt;br&gt;
};&lt;br&gt;
Create a custom cache handler (Redis/Memcached):&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
// cache-handler.js&lt;br&gt;
const redis = require('redis');&lt;br&gt;
const client = redis.createClient();&lt;/p&gt;

&lt;p&gt;module.exports = class RedisCache {&lt;br&gt;
  async get(key) { return await client.get(key); }&lt;br&gt;
  async set(key, data) { await client.setex(key, 3600, data); }&lt;br&gt;
};&lt;br&gt;
Fix #3: Explicit Garbage Collection&lt;br&gt;
Add this to your server code (use sparingly):&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
// pages/api/gc.js or app/api/gc/route.ts&lt;br&gt;
export async function GET() {&lt;br&gt;
  if (global.gc) {&lt;br&gt;
    global.gc();&lt;br&gt;
    return Response.json({ message: "GC triggered" });&lt;br&gt;
  }&lt;br&gt;
  return Response.json({ error: "Run with --expose-gc" }, { status: 500 });&lt;br&gt;
}&lt;br&gt;
Then add --expose-gc to NODE_OPTIONS.&lt;/p&gt;

&lt;p&gt;Fix #4: Horizontal Instead of Vertical&lt;br&gt;
Don’t run one Next.js pod with 2Gi RAM. Run 4 pods with 512Mi each.&lt;/p&gt;

&lt;p&gt;Add a HPA (Horizontal Pod Autoscaler) based on memory:&lt;/p&gt;

&lt;p&gt;yaml&lt;br&gt;
apiVersion: autoscaling/v2&lt;br&gt;
kind: HorizontalPodAutoscaler&lt;br&gt;
metadata:&lt;br&gt;
  name: nextjs-hpa&lt;br&gt;
spec:&lt;br&gt;
  scaleTargetRef:&lt;br&gt;
    apiVersion: apps/v1&lt;br&gt;
    kind: Deployment&lt;br&gt;
    name: nextjs&lt;br&gt;
  metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;type: Resource
resource:
  name: memory
  target:
    type: Utilization
    averageUtilization: 80

&lt;ol&gt;
&lt;li&gt;Monitoring: The Lifesaver
You can’t fix what you can’t see. Add:&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prometheus + Grafana: Monitor process.memoryUsage().heapUsed.&lt;/p&gt;

&lt;p&gt;Kubernetes Vertical Pod Autoscaler (VPA): Recommends memory requests/limits based on actual usage.&lt;/p&gt;

&lt;p&gt;Heap snapshots: Use node --inspect and Chrome DevTools to see what’s cached.&lt;/p&gt;

&lt;p&gt;Alert when memory &amp;gt; 80% limit for 5 minutes.&lt;/p&gt;

&lt;p&gt;Final Checklist Before You Ship&lt;br&gt;
✅ Set NODE_OPTIONS="--max-old-space-size=..."&lt;br&gt;
✅ Disable or externalize ISR/data cache (Redis)&lt;br&gt;
✅ Set resources.limits.memory and resources.requests.memory&lt;br&gt;
✅ Add livenessProbe and readinessProbe (crashed pods restart faster)&lt;br&gt;
✅ Run load tests with --memory flag in Docker&lt;br&gt;
✅ Upgrade to Node.js 20+ (better cgroup v2 support)&lt;/p&gt;

&lt;p&gt;The Bottom Line&lt;br&gt;
Your Next.js app isn't leaking memory in the traditional sense—it's intentionally caching without respecting Kubernetes limits. The framework assumes infinite RAM, infinite disk, and a long-running process. Kubernetes assumes the opposite.&lt;/p&gt;

&lt;p&gt;Bridge the gap by explicitly managing that cache and correctly sizing the Node.js heap. Or move static pages to a CDN and keep only dynamic routes in the pod.&lt;/p&gt;

&lt;p&gt;Next.js on K8s works beautifully—once you stop treating it like a static file server.&lt;/p&gt;

&lt;p&gt;Further reading:&lt;/p&gt;

&lt;p&gt;Next.js Custom Cache Handler&lt;/p&gt;

&lt;p&gt;Kubernetes OOM Explained&lt;/p&gt;

&lt;p&gt;Node.js memory limits in containers&lt;/p&gt;

&lt;p&gt;Let’s discuss: Have you seen Next.js OOMKilled in production? What was your fix? 👇&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>nextjs</category>
      <category>performance</category>
    </item>
    <item>
      <title>Helm Charts for a Simple SPA: Over-Engineering or Smart DevOps?</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Fri, 12 Jun 2026 14:15:21 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/helm-charts-for-a-simple-spa-over-engineering-or-smart-devops-1go9</link>
      <guid>https://dev.to/sohanaakbar7/helm-charts-for-a-simple-spa-over-engineering-or-smart-devops-1go9</guid>
      <description>&lt;p&gt;Ah, the Single Page Application (SPA). You’ve built it in React, Vue, or Svelte. You’ve run npm run build, and out pops a dist/ folder containing a few .js, .css, and an index.html.&lt;/p&gt;

&lt;p&gt;Now comes the boring part: Getting it onto a Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;You have two options:&lt;/p&gt;

&lt;p&gt;Write a simple Deployment and Service YAML file (maybe 30 lines total).&lt;/p&gt;

&lt;p&gt;Scaffold a full Helm chart with values.yaml, _helpers.tpl, and templates/.&lt;/p&gt;

&lt;p&gt;Is Helm worth it for a static file server? Let’s break down the dogma from the reality.&lt;/p&gt;

&lt;p&gt;The Case Against Helm (The "KISS" Principle)&lt;br&gt;
Let’s be honest. A production SPA is usually just an nginx container serving static files. No database migrations. No stateful sets. No persistent volumes.&lt;/p&gt;

&lt;p&gt;The "Vanilla YAML" setup looks like this:&lt;/p&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  deployment.yaml
&lt;/h1&gt;

&lt;p&gt;apiVersion: apps/v1&lt;br&gt;
kind: Deployment&lt;br&gt;
metadata:&lt;br&gt;
  name: my-spa&lt;br&gt;
spec:&lt;br&gt;
  replicas: 3&lt;br&gt;
  selector:&lt;br&gt;
    matchLabels:&lt;br&gt;
      app: my-spa&lt;br&gt;
  template:&lt;br&gt;
    metadata:&lt;br&gt;
      labels:&lt;br&gt;
        app: my-spa&lt;br&gt;
    spec:&lt;br&gt;
      containers:&lt;br&gt;
      - name: nginx&lt;br&gt;
        image: nginx:alpine&lt;br&gt;
        ports:&lt;/p&gt;

&lt;h2&gt;
  
  
          - containerPort: 80
&lt;/h2&gt;

&lt;h1&gt;
  
  
  service.yaml
&lt;/h1&gt;

&lt;p&gt;apiVersion: v1&lt;br&gt;
kind: Service&lt;br&gt;
metadata:&lt;br&gt;
  name: my-spa-svc&lt;br&gt;
spec:&lt;br&gt;
  selector:&lt;br&gt;
    app: my-spa&lt;br&gt;
  ports:&lt;br&gt;
    - port: 80&lt;br&gt;
That’s it. Copy that into your CI/CD pipeline, run kubectl apply -f, and go home.&lt;/p&gt;

&lt;p&gt;Why Helm feels like overkill here:&lt;br&gt;
Cognitive load: You now have to explain Go templating to junior devs who just want to fix a button color.&lt;/p&gt;

&lt;p&gt;The "Hello World" paradox: Your chart ends up being 90% boilerplate and 10% actual config.&lt;/p&gt;

&lt;p&gt;Debugging hell: helm template is cool, but kubectl describe on raw YAML is simpler.&lt;/p&gt;

&lt;p&gt;The Case FOR Helm (The "Reality" Check)&lt;br&gt;
You might be thinking, "But I only have one SPA!"&lt;/p&gt;

&lt;p&gt;Sure. For now. But let’s look at the six-month horizon.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Ingress + TLS problem
Your simple SPA needs HTTPS. So you add an Ingress resource. Then you need to annotate it for your specific controller (AWS ALB, nginx, Traefik). Then you need to reference a TLS secret.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Suddenly, your 30 lines of YAML become 80 lines, and you have to remember the annotation syntax for the 10th time.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Environment parity (Staging vs. Production)
You need Staging (with replicas: 1 and latest tag) and Production (with replicas: 5 and v1.2.3 tag).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without Helm, you either:&lt;/p&gt;

&lt;p&gt;Maintain two separate YAML folders (boring, error-prone).&lt;/p&gt;

&lt;p&gt;Use sed to replace values (brittle, 1990s style).&lt;/p&gt;

&lt;p&gt;With Helm:&lt;/p&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  values/staging.yaml
&lt;/h1&gt;

&lt;p&gt;replicaCount: 1&lt;br&gt;
image:&lt;br&gt;
  tag: latest&lt;br&gt;
ingress:&lt;br&gt;
  host: staging.myapp.com&lt;/p&gt;

&lt;h1&gt;
  
  
  values/prod.yaml
&lt;/h1&gt;

&lt;p&gt;replicaCount: 5&lt;br&gt;
image:&lt;br&gt;
  tag: v1.2.3&lt;br&gt;
ingress:&lt;br&gt;
  host: myapp.com&lt;br&gt;
helm install -f values/prod.yaml is a beautiful thing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The "ConfigMap Reload" trick for env.js
SPAs often need runtime config (API URLs) injected into window.&lt;em&gt;env&lt;/em&gt;. The standard pattern is:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A ConfigMap holding env.js.&lt;/p&gt;

&lt;p&gt;An nginx sub_filter or a init-container to inject it.&lt;/p&gt;

&lt;p&gt;Managing that ConfigMap lifecycle (rolling update when env vars change) is annoying in raw YAML. Helm hooks and helper templates make this deterministic.&lt;/p&gt;

&lt;p&gt;The Verdict: It depends on your org size&lt;br&gt;
Scenario    Use Helm?   Why&lt;br&gt;
Personal portfolio / hobby cluster  No  Just use kubectl apply or Kustomize. Helm adds friction without value.&lt;br&gt;
Startup with 2 environments Maybe   If you manually copy-paste YAML across dev/staging/prod, switch to Helm today.&lt;br&gt;
Enterprise with 50+ microservices   Yes Standardizing on Helm (even for SPAs) reduces "snowflake" infrastructure. Your Ops team will thank you.&lt;br&gt;
You use ArgoCD  Yes ArgoCD loves Helm charts. It gives you a UI to see exactly which values changed between deploys.&lt;br&gt;
A Better Alternative: Helm + Raw Manifest&lt;br&gt;
You don't have to use all of Helm's features.&lt;/p&gt;

&lt;p&gt;Don't do this:&lt;/p&gt;

&lt;p&gt;go&lt;br&gt;{{ if .Values.ingress.enabled }}&lt;br&gt;{{ include "my-spa.ingress" . }}&lt;br&gt;{{ end }}&lt;br&gt;
Do this instead:&lt;br&gt;
Keep your chart stupidly simple. Use a flat templates/ directory with minimal logic, and use values.yaml only for:&lt;/p&gt;

&lt;p&gt;image.tag&lt;/p&gt;

&lt;p&gt;replicaCount&lt;/p&gt;

&lt;p&gt;ingress.host&lt;/p&gt;

&lt;p&gt;Keep your helpers empty.&lt;/p&gt;

&lt;p&gt;The "Golden Path" for SPAs&lt;br&gt;
Here is the pragmatic rule:&lt;/p&gt;

&lt;p&gt;If your SPA YAML fits on one screen (50 lines): Use raw manifests + Kustomize.&lt;/p&gt;

&lt;p&gt;If you have more than one environment (dev/staging/prod): Use Helm.&lt;/p&gt;

&lt;p&gt;If you are already using Helm for your backend APIs: Use Helm for the SPA. Consistency &amp;gt; Purity.&lt;/p&gt;

&lt;p&gt;Sample "Just enough Helm" Chart for an SPA&lt;br&gt;
yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  values.yaml
&lt;/h1&gt;

&lt;p&gt;replicaCount: 2&lt;br&gt;
image:&lt;br&gt;
  repository: myregistry/spa&lt;br&gt;
  tag: latest&lt;br&gt;
  pullPolicy: IfNotPresent&lt;br&gt;
service:&lt;br&gt;
  port: 80&lt;br&gt;
ingress:&lt;br&gt;
  enabled: true&lt;br&gt;
  host: app.mycompany.com&lt;br&gt;
  tls:&lt;br&gt;
    - secretName: myapp-tls&lt;br&gt;
yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  templates/deployment.yaml (Condensed)
&lt;/h1&gt;

&lt;p&gt;apiVersion: apps/v1&lt;br&gt;
kind: Deployment&lt;br&gt;
metadata:&lt;br&gt;
  name: {{ .Release.Name }}-spa&lt;br&gt;
spec:&lt;br&gt;
  replicas: {{ .Values.replicaCount }}&lt;br&gt;
  selector:&lt;br&gt;
    matchLabels:&lt;br&gt;
      app: {{ .Release.Name }}&lt;br&gt;
  template:&lt;br&gt;
    metadata:&lt;br&gt;
      labels:&lt;br&gt;
        app: {{ .Release.Name }}&lt;br&gt;
    spec:&lt;br&gt;
      containers:&lt;br&gt;
      - name: nginx&lt;br&gt;
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"&lt;br&gt;
        ports:&lt;br&gt;
        - containerPort: {{ .Values.service.port }}&lt;br&gt;
Final Takeaway&lt;br&gt;
Is Helm worth it for a simple SPA?&lt;/p&gt;

&lt;p&gt;Technically: No. It’s a sledgehammer for a thumbtack.&lt;/p&gt;

&lt;p&gt;Operationally: Yes. If you value --dry-run debugging, environment parity, and not rewriting YAML.&lt;/p&gt;

&lt;p&gt;My advice: Use Kustomize as the middle ground. It gives you environment overlays without the templating headache. But if your team already speaks Helm, don't fight it—just keep the chart boring.&lt;/p&gt;

&lt;p&gt;What’s your take? Are you running SPAs in production with raw YAML, or have you embraced the Helm whale?&lt;/p&gt;

&lt;p&gt;Feel free to adapt this post with your specific nginx config examples or CI/CD snippets. Happy shipping! 🚢&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>webdev</category>
    </item>
    <item>
      <title>📈 Horizontal Pod Autoscaling Based on Frontend Traffic: Beyond CPU Metrics</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Thu, 11 Jun 2026 14:00:35 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/horizontal-pod-autoscaling-based-on-frontend-traffic-beyond-cpu-metrics-3am2</link>
      <guid>https://dev.to/sohanaakbar7/horizontal-pod-autoscaling-based-on-frontend-traffic-beyond-cpu-metrics-3am2</guid>
      <description>&lt;p&gt;Stop scaling by CPU. Start scaling by what actually matters — your users.&lt;/p&gt;

&lt;p&gt;Let’s be honest: Scaling because your pod is at 80% CPU is like refueling your car after the gas light has been flashing for 20 miles. It works, but it’s reactive, clumsy, and your users already felt the lag.&lt;/p&gt;

&lt;p&gt;But what if your Kubernetes cluster could scale before the traffic spike hits? What if your backend could read the room — or rather, the frontend — and prepare itself?&lt;/p&gt;

&lt;p&gt;Welcome to traffic‑based HPA. Let's build it.&lt;/p&gt;

&lt;p&gt;🤔 Why frontend traffic?&lt;br&gt;
Your users don't care about CPU throttling or memory limits. They care about:&lt;/p&gt;

&lt;p&gt;Page load speed&lt;/p&gt;

&lt;p&gt;API response times&lt;/p&gt;

&lt;p&gt;Smooth checkout flows&lt;/p&gt;

&lt;p&gt;CPU‑based scaling reacts to resource pressure. Traffic‑based scaling reacts to user demand.&lt;/p&gt;

&lt;p&gt;Metric  When it scales  Problem&lt;br&gt;
CPU After load increases    Users already suffer&lt;br&gt;
Memory  After allocation spikes Too late for batch jobs&lt;br&gt;
RPS (requests/sec)  As traffic rises    Proactive, user‑first&lt;br&gt;
🧠 The mental model&lt;br&gt;
Think of your frontend (or API gateway) as the canary. It sees every incoming request before your backend pods do.&lt;/p&gt;

&lt;p&gt;By exporting request rate from your frontend layer — whether that’s an ingress controller, a Node.js middleware, or a service mesh sidecar — you can feed that signal into the Kubernetes HPA.&lt;/p&gt;

&lt;p&gt;The result?&lt;br&gt;
👉 Pods scale up as soon as the traffic graph tilts upward, not seconds later when CPU catches up.&lt;/p&gt;

&lt;p&gt;🛠️ How to actually do this (step‑by‑step)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Expose frontend traffic metrics
The easiest path? Use your ingress controller. Most popular ones expose Prometheus metrics out of the box:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  Prometheus scrape config for Nginx Ingress
&lt;/h1&gt;

&lt;p&gt;scrape_configs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;job_name: 'nginx-ingress'
static_configs:

&lt;ul&gt;
&lt;li&gt;targets: ['nginx-ingress-controller.monitoring:10254']
Look for metrics like:&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;nginx_ingress_controller_requests&lt;/p&gt;

&lt;p&gt;nginx_ingress_controller_request_duration_seconds_count&lt;/p&gt;

&lt;p&gt;Pro tip: Filter by host or path to get per‑service traffic.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up Prometheus Adapter
The Kubernetes HPA can’t talk to Prometheus directly. Enter the Prometheus Adapter:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;bash&lt;br&gt;
helm repo add prometheus-community &lt;a href="https://prometheus-community.github.io/helm-charts" rel="noopener noreferrer"&gt;https://prometheus-community.github.io/helm-charts&lt;/a&gt;&lt;br&gt;
helm install prometheus-adapter prometheus-community/prometheus-adapter&lt;br&gt;
Configure it to expose a custom metric called frontend_rps:&lt;/p&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  adapter config
&lt;/h1&gt;

&lt;p&gt;rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;seriesQuery: 'nginx_ingress_controller_requests{host="myapp.example.com"}'
resources:
  overrides:
    namespace: {resource: "namespace"}
    service: {resource: "service"}
metricsQuery: 'sum(rate(&amp;lt;&amp;lt;.Series&amp;gt;&amp;gt;{&amp;lt;&amp;lt;.LabelMatchers&amp;gt;&amp;gt;}[2m]))'

&lt;ol&gt;
&lt;li&gt;Define your HPA
Now the magic — an HPA that scales on requests per second:&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;yaml&lt;br&gt;
apiVersion: autoscaling/v2&lt;br&gt;
kind: HorizontalPodAutoscaler&lt;br&gt;
metadata:&lt;br&gt;
  name: frontend-traffic-hpa&lt;br&gt;
spec:&lt;br&gt;
  scaleTargetRef:&lt;br&gt;
    apiVersion: apps/v1&lt;br&gt;
    kind: Deployment&lt;br&gt;
    name: my-backend-api&lt;br&gt;
  minReplicas: 3&lt;br&gt;
  maxReplicas: 30&lt;br&gt;
  metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;type: Pods
pods:
  metric:
    name: frontend_rps
  target:
    type: AverageValue
    averageValue: "500"  # 500 requests/sec per pod
When traffic exceeds 500 RPS per pod, Kubernetes scales up. Dropped below? Scales down.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🚀 Real‑world example: Black Friday ready&lt;br&gt;
Let’s say your e‑commerce frontend normally serves 1,500 RPS with 3 pods (500 RPS each).&lt;/p&gt;

&lt;p&gt;Suddenly, a flash sale starts. Frontend RPS jumps to 4,500.&lt;/p&gt;

&lt;p&gt;CPU‑based HPA: Waits 60–120s for CPU to max out → users see timeouts.&lt;/p&gt;

&lt;p&gt;Traffic‑based HPA: Scales to 9 pods within 30s (prometheus scrape + HPA sync) → users never notice.&lt;/p&gt;

&lt;p&gt;We’ve seen this cut P99 latency by 40% during ramp‑up spikes in production.&lt;/p&gt;

&lt;p&gt;⚠️ But watch out for…&lt;br&gt;
Noisy neighbors&lt;br&gt;
If your frontend sees bot traffic or web scrapers, you’ll scale unnecessarily. Solution: filter metrics by HTTP status (e.g., exclude 4xx/5xx) or use a sliding window.&lt;/p&gt;

&lt;p&gt;Cold starts&lt;br&gt;
Traffic‑based scaling works after the first request of a spike lands. For truly bursty workloads, combine with:&lt;/p&gt;

&lt;p&gt;Minimum replicas (always keep a baseline)&lt;/p&gt;

&lt;p&gt;Predictive scaling (e.g., KEDA with cron)&lt;/p&gt;

&lt;p&gt;Single source of truth&lt;br&gt;
If you have multiple ingresses or CDNs, aggregate metrics. Prometheus’ sum() across all sources is your friend.&lt;/p&gt;

&lt;p&gt;🔮 Beyond simple RPS&lt;br&gt;
Once you’ve got traffic‑based HPA working, you can get creative:&lt;/p&gt;

&lt;p&gt;Metric  What it detects&lt;br&gt;
RPS per endpoint    /search spikes vs /status traffic&lt;br&gt;
Active WebSocket connections    Real‑time apps&lt;br&gt;
Queue length (frontend → backend) Request backlog&lt;br&gt;
P99 latency of frontend "Users are waiting too long"&lt;br&gt;
🧩 Putting it all together&lt;br&gt;
Here’s the architecture you just built:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
User Request → Ingress Controller → Prometheus → Prometheus Adapter → Kubernetes HPA → Scale Backend Pods&lt;br&gt;
                    ↓                      ↓                    ↓&lt;br&gt;
              (export RPS)           (scrape every 15s)   (expose custom metric)&lt;br&gt;
No new tools. No black magic. Just metrics you already have, used intelligently.&lt;/p&gt;

&lt;p&gt;✅ TL;DR — Do this today&lt;br&gt;
Check if your ingress controller exposes request rate metrics.&lt;/p&gt;

&lt;p&gt;Deploy Prometheus + Prometheus Adapter.&lt;/p&gt;

&lt;p&gt;Write an HPA using pods metric with averageValue in RPS.&lt;/p&gt;

&lt;p&gt;Test with kubectl run load-generator while watching kubectl get hpa -w.&lt;/p&gt;

&lt;p&gt;Your users will never know you scaled. And that’s exactly the point.&lt;/p&gt;

&lt;p&gt;Have you tried scaling on business metrics instead of infrastructure ones? Drop your war stories below — I’d love to hear how others are moving beyond CPU. 👇&lt;/p&gt;

&lt;p&gt;🔗 Follow for more Kubernetes scaling deep dives. Next up: “Scaling on RabbitMQ queue depth (the right way).”&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>performance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Rollback a Frontend Deployment in 10 Seconds — kubectl rollout undo</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Wed, 10 Jun 2026 14:06:55 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/rollback-a-frontend-deployment-in-10-seconds-kubectl-rollout-undo-2c75</link>
      <guid>https://dev.to/sohanaakbar7/rollback-a-frontend-deployment-in-10-seconds-kubectl-rollout-undo-2c75</guid>
      <description>&lt;p&gt;TL;DR: When your SPA is crashing or your CSS is broken, kubectl rollout undo deployment/frontend is your fire extinguisher. Let's break down why this works instantly and how to use it without panic.&lt;/p&gt;

&lt;p&gt;We've all been there. You ship a hotfix for a typo, but accidentally minify the wrong environment variable. Suddenly, your signup page is a white screen of death. The monitoring alerts start buzzing.&lt;/p&gt;

&lt;p&gt;You have two choices:&lt;/p&gt;

&lt;p&gt;Debug the bad build (5+ minutes).&lt;/p&gt;

&lt;p&gt;Rollback to the last working version (10 seconds).&lt;/p&gt;

&lt;p&gt;Let's choose speed.&lt;/p&gt;

&lt;p&gt;The Magic Command&lt;br&gt;
bash&lt;br&gt;
kubectl rollout undo deployment/frontend&lt;br&gt;
That’s it. In the time it takes to type that, Kubernetes has:&lt;/p&gt;

&lt;p&gt;Scaled down the new, broken Pods&lt;/p&gt;

&lt;p&gt;Scaled up the previous, stable ReplicaSet&lt;/p&gt;

&lt;p&gt;Rerouted all traffic back to the good version&lt;/p&gt;

&lt;p&gt;Under the Hood: How It Works So Fast&lt;br&gt;
Unlike VM-based rollbacks (which require re-provisioning or re-imaging), Kubernetes keeps the history of your ReplicaSets. When you run undo, it simply changes which ReplicaSet the Deployment points to.&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
Before rollback:&lt;br&gt;
Deployment → ReplicaSet-v2 (broken) → Pods (red)&lt;/p&gt;

&lt;p&gt;After &lt;code&gt;kubectl rollout undo&lt;/code&gt;:&lt;br&gt;
Deployment → ReplicaSet-v1 (stable) → Pods (green)&lt;br&gt;
No image re-pulling. No container rebuild. Just a pointer change.&lt;/p&gt;

&lt;p&gt;Real-World Example: Frontend CDN vs. Kubernetes&lt;br&gt;
You might think: "But my frontend is just static files on a CDN. Why use k8s rollback?"&lt;/p&gt;

&lt;p&gt;Great question. If you serve pure HTML/JS from S3 + CloudFront, rollback means invalidating a cache (minutes). But if you run:&lt;/p&gt;

&lt;p&gt;A Next.js app with server-side rendering (SSR)&lt;/p&gt;

&lt;p&gt;A Node.js BFF (Backend for Frontend)&lt;/p&gt;

&lt;p&gt;An Nginx container serving your build&lt;/p&gt;

&lt;p&gt;...then Kubernetes rollback is faster than any CDN cache purge.&lt;/p&gt;

&lt;p&gt;Practical Demo&lt;br&gt;
Assume your frontend deployment is called web-app:&lt;/p&gt;

&lt;p&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  See the rollout history
&lt;/h1&gt;

&lt;p&gt;kubectl rollout history deployment/web-app&lt;/p&gt;

&lt;h1&gt;
  
  
  Output example:
&lt;/h1&gt;

&lt;p&gt;REVISION  CHANGE-CAUSE&lt;br&gt;
1         Image: frontend:v1.2.0&lt;br&gt;
2         Image: frontend:v1.3.0 (broken)&lt;br&gt;
3         Image: frontend:v1.4.0 (current, also broken)&lt;/p&gt;

&lt;h1&gt;
  
  
  Undo to the last known good (v1.2.0 = revision 1)
&lt;/h1&gt;

&lt;p&gt;kubectl rollout undo deployment/web-app --to-revision=1&lt;br&gt;
Boom. Fixed.&lt;/p&gt;

&lt;p&gt;Pro Tips for "10 Seconds" to Be Real&lt;br&gt;
Don't use latest tag — Always version your images. Rollback relies on knowing which version was good.&lt;/p&gt;

&lt;p&gt;Enable revision history limit (default is 10). Keep at least 3 old ReplicaSets:&lt;/p&gt;

&lt;p&gt;yaml&lt;br&gt;
spec:&lt;br&gt;
  revisionHistoryLimit: 5&lt;br&gt;
Annotate your changes — So you know what you're rolling back to:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
kubectl annotate deployment/frontend kubernetes.io/change-cause="fix: revert to v1.2.0"&lt;br&gt;
Watch the rollback — Don't just type and pray:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
kubectl rollout status deployment/frontend --watch&lt;br&gt;
When NOT to Use rollout undo&lt;br&gt;
If your frontend manages state in localStorage and the data schema changed between versions → users may still see errors until hard refresh.&lt;/p&gt;

&lt;p&gt;If you've already scaled to zero the old ReplicaSet (manual deletion) → you'll need to redeploy the image.&lt;/p&gt;

&lt;p&gt;If your database schema changed in the backend (rare for frontend-only, but BFFs beware).&lt;/p&gt;

&lt;p&gt;The 10-Second Drill&lt;br&gt;
Practice this so it becomes muscle memory:&lt;/p&gt;

&lt;p&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  1. See red alert
&lt;/h1&gt;

&lt;h1&gt;
  
  
  2. Type this instantly:
&lt;/h1&gt;

&lt;p&gt;kubectl rollout undo deployment/frontend -n production&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Verify it worked:
&lt;/h1&gt;

&lt;p&gt;kubectl get pods -n production -l app=frontend&lt;br&gt;
Final Thought&lt;br&gt;
Speed of recovery is more important than speed of deployment. A broken deploy that takes 10 seconds to fix is safe. One that takes 10 minutes is dangerous.&lt;/p&gt;

&lt;p&gt;So go ahead — ship that Friday afternoon hotfix. You know how to undo it in less time than it takes to brew coffee.&lt;/p&gt;

&lt;p&gt;Found this useful? ♻️ Share it with your team. And remember: kubectl rollout undo is not a failure — it's a feature.&lt;/p&gt;

&lt;p&gt;Have you ever had a frontend rollback horror story? Let me know in the comments.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>frontend</category>
      <category>kubernetes</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>ConfigMaps for Environment Variables in a React App: Stop Rebuilding, Start Injecting</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Tue, 09 Jun 2026 12:36:25 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/configmaps-for-environment-variables-in-a-react-app-stop-rebuilding-start-injecting-1ibg</link>
      <guid>https://dev.to/sohanaakbar7/configmaps-for-environment-variables-in-a-react-app-stop-rebuilding-start-injecting-1ibg</guid>
      <description>&lt;p&gt;TL;DR: Create React App builds bake environment variables at build time. ConfigMaps let you inject runtime configs into your container. Here’s how to bridge them so the same Docker image works across dev, staging, and production.&lt;/p&gt;

&lt;p&gt;The Problem&lt;br&gt;
You’ve built a React app with Create React App (CRA), Vite, or Next.js. You use .env files:&lt;/p&gt;

&lt;p&gt;js&lt;br&gt;
// api.js&lt;br&gt;
const API_URL = process.env.REACT_APP_API_URL;&lt;br&gt;
You build your Docker image:&lt;/p&gt;

&lt;p&gt;dockerfile&lt;br&gt;
FROM node:18 AS builder&lt;br&gt;
COPY . .&lt;br&gt;
RUN npm run build   # REACT_APP_API_URL gets baked here&lt;/p&gt;

&lt;p&gt;FROM nginx:alpine&lt;br&gt;
COPY --from=builder /build /usr/share/nginx/html&lt;br&gt;
Then you deploy to Kubernetes.&lt;/p&gt;

&lt;p&gt;But now you want different API URLs for staging vs production.&lt;/p&gt;

&lt;p&gt;You could rebuild the image for each environment (bad – slow, wasteful). Or you could use a ConfigMap to inject values at runtime.&lt;/p&gt;

&lt;p&gt;ConfigMap to the Rescue&lt;br&gt;
A ConfigMap stores key-value pairs. Kubernetes can mount it as a file inside your pod.&lt;/p&gt;

&lt;p&gt;But React runs in the browser, not in the container’s filesystem. So how does the browser read a file from a ConfigMap?&lt;/p&gt;

&lt;p&gt;Simple: You serve a dynamic env-config.js file from your web server.&lt;/p&gt;

&lt;p&gt;Step-by-Step Solution&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a ConfigMap with your environment variables
yaml
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: react-env-config
data:
env-config.js: |
window.__env = {
  REACT_APP_API_URL: "&lt;a href="https://api.production.com" rel="noopener noreferrer"&gt;https://api.production.com&lt;/a&gt;",
  REACT_APP_FEATURE_FLAG: "true"
};
Apply it:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;bash&lt;br&gt;
kubectl apply -f configmap.yaml&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Modify your React app to read from window.__env
Instead of reading process.env directly, use a runtime config:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;js&lt;br&gt;
// config.js&lt;br&gt;
export function getEnvVar(name) {&lt;br&gt;
  // Runtime injection from window.&lt;strong&gt;env (provided by ConfigMap)&lt;br&gt;
  if (window.&lt;/strong&gt;env &amp;amp;&amp;amp; window.&lt;strong&gt;env[name] !== undefined) {&lt;br&gt;
    return window.&lt;/strong&gt;env[name];&lt;br&gt;
  }&lt;br&gt;
  // Fallback to build-time env vars (for local dev)&lt;br&gt;
  return process.env[name];&lt;br&gt;
}&lt;br&gt;
Use it in your components:&lt;/p&gt;

&lt;p&gt;js&lt;br&gt;
// api.js&lt;br&gt;
import { getEnvVar } from './config';&lt;/p&gt;

&lt;p&gt;const API_URL = getEnvVar('REACT_APP_API_URL');&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Serve the ConfigMap file via your web server
Update your nginx.conf (or serve via Express) to include the dynamic config:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;nginx&lt;br&gt;
server {&lt;br&gt;
  listen 80;&lt;br&gt;
  location /env-config.js {&lt;br&gt;
    alias /usr/share/nginx/html/env-config.js;&lt;br&gt;
  }&lt;br&gt;
  location / {&lt;br&gt;
    root /usr/share/nginx/html;&lt;br&gt;
    try_files $uri /index.html;&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Mount the ConfigMap into your deployment
yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: react-app
spec:
replicas: 2
selector:
matchLabels:
  app: react-app
template:
metadata:
  labels:
    app: react-app
spec:
  containers:

&lt;ul&gt;
&lt;li&gt;name: app
image: myregistry/react-app:latest
ports:

&lt;ul&gt;
&lt;li&gt;containerPort: 80
volumeMounts:&lt;/li&gt;
&lt;li&gt;name: env-config
mountPath: /usr/share/nginx/html/env-config.js
subPath: env-config.js
volumes:&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;name: env-config
configMap:
  name: react-env-config&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Include the script in your index.html
Make sure your index.html loads the config before your main bundle:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;html&lt;br&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br&gt;
&lt;br&gt;
  &lt;br&gt;
    &lt;br&gt;
    &amp;lt;!-- your main bundle after --&amp;gt;&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
    &lt;/p&gt;
&lt;br&gt;
    &lt;br&gt;
  &lt;br&gt;
&lt;br&gt;
How It Works&lt;br&gt;
The ConfigMap provides env-config.js with window.__env = {...}

&lt;p&gt;The pod mounts this file into the nginx HTML directory&lt;/p&gt;

&lt;p&gt;The browser requests index.html, which loads env-config.js first&lt;/p&gt;

&lt;p&gt;Your app reads from window.__env instead of process.env&lt;/p&gt;

&lt;p&gt;Result: The same Docker image now gets different configs per environment.&lt;/p&gt;

&lt;p&gt;Different Environments, Same Image&lt;br&gt;
Environment ConfigMap data&lt;br&gt;
Dev REACT_APP_API_URL: "&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;"&lt;br&gt;
Staging REACT_APP_API_URL: "&lt;a href="https://api.staging.com" rel="noopener noreferrer"&gt;https://api.staging.com&lt;/a&gt;"&lt;br&gt;
Production  REACT_APP_API_URL: "&lt;a href="https://api.production.com" rel="noopener noreferrer"&gt;https://api.production.com&lt;/a&gt;"&lt;br&gt;
No rebuilds. No new images. Just update the ConfigMap and restart pods (or use a reloader like stakater/Reloader).&lt;/p&gt;

&lt;p&gt;For Vite Users&lt;br&gt;
Vite uses import.meta.env. The same pattern works, but you need to expose the window config similarly:&lt;/p&gt;

&lt;p&gt;js&lt;br&gt;
// config.js&lt;br&gt;
export const API_URL = window.__env?.VITE_API_URL || import.meta.env.VITE_API_URL;&lt;br&gt;
Security Considerations&lt;br&gt;
Never put secrets in ConfigMaps (use Secrets instead, but even then, browser-accessible secrets are exposed to devtools).&lt;/p&gt;

&lt;p&gt;For sensitive values, your backend should handle them. Frontend env vars are always readable by the user.&lt;/p&gt;

&lt;p&gt;Common Pitfalls&lt;br&gt;
Caching: Browsers may cache env-config.js. Add cache-busting headers or a unique query param.&lt;/p&gt;

&lt;p&gt;Missing fallback: Always provide a fallback to process.env for local development.&lt;/p&gt;

&lt;p&gt;Order of scripts: env-config.js must load before your app bundle.&lt;/p&gt;

&lt;p&gt;Full Example Repository&lt;br&gt;
I’ve created a working example: react-configmap-demo (replace with your actual repo).&lt;/p&gt;

&lt;p&gt;The key files:&lt;/p&gt;

&lt;p&gt;public/env-config.js (placeholder for local dev)&lt;/p&gt;

&lt;p&gt;src/config.js (reads from window or process.env)&lt;/p&gt;

&lt;p&gt;Kubernetes configmap.yaml and deployment.yaml&lt;/p&gt;

&lt;p&gt;Conclusion&lt;br&gt;
Stop rebuilding Docker images for config changes. Use ConfigMaps to inject runtime environment variables into your React app served via nginx. This gives you:&lt;/p&gt;

&lt;p&gt;One image, many environments&lt;/p&gt;

&lt;p&gt;No build per deployment&lt;/p&gt;

&lt;p&gt;Instant config changes (just rollout restart)&lt;/p&gt;

&lt;p&gt;Your CI/CD will thank you. Your SRE team will thank you. And your future self will thank you when you don’t have to rebuild the frontend at 2 AM just to change an API endpoint.&lt;/p&gt;

&lt;p&gt;Have you tried other approaches like react-runtime-config or Helm templating? Let me know in the comments!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>kubernetes</category>
      <category>react</category>
    </item>
    <item>
      <title>Kubernetes Liveness &amp; Readiness Probes for a Static Site: Stop 404-ing Your Traffic</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Mon, 08 Jun 2026 13:06:00 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/kubernetes-liveness-readiness-probes-for-a-static-site-stop-404-ing-your-traffic-2cmk</link>
      <guid>https://dev.to/sohanaakbar7/kubernetes-liveness-readiness-probes-for-a-static-site-stop-404-ing-your-traffic-2cmk</guid>
      <description>&lt;p&gt;You just containerized your static site (HTML, CSS, JS) using an nginx:alpine image. You deployed it to Kubernetes. It works locally.&lt;/p&gt;

&lt;p&gt;Then you get the page flip: 502 Bad Gateway or random Connection Refused errors during a rolling update.&lt;/p&gt;

&lt;p&gt;Why? Because Kubernetes thinks your container is "alive" and "ready" simply because the Nginx process is running—even if the configuration is broken, the disk is full, or the site is compiling.&lt;/p&gt;

&lt;p&gt;Let's fix that. Here is the only guide you need for liveness and readiness probes on static sites.&lt;/p&gt;

&lt;p&gt;The Golden Rule of Static Sites&lt;br&gt;
Liveness ≠ Readiness.&lt;/p&gt;

&lt;p&gt;Readiness: "Can I send traffic to this Pod right now?" (Startup &amp;amp; reloads)&lt;/p&gt;

&lt;p&gt;Liveness: "Is the Pod broken beyond repair?" (Hard crashes)&lt;/p&gt;

&lt;p&gt;For a static site, we use Readiness for startup delays and Liveness for deadlocks.&lt;/p&gt;

&lt;p&gt;The Naive Approach (Don't do this)&lt;br&gt;
Most tutorials tell you to do this:&lt;/p&gt;

&lt;p&gt;yaml&lt;br&gt;
livenessProbe:&lt;br&gt;
  httpGet:&lt;br&gt;
    path: /&lt;br&gt;
    port: 80&lt;br&gt;
  initialDelaySeconds: 5&lt;br&gt;
  periodSeconds: 10&lt;br&gt;
Why this is bad for static sites:&lt;/p&gt;

&lt;p&gt;Slow Builds: If you use a sidecar or initContainer to fetch a huge static bundle, the probe starts failing immediately. Kubernetes kills your pod before it finishes downloading.&lt;/p&gt;

&lt;p&gt;404s happen: What if your app serves a 200 OK for /, but your CSS bundle main.css is corrupted? The pod is "alive" but the site is broken.&lt;/p&gt;

&lt;p&gt;The Correct Setup: Two Probes, One Health Endpoint&lt;br&gt;
Step 1: Create a Custom Health Endpoint&lt;br&gt;
Do not probe /. Why? Because a CDN or browser cache might return a stale 200 OK. Instead, map a unique path like /health that returns a real status.&lt;/p&gt;

&lt;p&gt;If you're using Nginx (the static site king), add this to your nginx.conf:&lt;/p&gt;

&lt;p&gt;nginx&lt;br&gt;
server {&lt;br&gt;
    listen 80;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location / {
    root /usr/share/nginx/html;
    try_files $uri $uri/ /index.html;
}

# Health check endpoint
location /health {
    access_log off;
    return 200 "healthy\n";
    add_header Content-Type text/plain;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
Step 2: Configuring the Readiness Probe (The "Slow Starter")&lt;br&gt;
The readiness probe determines if your pod is ready to serve traffic. For static sites, the main risk is slow asset hydration (downloading from S3, building from Git, etc.).&lt;/p&gt;

&lt;p&gt;yaml&lt;br&gt;
readinessProbe:&lt;br&gt;
  httpGet:&lt;br&gt;
    path: /health&lt;br&gt;
    port: 80&lt;br&gt;
  initialDelaySeconds: 0  # Start checking immediately&lt;br&gt;
  periodSeconds: 5        # Check every 5 seconds&lt;br&gt;
  failureThreshold: 3     # Allow 3 failures (15 seconds) before marking Unready&lt;br&gt;
  successThreshold: 1     # One success = ready&lt;br&gt;
Pro-tip: Set initialDelaySeconds: 0 and rely on failureThreshold. This way, your pod starts "NotReady" and only becomes "Ready" when Nginx actually responds.&lt;/p&gt;

&lt;p&gt;Step 3: Configuring the Liveness Probe (The "Zombie Killer")&lt;br&gt;
The liveness probe restarts the pod if it enters a deadlock. For static sites, this rarely happens, but you still need it.&lt;/p&gt;

&lt;p&gt;Important: The liveness probe should be more conservative than readiness. You don't want Kubernetes restarting your pod during a brief, heavy load spike.&lt;/p&gt;

&lt;p&gt;yaml&lt;br&gt;
livenessProbe:&lt;br&gt;
  httpGet:&lt;br&gt;
    path: /health&lt;br&gt;
    port: 80&lt;br&gt;
  initialDelaySeconds: 30   # Give it time to start first&lt;br&gt;
  periodSeconds: 15&lt;br&gt;
  timeoutSeconds: 5&lt;br&gt;
  failureThreshold: 3       # Restart after 45 seconds of failures&lt;br&gt;
Step 4: The Complete Deployment&lt;br&gt;
Here is a production-ready static site deployment:&lt;/p&gt;

&lt;p&gt;yaml&lt;br&gt;
apiVersion: apps/v1&lt;br&gt;
kind: Deployment&lt;br&gt;
metadata:&lt;br&gt;
  name: static-site&lt;br&gt;
spec:&lt;br&gt;
  replicas: 3&lt;br&gt;
  selector:&lt;br&gt;
    matchLabels:&lt;br&gt;
      app: static-site&lt;br&gt;
  template:&lt;br&gt;
    metadata:&lt;br&gt;
      labels:&lt;br&gt;
        app: static-site&lt;br&gt;
    spec:&lt;br&gt;
      containers:&lt;br&gt;
      - name: nginx&lt;br&gt;
        image: nginx:alpine&lt;br&gt;
        ports:&lt;br&gt;
        - containerPort: 80&lt;br&gt;
        # 🟢 The magic happens here&lt;br&gt;
        readinessProbe:&lt;br&gt;
          httpGet:&lt;br&gt;
            path: /health&lt;br&gt;
            port: 80&lt;br&gt;
          initialDelaySeconds: 0&lt;br&gt;
          periodSeconds: 5&lt;br&gt;
          failureThreshold: 3&lt;br&gt;
        livenessProbe:&lt;br&gt;
          httpGet:&lt;br&gt;
            path: /health&lt;br&gt;
            port: 80&lt;br&gt;
          initialDelaySeconds: 30&lt;br&gt;
          periodSeconds: 15&lt;br&gt;
          failureThreshold: 3&lt;br&gt;
        # 🟢 Make sure config is mounted&lt;br&gt;
        volumeMounts:&lt;br&gt;
        - name: nginx-config&lt;br&gt;
          mountPath: /etc/nginx/conf.d/default.conf&lt;br&gt;
          subPath: default.conf&lt;br&gt;
      volumes:&lt;br&gt;
      - name: nginx-config&lt;br&gt;
        configMap:&lt;/p&gt;

&lt;h2&gt;
  
  
            name: nginx-health-config
&lt;/h2&gt;

&lt;p&gt;apiVersion: v1&lt;br&gt;
kind: ConfigMap&lt;br&gt;
metadata:&lt;br&gt;
  name: nginx-health-config&lt;br&gt;
data:&lt;br&gt;
  default.conf: |&lt;br&gt;
    server {&lt;br&gt;
        listen 80;&lt;br&gt;
        location / {&lt;br&gt;
            root /usr/share/nginx/html;&lt;br&gt;
            try_files $uri $uri/ /index.html;&lt;br&gt;
        }&lt;br&gt;
        location /health {&lt;br&gt;
            return 200 "OK\n";&lt;br&gt;
            access_log off;&lt;br&gt;
        }&lt;br&gt;
    }&lt;br&gt;
Advanced: Probing a "Real" Resource&lt;br&gt;
Do you want to ensure the actual content is valid? Change the probe path from /health to a critical asset:&lt;/p&gt;

&lt;p&gt;yaml&lt;br&gt;
readinessProbe:&lt;br&gt;
  httpGet:&lt;br&gt;
    path: /index.html  # The actual homepage&lt;br&gt;
    port: 80&lt;br&gt;
  # ... rest of config&lt;br&gt;
But be careful: If you use index.html as your probe, and you delete a CSS file referenced in it, Kubernetes will correctly mark the pod Unready—but it might also restart it unnecessarily. Use this only if your static site is truly atomic (all files deploy together).&lt;/p&gt;

&lt;p&gt;Common Static Site Pitfalls&lt;br&gt;
Problem Symptom Fix&lt;br&gt;
Git-sync sidecar slow   Pod starts, probe fails Increase initialDelaySeconds or failureThreshold on readiness probe&lt;br&gt;
Nginx config typo   500/502 errors  No probe will save you; use ConfigMap validation in CI&lt;br&gt;
Memory leak (rare)  Pod slow but alive  Liveness probe will restart it&lt;br&gt;
Rolling update hanging  Old pods terminate, new pods never get traffic  Readiness probe failing? Check your health endpoint's dependencies&lt;br&gt;
The Ultimate Static Site Rule&lt;br&gt;
Readiness probe = Critical.&lt;br&gt;
Liveness probe = Safety net.&lt;/p&gt;

&lt;p&gt;For 99% of static sites, a basic httpGet on a custom /health endpoint is all you need. Don't overcomplicate it with exec commands or TCP probes.&lt;/p&gt;

&lt;p&gt;What's your static site horror story? Let me know in the comments. 🚀&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>nginx</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Ingress vs Service vs LoadBalancer — explained for devs who hate ops</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Sun, 07 Jun 2026 12:43:29 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/ingress-vs-service-vs-loadbalancer-explained-for-devs-who-hate-ops-5g97</link>
      <guid>https://dev.to/sohanaakbar7/ingress-vs-service-vs-loadbalancer-explained-for-devs-who-hate-ops-5g97</guid>
      <description>&lt;p&gt;Let’s be honest: you just want your container to talk to the internet.&lt;/p&gt;

&lt;p&gt;Instead, someone handed you a YAML file with three different kind: fields and said “It depends.”&lt;/p&gt;

&lt;p&gt;If you’re a backend or full-stack dev who breaks out in hives at the thought of kubectl get nodes, this is for you.&lt;/p&gt;

&lt;p&gt;We’re going to ignore the networking theory. No VxLAN, no iptables deep dives, no BGP. Instead, let’s map Kubernetes networking onto concepts you already understand: function calls, localhost, API gateways, and that one time you tried to host a game server from your dorm room.&lt;/p&gt;

&lt;p&gt;The mental model (skip if you just want the cheat sheet)&lt;br&gt;
Think of your cluster like a apartment building.&lt;/p&gt;

&lt;p&gt;Pod = a person inside an apartment. Has a private address (10.0.0.5). No doorbell. No mail slot. You cannot reach them from outside the building. They don’t even know the outside exists.&lt;/p&gt;

&lt;p&gt;Service = the building’s mailroom. It gives a stable internal address so other residents can send mail to “Apartment 3B” even if the person moves to 4C. Still internal only.&lt;/p&gt;

&lt;p&gt;LoadBalancer = your front door, with a public street address. Anyone on the internet can knock. But it only points to one apartment at a time.&lt;/p&gt;

&lt;p&gt;Ingress = the building’s receptionist / HTTP router. You say “I need to speak to /api/payments”, she looks at the sign, and sends you to Apartment 3B. You say “/dashboard”, she sends you to Apartment 7A. One public door, many destinations.&lt;/p&gt;

&lt;p&gt;Now let’s translate that to actual YAML (the parts you care about).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Service (ClusterIP) – “localhost for the cluster”
You need this when: Your pods need to talk to each other.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A Service of type ClusterIP (the default) gives you one stable DNS name inside the cluster. Pods come and go. The Service doesn’t care.&lt;/p&gt;

&lt;p&gt;yaml&lt;br&gt;
apiVersion: v1&lt;br&gt;
kind: Service&lt;br&gt;
metadata:&lt;br&gt;
  name: payments-api&lt;br&gt;
spec:&lt;br&gt;
  selector:&lt;br&gt;
    app: payments          # routes to any pod with this label&lt;br&gt;
  ports:&lt;br&gt;
    - port: 8080           # what the service listens on&lt;br&gt;
      targetPort: 3000     # what your container actually uses&lt;br&gt;
From any other pod: curl &lt;a href="http://payments-api:8080/health" rel="noopener noreferrer"&gt;http://payments-api:8080/health&lt;/a&gt; — works every time.&lt;/p&gt;

&lt;p&gt;What it can’t do: be reached from your laptop. Or the internet. Or your mom’s iPad.&lt;/p&gt;

&lt;p&gt;If you only have this, you’re still in private dev mode.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;LoadBalancer – “the brute force approach”
You need this when: You have exactly one service that needs public access, and you don’t care about cost (cloud LB starts at ~$20/mo).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;yaml&lt;br&gt;
apiVersion: v1&lt;br&gt;
kind: Service&lt;br&gt;
metadata:&lt;br&gt;
  name: payments-api-public&lt;br&gt;
spec:&lt;br&gt;
  type: LoadBalancer   # &amp;lt;-- the magic line&lt;br&gt;
  selector:&lt;br&gt;
    app: payments&lt;br&gt;
  ports:&lt;br&gt;
    - port: 80&lt;br&gt;
      targetPort: 3000&lt;br&gt;
Run kubectl get svc → EXTERNAL-IP will show (after a minute) 203.0.113.42.&lt;/p&gt;

&lt;p&gt;Hit that IP from anywhere. Done.&lt;/p&gt;

&lt;p&gt;The catch: one public IP per service. If you have three microservices, you pay for three LBs. Also, no path‑based routing. No host‑based routing. No SSL termination (unless you bolt on annotations).&lt;/p&gt;

&lt;p&gt;It’s like renting a dedicated front door for every single room in your apartment.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ingress – “one door to rule them all”
You need this when: You have multiple HTTP services (web, API, admin dashboard, docs) and you want one single IP address + domain.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An Ingress is not a Service type. It’s a separate object that needs an Ingress Controller (nginx, Traefik, AWS ALB, etc.) running in your cluster.&lt;/p&gt;

&lt;p&gt;First, install an Ingress Controller (one-time ops task, sorry). Then:&lt;/p&gt;

&lt;p&gt;yaml&lt;br&gt;
apiVersion: networking.k8s.io/v1&lt;br&gt;
kind: Ingress&lt;br&gt;
metadata:&lt;br&gt;
  name: main-router&lt;br&gt;
spec:&lt;br&gt;
  rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;host: api.myapp.com
http:
  paths:

&lt;ul&gt;
&lt;li&gt;path: /payments
pathType: Prefix
backend:
  service:
    name: payments-api    # points to a regular ClusterIP Service
    port:
      number: 8080&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;host: myapp.com
http:
  paths:

&lt;ul&gt;
&lt;li&gt;path: /
pathType: Prefix
backend:
  service:
    name: frontend-web
    port:
      number: 80
Now:&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;myapp.com/dashboard → frontend-web&lt;/p&gt;

&lt;p&gt;api.myapp.com/payments → payments-api&lt;/p&gt;

&lt;p&gt;SSL? Add a tls: section. Path rewriting? Annotations. Rate limiting? Ingress controller config.&lt;/p&gt;

&lt;p&gt;One public endpoint. Unlimited backend services.&lt;/p&gt;

&lt;p&gt;The cheat sheet (tape this to your monitor)&lt;br&gt;
Want to…  Use YAML snippet&lt;br&gt;
Let Pod A talk to Pod B Service (ClusterIP) type: ClusterIP (default)&lt;br&gt;
Expose a TCP/UDP service to the internet (e.g., Redis, a game server, raw TCP)  LoadBalancer    type: LoadBalancer&lt;br&gt;
Expose multiple HTTP services on one IP with path/host routing  Ingress + a Service (ClusterIP) kind: Ingress&lt;br&gt;
Reach a service from your laptop for debugging (no cloud LB)    kubectl port-forward    kubectl port-forward svc/payments-api 8080:8080&lt;br&gt;
Get a static IP but still want HTTP routing (cloud-specific)    LoadBalancer + annotations (e.g., AWS NLB + Ingress)    Read your cloud’s docs. Sorry.&lt;br&gt;
The “wait, but why do I need all three?” example&lt;br&gt;
You write a checkout service:&lt;/p&gt;

&lt;p&gt;You create a ClusterIP Service so your orders pod can call checkout:8080/create.&lt;/p&gt;

&lt;p&gt;Your PM says “expose it for a test”. You add type: LoadBalancer — it works, but now you have an IP per service and no SSL.&lt;/p&gt;

&lt;p&gt;You add a second service (coupons). You now have two public IPs. Your DNS looks like a crime scene.&lt;/p&gt;

&lt;p&gt;You finally install an Ingress Controller. You change both services back to ClusterIP (they were always internal). You write one Ingress rule: /api/* → checkout, /coupons/* → coupons.&lt;/p&gt;

&lt;p&gt;You go from 2 LBs ($40/mo) + manual SSL to 0 LBs + automatic certs. Your ops person buys you coffee.&lt;/p&gt;

&lt;p&gt;The one sentence summary&lt;br&gt;
Service = internal DNS + stable IP for pod-to-pod.&lt;br&gt;
LoadBalancer = one public IP per service (expensive, simple).&lt;br&gt;
Ingress = one public entry point for many HTTP services (cheap, powerful).&lt;/p&gt;

&lt;p&gt;Now go back to writing actual code. You’re welcome.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>kubernetes</category>
      <category>networking</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Frontend on Kubernetes? Yes, and here’s why you’d want it</title>
      <dc:creator>Sohana Akbar</dc:creator>
      <pubDate>Sat, 06 Jun 2026 14:59:39 +0000</pubDate>
      <link>https://dev.to/sohanaakbar7/frontend-on-kubernetes-yes-and-heres-why-youd-want-it-5cnl</link>
      <guid>https://dev.to/sohanaakbar7/frontend-on-kubernetes-yes-and-heres-why-youd-want-it-5cnl</guid>
      <description>&lt;p&gt;Let me guess.&lt;/p&gt;

&lt;p&gt;You’re a frontend developer. You just finished a beautiful Next.js app. Smooth animations. Perfect Lighthouse score. You’re ready to ship.&lt;/p&gt;

&lt;p&gt;Then someone says: “We’re putting it on Kubernetes.”&lt;/p&gt;

&lt;p&gt;Your first thought? Why would I need an orchestrator for static files and a Node server? Isn’t that like using a cargo ship to deliver a pizza?&lt;/p&gt;

&lt;p&gt;I get it. For years, the playbook was simple:&lt;br&gt;
npm run build → upload to S3/Netlify/Vercel → done.&lt;/p&gt;

&lt;p&gt;But the web has changed. And Kubernetes isn’t just for microservices anymore.&lt;/p&gt;

&lt;p&gt;Here’s why running your frontend on K8s might be the smartest move your team makes this year.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You already have the cluster (so stop paying twice)
If your backend runs on Kubernetes, your cloud bill already includes the control plane, nodes, and networking. Why spin up a separate Vercel bill, Lambda costs, or a CDN-plus-function setup?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Putting the frontend in the same cluster means:&lt;/p&gt;

&lt;p&gt;Shared observability stack (Prometheus + Grafana dashboards for everything)&lt;/p&gt;

&lt;p&gt;Unified CI/CD (one ArgoCD or Flux config for frontend + backend)&lt;/p&gt;

&lt;p&gt;No more “the API is in K8s, but the frontend is… somewhere else” debugging hell.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SSR and preview environments without the cold-start tax
Server-side rendering (Next.js, Nuxt, SvelteKit) is amazing for UX and SEO. But serverless functions have a dirty secret: cold starts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On Kubernetes, your Node server is always warm. Request comes in → response in milliseconds. No Lambda container spinning up while your user stares at a white screen.&lt;/p&gt;

&lt;p&gt;And preview environments?&lt;br&gt;
With Kubernetes, every PR can get its own namespace (my-feature.svc.cluster.local) with the exact same configuration as production. Try doing that cost-effectively with serverless functions at scale.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Traffic splitting for fearless frontend deploys
You know that moment when you merge a UI change and hold your breath? On Kubernetes, you can do canary deployments for your frontend.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;90% of users see the old version&lt;/p&gt;

&lt;p&gt;10% see the new one&lt;/p&gt;

&lt;p&gt;Watch error rates and Core Web Vitals&lt;/p&gt;

&lt;p&gt;Promote only when you’re confident&lt;/p&gt;

&lt;p&gt;Try that with a static CDN. You can’t. It’s all or nothing.&lt;/p&gt;

&lt;p&gt;With K8s + Istio or Traefik, you get A/B testing, gradual rollouts, and instant rollbacks—all without clearing browser caches.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Edge caching is great. Edge logic is better.
Yes, CDNs are fast. But a CDN can’t personalize a page per user, handle geolocation routing, or run middleware logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Kubernetes + a service mesh lets you run frontend logic close to the user (hello, distributed nodes across regions) while still managing everything from one control plane.&lt;/p&gt;

&lt;p&gt;Think: API aggregation, authentication checks, feature flag evaluation—all before the HTML even hits the browser.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The “but it’s complicated” objection (and why it’s fading)
The old argument: “Kubernetes is too hard for frontend teams.”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But in 2026?&lt;/p&gt;

&lt;p&gt;Platform engineering teams give you a safe, simple abstraction.&lt;/p&gt;

&lt;p&gt;Tools like Knative, Keda, or even a simple Deployment + HPA make scaling from zero to thousands trivial.&lt;/p&gt;

&lt;p&gt;Helm charts for frontends exist now (check out nextjs-chart).&lt;/p&gt;

&lt;p&gt;You don’t need to be a CKA. You just need a Dockerfile and a deployment.yaml. The platform team (or modern DevOps culture) handles the rest.&lt;/p&gt;

&lt;p&gt;When you shouldn’t do this&lt;br&gt;
Let’s be honest:&lt;/p&gt;

&lt;p&gt;Single, purely static marketing site? No. Use Netlify.&lt;/p&gt;

&lt;p&gt;Tiny team with no ops support? Start simpler.&lt;/p&gt;

&lt;p&gt;Your app has &amp;lt;1000 users and one region? K8s is overkill.&lt;/p&gt;

&lt;p&gt;But if you have:&lt;/p&gt;

&lt;p&gt;Multiple environments&lt;/p&gt;

&lt;p&gt;SSR or dynamic routing&lt;/p&gt;

&lt;p&gt;Backend already in K8s&lt;/p&gt;

&lt;p&gt;A need for previews, canaries, or complex routing&lt;/p&gt;

&lt;p&gt;…then Kubernetes isn’t just viable. It’s better.&lt;/p&gt;

&lt;p&gt;The bottom line&lt;br&gt;
Frontend on Kubernetes isn’t about flexing infrastructure.&lt;br&gt;
It’s about bringing the same reliability, repeatability, and control to the UI layer that backend teams have enjoyed for years.&lt;/p&gt;

&lt;p&gt;So next time someone says “frontend doesn’t belong in K8s,” ask them:&lt;/p&gt;

&lt;p&gt;“Does your CDN do canary deployments? Does it keep SSR servers warm? Can you spin up a production-accurate preview for every PR?”&lt;/p&gt;

&lt;p&gt;If they hesitate… you know the answer.&lt;/p&gt;

&lt;p&gt;Your stack should fit your product, not the other way around.&lt;/p&gt;

&lt;p&gt;🚢 Happy shipping.&lt;/p&gt;

&lt;p&gt;P.S. Want a minimal Dockerfile + deployment.yaml for Next.js on K8s? Drop a comment and I’ll share the template we use in production.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>frontend</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
