<?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: aiunplugged</title>
    <description>The latest articles on DEV Community by aiunplugged (@aiunplugged).</description>
    <link>https://dev.to/aiunplugged</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%2F4121134%2Fa133fa15-2287-4bbc-872a-1a0fabed3040.png</url>
      <title>DEV Community: aiunplugged</title>
      <link>https://dev.to/aiunplugged</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aiunplugged"/>
    <language>en</language>
    <item>
      <title>Build a RAG pipeline from scratch — the actually simple version</title>
      <dc:creator>aiunplugged</dc:creator>
      <pubDate>Sat, 10 Oct 2026 17:10:17 +0000</pubDate>
      <link>https://dev.to/aiunplugged/build-a-rag-pipeline-from-scratch-the-actually-simple-version-gen</link>
      <guid>https://dev.to/aiunplugged/build-a-rag-pipeline-from-scratch-the-actually-simple-version-gen</guid>
      <description>&lt;p&gt;Originally published on &lt;a href="https://aiunplugged.in/blog/build-rag-pipeline-from-scratch/" rel="noopener noreferrer"&gt;aiunplugged.in&lt;/a&gt; — cross-posting for the Dev.to community.&lt;/p&gt;

&lt;p&gt;Every RAG tutorial online starts with LangChain, LlamaIndex, or a hosted vector database signup. None of that is necessary to understand what a RAG pipeline actually is. This walkthrough builds one in a single Python file with three libraries — no framework, no cloud accounts, no config files.&lt;/p&gt;

&lt;h2&gt;
  
  
  What RAG actually is
&lt;/h2&gt;

&lt;p&gt;RAG (Retrieval-Augmented Generation) is a pattern where an LLM answers a question using text pulled from a private document store at query time, not from what the model memorized during training. It exists because pretrained LLMs don't know a team's docs, a product's changelog, or last week's meeting notes, and fine-tuning every time the source changes is impractical.&lt;/p&gt;

&lt;h2&gt;
  
  
  When RAG is worth adding
&lt;/h2&gt;

&lt;p&gt;RAG makes sense when at least one of these is true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source data changes often (docs, tickets, chat logs, code)&lt;/li&gt;
&lt;li&gt;Source data is private (company internals, customer records)&lt;/li&gt;
&lt;li&gt;Source data is large — thousands of pages — and answers must cite specific passages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the entire dataset fits in a single 200K-token context window (roughly 500 pages) and doesn't change between requests, RAG is often overkill — sending the whole corpus with the question is simpler and sometimes cheaper. That trade-off is worth its own walkthrough, coming later in this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 4-step pipeline
&lt;/h2&gt;

&lt;p&gt;Every RAG system, regardless of framework or vendor, does some version of these four steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Chunk&lt;/strong&gt; — split source documents into smaller passages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embed&lt;/strong&gt; — convert each chunk into a vector (a list of floats)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store&lt;/strong&gt; — save chunks + vectors in a vector database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieve + generate&lt;/strong&gt; — embed the user's question, find the closest chunks, hand them to an LLM&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything else — reranking, hybrid search, query rewriting, evaluation — is optional refinement on top of this loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Three libraries, one pip install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;chromadb sentence-transformers anthropic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;chromadb&lt;/code&gt; — a small local vector database (no server, no signup, in-memory or disk-backed)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sentence-transformers&lt;/code&gt; — the embedding model, downloaded once on first run&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;anthropic&lt;/code&gt; — client for Claude, used for the answer step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The embedding model (&lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt;) has 22 million parameters (~90 MB download) and runs on CPU in milliseconds. The Claude client needs an API key from &lt;code&gt;console.anthropic.com&lt;/code&gt; — Haiku 4.5 is $1 per million input tokens, so a full test session costs cents.&lt;/p&gt;

&lt;p&gt;Set the key as an env var:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-ant-...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1 — Chunk the documents
&lt;/h2&gt;

&lt;p&gt;A "chunk" is a small passage the embedding model can turn into one vector. Chunks that are too big (thousands of tokens) blur meaning across topics. Too small (single sentences) lose surrounding context. A good starting point: 200-400 words per chunk with a 40-word overlap between adjacent chunks.&lt;/p&gt;

&lt;p&gt;For this tutorial the source is a short list of imaginary product-doc passages held in a Python list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aurora API rate limits: free tier is 60 requests per minute. Paid tiers start at 600 rpm.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aurora API authentication uses a bearer token in the Authorization header.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aurora supports Python, Go, and Node.js SDKs. Community SDKs exist for Ruby and Rust.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aurora billing is per-request. Failed requests (5xx) are not charged. 4xx requests are charged.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aurora response latency: p50 is 120ms, p99 is 800ms across all regions.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aurora data residency: EU customers can pin storage to Frankfurt. US customers use us-east-1 by default.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each entry is already a self-contained chunk, so no splitting is needed here. For real documents (Markdown files, PDFs, HTML pages), a character-based splitter works for a first pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chunk_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Split text into overlapping character windows. Cheap and predictable.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Character splitting is imperfect — it can cut mid-sentence — but for a first pipeline it beats spending an hour picking the "right" splitter. Better strategies (heading-aware, semantic, sentence-boundary) get their own deep-dive later in this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Embed the chunks
&lt;/h2&gt;

&lt;p&gt;An embedding is a fixed-length vector that represents the meaning of a text passage. Chunks with similar meanings sit close together in vector space; unrelated chunks are far apart. That distance is the entire mechanism behind retrieval.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SentenceTransformer&lt;/span&gt;

&lt;span class="n"&gt;embedder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SentenceTransformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all-MiniLM-L6-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embedder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt; outputs 384-dimensional vectors. The &lt;code&gt;.tolist()&lt;/code&gt; call converts the NumPy array into plain Python lists so Chroma can serialize them.&lt;/p&gt;

&lt;p&gt;First run downloads the model to &lt;code&gt;~/.cache/huggingface/&lt;/code&gt; — a one-time 22 MB download.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Store chunks and embeddings in a vector database
&lt;/h2&gt;

&lt;p&gt;Chroma runs in-process. &lt;code&gt;EphemeralClient()&lt;/code&gt; keeps everything in RAM (fine for tutorials and tests). &lt;code&gt;PersistentClient(path="./chroma_data")&lt;/code&gt; writes to disk and survives restarts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;chromadb&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chromadb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EphemeralClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aurora_docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;doc_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;))],&lt;/span&gt;
    &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every stored item needs a unique &lt;code&gt;id&lt;/code&gt;. Chroma keeps the raw document text alongside the embedding, so retrieval later returns both the vector match and the original text to feed to the LLM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — Retrieve, then generate the answer
&lt;/h2&gt;

&lt;p&gt;Retrieval is symmetric with storage: embed the user's question with the same model, ask Chroma for the closest N chunks, and hand them to the LLM as context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;q_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embedder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;query_embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;q_embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;n_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;documents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-haiku-4-5-20251001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer the question using ONLY the context below. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;If the answer is not in the context, say so.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Context:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Question: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details worth pinning down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use the same embedding model for storage and retrieval.&lt;/strong&gt; Mixing models silently breaks similarity math — vectors from two different models are not directly comparable, and search results become noise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instruct the model to say "I don't know."&lt;/strong&gt; Without that clause, LLMs will invent an answer from prior training when retrieval misses. It is one of the cheapest reliability wins in RAG.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The full script
&lt;/h2&gt;

&lt;p&gt;Everything above, in one file (&lt;code&gt;rag.py&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;chromadb&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SentenceTransformer&lt;/span&gt;

&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aurora API rate limits: free tier is 60 requests per minute. Paid tiers start at 600 rpm.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aurora API authentication uses a bearer token in the Authorization header.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aurora supports Python, Go, and Node.js SDKs. Community SDKs exist for Ruby and Rust.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aurora billing is per-request. Failed requests (5xx) are not charged. 4xx requests are charged.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aurora response latency: p50 is 120ms, p99 is 800ms across all regions.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aurora data residency: EU customers can pin storage to Frankfurt. US customers use us-east-1 by default.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;embedder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SentenceTransformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all-MiniLM-L6-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embedder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chromadb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EphemeralClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aurora_docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;doc_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;))],&lt;/span&gt;
    &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;q_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embedder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;q_embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;documents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-haiku-4-5-20251001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer the question using ONLY the context below. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;If the answer is not in the context, say so.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Context:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Question: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Are failed requests charged?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No — failed requests that return a 5xx status are not charged. 4xx requests are charged.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a complete RAG loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's still missing before this is production-grade
&lt;/h2&gt;

&lt;p&gt;The pipeline above works, but a production RAG system adds five things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A persistent vector store.&lt;/strong&gt; &lt;code&gt;EphemeralClient()&lt;/code&gt; loses data on restart. Swap for &lt;code&gt;PersistentClient(path=...)&lt;/code&gt;, or graduate to Qdrant, pgvector, or Pinecone when the collection grows past a few hundred thousand chunks. A comparison of vector databases is coming next in this series.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A smarter chunker.&lt;/strong&gt; Markdown-aware, heading-aware, or semantic splitters preserve context better than fixed-size character windows. A deep-dive on chunking strategies follows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata filtering.&lt;/strong&gt; Attaching &lt;code&gt;{"source": "changelog", "date": "2026-08-01"}&lt;/code&gt; to each chunk lets retrieval filter by tag or date range, not similarity alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reranking.&lt;/strong&gt; Pull 20 candidates from the vector DB, then rerank them with a cross-encoder before sending the top 3 to the LLM. Cuts hallucinations and improves precision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation.&lt;/strong&gt; Something that measures whether the pipeline actually returned the right answer, not just "an" answer. Also getting its own article in this series.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything past those is optimization. The four-step loop above is the whole idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes at this stage
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Storing the whole document as one chunk.&lt;/strong&gt; Retrieval then returns the entire document every time, and the LLM ignores the middle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunking on newlines only.&lt;/strong&gt; A single paragraph in a well-formatted document can be 2000 words. Fix chunk size in characters or tokens, not lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using different embedding models for storage and query.&lt;/strong&gt; All similarity math becomes noise; results feel random.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sending the retrieved chunks without an instruction to only use them.&lt;/strong&gt; The LLM blends prior training with the new context and hallucinates confidently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skipping the "I don't know" clause.&lt;/strong&gt; Guarantees a plausible-sounding wrong answer whenever retrieval misses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;RAG is four steps: chunk, embed, store, retrieve+answer. Everything else — LangChain, LlamaIndex, hosted vector DBs, hybrid search, reranking, query rewriting — is scaffolding built on top of this loop. Once the version above works on real documents, layering the extras one at a time reveals exactly what each one changes and why.&lt;/p&gt;




&lt;p&gt;If you've worked through this and are now wrestling with the production side (chunking that doesn't lose context, picking vector DBs, modeling costs at scale), I'm writing a handbook on exactly that — ships next month. &lt;a href="https://aiunplugged.in/products/rag-production-handbook/" rel="noopener noreferrer"&gt;More here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rag</category>
      <category>ai</category>
      <category>python</category>
      <category>llm</category>
    </item>
    <item>
      <title>Kubernetes Pods stuck in Pending — 8 causes and exact fixes</title>
      <dc:creator>aiunplugged</dc:creator>
      <pubDate>Fri, 02 Oct 2026 18:45:41 +0000</pubDate>
      <link>https://dev.to/aiunplugged/kubernetes-pods-stuck-in-pending-8-causes-and-exact-fixes-23cg</link>
      <guid>https://dev.to/aiunplugged/kubernetes-pods-stuck-in-pending-8-causes-and-exact-fixes-23cg</guid>
      <description>&lt;p&gt;A Pod stuck in &lt;code&gt;Pending&lt;/code&gt; means Kubernetes cannot schedule it onto any node. The Pod is created but no container is running. Here are the 8 real causes ranked by frequency, with exact diagnostic commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  First — always run this
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe pod &amp;lt;pod-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scroll to the &lt;code&gt;Events:&lt;/code&gt; section at the bottom. The scheduler explains WHY it can't place the Pod. Every fix below starts from this event message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause 1 — Insufficient CPU or memory on nodes
&lt;/h2&gt;

&lt;p&gt;Event message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0/3 nodes are available: 3 Insufficient cpu.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Pod requests more resources than any node has free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl top nodes
kubectl describe nodes | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 5 &lt;span class="s2"&gt;"Allocated resources"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce Pod's &lt;code&gt;resources.requests.cpu&lt;/code&gt; / &lt;code&gt;memory&lt;/code&gt; if over-requested&lt;/li&gt;
&lt;li&gt;Scale cluster (add nodes)&lt;/li&gt;
&lt;li&gt;Free capacity by removing unused Deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cause 2 — Node selector or affinity doesn't match
&lt;/h2&gt;

&lt;p&gt;Event message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0/3 nodes are available: 3 node(s) didn't match Pod's node affinity/selector.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pod requires nodes with specific labels but none have them. (The scheduler uses the same NodeAffinity plugin for both &lt;code&gt;nodeSelector&lt;/code&gt; and &lt;code&gt;nodeAffinity&lt;/code&gt;, hence the combined message.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get nodes &lt;span class="nt"&gt;--show-labels&lt;/span&gt;
kubectl get pod &amp;lt;pod-name&amp;gt; &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 5 nodeSelector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add the required label to a node: &lt;code&gt;kubectl label node &amp;lt;node&amp;gt; disktype=ssd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Or remove the &lt;code&gt;nodeSelector&lt;/code&gt; from Pod spec if not needed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cause 3 — Taints without matching tolerations
&lt;/h2&gt;

&lt;p&gt;Event message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0/3 nodes are available: 3 node(s) had untolerated taint.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nodes have taints (e.g. &lt;code&gt;NoSchedule&lt;/code&gt;) that Pod doesn't tolerate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe nodes | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 2 Taints
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add matching toleration to Pod spec:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;tolerations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;special"&lt;/span&gt;
  &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Equal"&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
  &lt;span class="na"&gt;effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NoSchedule"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Or remove the taint from node: &lt;code&gt;kubectl taint node &amp;lt;node&amp;gt; special-&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cause 4 — PersistentVolumeClaim not bound
&lt;/h2&gt;

&lt;p&gt;Event message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;persistentvolumeclaim "data-pvc" not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaims.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pod references a PVC that doesn't exist or has no matching PV.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pvc
kubectl describe pvc &amp;lt;pvc-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create the missing PVC&lt;/li&gt;
&lt;li&gt;Check StorageClass exists: &lt;code&gt;kubectl get storageclass&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Verify dynamic provisioning is working (CSI driver installed)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cause 5 — Image pull secret missing (private registry)
&lt;/h2&gt;

&lt;p&gt;Event message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Failed to pull image: unauthorized
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pod's image is in a private registry, no credentials configured. &lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;kubectl get pods&lt;/code&gt; will show STATUS as &lt;code&gt;ErrImagePull&lt;/code&gt; or &lt;code&gt;ImagePullBackOff&lt;/code&gt; for this case — the Pod's &lt;code&gt;.status.phase&lt;/code&gt; is technically still &lt;code&gt;Pending&lt;/code&gt;, but readers watching the STATUS column won't see the word "Pending." Include this in your triage when a Pod visibly appears not Pending but is stuck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pod &amp;lt;pod-name&amp;gt; &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | &lt;span class="nb"&gt;grep &lt;/span&gt;imagePullSecrets
kubectl get secrets | &lt;span class="nb"&gt;grep &lt;/span&gt;regcred
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create secret docker-registry regcred &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--docker-server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;registry-url&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--docker-username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;user&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--docker-password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;pass&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add to Pod spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;imagePullSecrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;regcred&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cause 6 — Pod anti-affinity conflicts
&lt;/h2&gt;

&lt;p&gt;Event message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0/3 nodes are available: 3 node(s) didn't match pod anti-affinity rules.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pod has anti-affinity requiring separation from other Pods, but no eligible node.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pod &amp;lt;pod-name&amp;gt; &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 20 affinity
kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add more nodes (so anti-affinity has room to spread)&lt;/li&gt;
&lt;li&gt;Relax anti-affinity from &lt;code&gt;requiredDuringScheduling&lt;/code&gt; to &lt;code&gt;preferredDuringScheduling&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cause 7 — Namespace ResourceQuota rejects Pod creation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Important distinction:&lt;/strong&gt; unlike causes 1-6 and 8, a ResourceQuota violation &lt;strong&gt;prevents the Pod object from being created at all&lt;/strong&gt;. The API server's admission controller rejects the create request with &lt;code&gt;403 Forbidden&lt;/code&gt;, so there's no Pod stuck in Pending — the Pod never exists. Include this in your triage when &lt;code&gt;kubectl create&lt;/code&gt; / &lt;code&gt;kubectl apply&lt;/code&gt; returns an error and no Pod appears in &lt;code&gt;kubectl get pods&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Error message (from the CLI, not the Pod events):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error from server (Forbidden): pods "foo" is forbidden: exceeded quota: &amp;lt;quota-name&amp;gt;, requested: ..., used: ..., limited: ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Diagnose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe quota &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;namespace&amp;gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; pod.yaml   &lt;span class="c"&gt;# observe the Forbidden error text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete unused Pods in namespace to free quota&lt;/li&gt;
&lt;li&gt;Increase quota: &lt;code&gt;kubectl edit quota &amp;lt;quota-name&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Deploy to a different namespace with headroom&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cause 8 — Scheduler broken or overloaded
&lt;/h2&gt;

&lt;p&gt;Rare, but happens on stressed clusters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system | &lt;span class="nb"&gt;grep &lt;/span&gt;scheduler
kubectl logs &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system &amp;lt;scheduler-pod-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Restart scheduler pod&lt;/li&gt;
&lt;li&gt;Check for scheduler configuration issues&lt;/li&gt;
&lt;li&gt;On managed K8s (EKS/GKE), this usually resolves itself&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The universal debug checklist
&lt;/h2&gt;

&lt;p&gt;Copy this and run through it every time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Basic status&lt;/span&gt;
kubectl get pod &amp;lt;pod-name&amp;gt;

&lt;span class="c"&gt;# Detailed events (THE most important)&lt;/span&gt;
kubectl describe pod &amp;lt;pod-name&amp;gt;

&lt;span class="c"&gt;# Node status&lt;/span&gt;
kubectl get nodes
kubectl describe nodes

&lt;span class="c"&gt;# Recent events across cluster&lt;/span&gt;
kubectl get events &lt;span class="nt"&gt;--sort-by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'.lastTimestamp'&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;

&lt;span class="c"&gt;# Related resources&lt;/span&gt;
kubectl get pvc
kubectl get storageclass
kubectl get secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reproduce this yourself (browser sandbox)
&lt;/h2&gt;

&lt;p&gt;Free Kubernetes playground: &lt;a href="https://killercoda.com/playgrounds/scenario/kubernetes" rel="noopener noreferrer"&gt;https://killercoda.com/playgrounds/scenario/kubernetes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a pod that will be Pending:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl run stuck-pod &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx &lt;span class="nt"&gt;--overrides&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{"apiVersion":"v1","spec":{"nodeSelector":{"nonexistent":"label"}}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe pod stuck-pod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Screenshot the Events section. Note the "didn't match node selector" message.&lt;/p&gt;

&lt;p&gt;Clean up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete pod stuck-pod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prevention
&lt;/h2&gt;

&lt;p&gt;Set sensible defaults from the start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resource requests&lt;/strong&gt; — not too high, not too low. Start with 100m CPU, 128Mi memory. Adjust based on actual usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No &lt;code&gt;nodeSelector&lt;/code&gt;&lt;/strong&gt; unless truly needed. Default schedulers work well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test PVCs before deploying real workloads&lt;/strong&gt; — verify storage class works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;kubectl top&lt;/code&gt;&lt;/strong&gt; regularly to see node capacity trends.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Every Pending pod has a specific reason. The scheduler always tells you what it is via &lt;code&gt;kubectl describe&lt;/code&gt;. Match the event message to one of the 8 causes above. Apply the corresponding fix. Done in 5 minutes.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>debugging</category>
      <category>devops</category>
      <category>k8</category>
    </item>
    <item>
      <title>Fix: kubectl exec fails with 'connection refused' — 5 real causes</title>
      <dc:creator>aiunplugged</dc:creator>
      <pubDate>Sat, 26 Sep 2026 18:56:13 +0000</pubDate>
      <link>https://dev.to/aiunplugged/fix-kubectl-exec-fails-with-connection-refused-5-real-causes-1fim</link>
      <guid>https://dev.to/aiunplugged/fix-kubectl-exec-fails-with-connection-refused-5-real-causes-1fim</guid>
      <description>

&lt;p&gt;Trying to exec into a running pod and Kubernetes refuses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: unable to upgrade connection: pod does not exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error from server: error dialing backend: dial tcp 10.42.0.7:10250: connect: connection refused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pod shows &lt;code&gt;Running&lt;/code&gt; in &lt;code&gt;kubectl get pods&lt;/code&gt;, yet exec doesn't work. Here are the real causes and how to fix each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagnose first
&lt;/h2&gt;

&lt;p&gt;Before guessing, gather facts:&lt;/p&gt;

&lt;p&gt;For RBAC-related issues, also check what permissions the current user has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Confirm pod is actually running&lt;/span&gt;
kubectl get pod &amp;lt;pod-name&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;namespace&amp;gt;

&lt;span class="c"&gt;# Check node the pod is on&lt;/span&gt;
kubectl get pod &amp;lt;pod-name&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;namespace&amp;gt; &lt;span class="nt"&gt;-o&lt;/span&gt; wide

&lt;span class="c"&gt;# Check the node's status&lt;/span&gt;
kubectl get node &amp;lt;node-name&amp;gt;

&lt;span class="c"&gt;# Check kubelet logs on the node (if you have SSH access)&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; kubelet &lt;span class="nt"&gt;-n&lt;/span&gt; 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact error message narrows down which of the 5 causes below applies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause 1 — kubelet not reachable from API server
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kubectl exec&lt;/code&gt; requires the API server to open a tunnel to the kubelet on port 10250. If a firewall or security group blocks that port, exec fails while everything else works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; error like &lt;code&gt;dial tcp &amp;lt;node-ip&amp;gt;:10250: connect: connection refused&lt;/code&gt; or &lt;code&gt;i/o timeout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud clusters (EKS/GKE/AKS): check that the control plane security group can reach worker nodes on 10250/TCP. On EKS, this is the &lt;code&gt;eksClusterSecurityGroup&lt;/code&gt; inbound rule on the node security group.&lt;/li&gt;
&lt;li&gt;Self-hosted: verify &lt;code&gt;iptables&lt;/code&gt; or &lt;code&gt;ufw&lt;/code&gt; allows 10250 from control plane nodes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# On the node, verify kubelet is listening&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ss &lt;span class="nt"&gt;-tlnp&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;10250

&lt;span class="c"&gt;# From the control plane, test connectivity&lt;/span&gt;
nc &lt;span class="nt"&gt;-zv&lt;/span&gt; &amp;lt;node-internal-ip&amp;gt; 10250
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the port is closed, add the firewall rule. If open but still failing, jump to cause 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause 2 — kubelet is running but crashed the streaming server
&lt;/h2&gt;

&lt;p&gt;kubelet has a separate HTTPS server for exec/attach/logs. It sometimes crashes without taking down the whole kubelet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; &lt;code&gt;logs&lt;/code&gt; works but &lt;code&gt;exec&lt;/code&gt; and &lt;code&gt;attach&lt;/code&gt; fail. Or intermittent failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix — restart kubelet:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart kubelet
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; kubelet &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch for errors during startup. If kubelet keeps crashing, the underlying issue is often disk pressure, memory pressure, or a stale container runtime socket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause 3 — pod is Running but container isn't ready for TTY
&lt;/h2&gt;

&lt;p&gt;Distroless containers, scratch images, or containers without a shell installed have no &lt;code&gt;/bin/sh&lt;/code&gt; — exec fails immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; &lt;code&gt;OCI runtime exec failed: exec failed: unable to start container process: exec: "sh": executable file not found in $PATH&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix — use an ephemeral debug container (Kubernetes 1.23+):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl debug &lt;span class="nt"&gt;-it&lt;/span&gt; &amp;lt;pod-name&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;namespace&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;busybox:1.36 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;container-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This attaches a debug container to the same pod's namespace, giving you a shell that CAN inspect the target container's processes, network, and mounts. Works even for distroless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause 4 — RBAC blocking your user from exec
&lt;/h2&gt;

&lt;p&gt;You can list pods but not exec into them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; &lt;code&gt;Error from server (Forbidden): pods "&amp;lt;name&amp;gt;" is forbidden: User "&amp;lt;you&amp;gt;" cannot create resource "pods/exec"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl auth can-i create pods/exec &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;namespace&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it returns &lt;code&gt;no&lt;/code&gt;, your Role or ClusterRole is missing the &lt;code&gt;pods/exec&lt;/code&gt; sub-resource.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix — grant exec permission:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Role&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pod-exec&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YOUR_NAMESPACE&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods/exec"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bind it to your user with a &lt;code&gt;RoleBinding&lt;/code&gt; and retry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause 5 — kubectl version mismatch with cluster
&lt;/h2&gt;

&lt;p&gt;Very old kubectl talking to a much newer cluster (or vice versa) can hit exec-protocol incompatibilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; exec starts, prints a partial banner, then disconnects. Or &lt;code&gt;error: unable to upgrade connection: unauthorized&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default output shows both client and server versions. Kubernetes officially supports client/server skew of +/- 1 minor version. Beyond that, exec is unreliable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix — align kubectl version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;kubernetes-cli
&lt;span class="c"&gt;# or pinned to a specific minor version:&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;kubernetes-cli@1.29

&lt;span class="c"&gt;# Linux — pinned install&lt;/span&gt;
curl &lt;span class="nt"&gt;-LO&lt;/span&gt; &lt;span class="s2"&gt;"https://dl.k8s.io/release/v1.29.0/bin/linux/amd64/kubectl"&lt;/span&gt;
&lt;span class="nb"&gt;sudo install&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; root &lt;span class="nt"&gt;-g&lt;/span&gt; root &lt;span class="nt"&gt;-m&lt;/span&gt; 0755 kubectl /usr/local/bin/kubectl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Match kubectl to your cluster's minor version and retry.&lt;/p&gt;

&lt;h2&gt;
  
  
  The universal exec debug flow
&lt;/h2&gt;

&lt;p&gt;Every stuck-exec case, run in order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Pod actually running?&lt;/span&gt;
kubectl get pod &amp;lt;pod&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;ns&amp;gt;

&lt;span class="c"&gt;# 2. Node healthy?&lt;/span&gt;
kubectl get node &lt;span class="nt"&gt;-o&lt;/span&gt; wide

&lt;span class="c"&gt;# 3. Can I even see logs? (rules out kubelet-total-failure)&lt;/span&gt;
kubectl logs &amp;lt;pod&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;ns&amp;gt; &lt;span class="nt"&gt;--tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5

&lt;span class="c"&gt;# 4. RBAC permission for exec?&lt;/span&gt;
kubectl auth can-i create pods/exec &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;ns&amp;gt;

&lt;span class="c"&gt;# 5. Try debug container (bypasses distroless issue)&lt;/span&gt;
kubectl debug &lt;span class="nt"&gt;-it&lt;/span&gt; &amp;lt;pod&amp;gt; &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;busybox &lt;span class="nt"&gt;--target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;container&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ninety percent of exec failures resolve after step 5.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevention
&lt;/h2&gt;

&lt;p&gt;Bake these into every cluster setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explicit security group rules&lt;/strong&gt; for control-plane to node on 10250/TCP; documented in Terraform, not vibes-based&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ephemeral containers enabled&lt;/strong&gt; on all managed clusters (default in K8s 1.25+; verify with &lt;code&gt;kubectl api-resources | grep ephemeral&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standardized debug image&lt;/strong&gt; in a private registry — a common &lt;code&gt;debug:latest&lt;/code&gt; with curl, bind-utils, netcat, iputils pre-installed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RBAC role called &lt;code&gt;debugger&lt;/code&gt;&lt;/strong&gt; with pods/exec permission, granted temporarily via &lt;code&gt;kubectl auth reconcile&lt;/code&gt; for incident response&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regular kubectl version audits&lt;/strong&gt; across the team — pin to cluster version in your dotfiles&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kubectl exec&lt;/code&gt; failing usually points at one of five layers: network to kubelet, kubelet streaming server, missing shell in the container, RBAC, or version skew. Diagnose with &lt;code&gt;kubectl get pod -o wide&lt;/code&gt;, &lt;code&gt;kubectl auth can-i create pods/exec&lt;/code&gt;, and &lt;code&gt;kubectl debug&lt;/code&gt;. The debug container is a strong Swiss army knife — reach for it early rather than fighting distroless exec issues directly.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>kubectl</category>
      <category>troubleshooting</category>
    </item>
    <item>
      <title>Claude API vs OpenAI API for developers — 2026 practical comparison</title>
      <dc:creator>aiunplugged</dc:creator>
      <pubDate>Fri, 18 Sep 2026 12:54:37 +0000</pubDate>
      <link>https://dev.to/aiunplugged/claude-api-vs-openai-api-for-developers-2026-practical-comparison-22c0</link>
      <guid>https://dev.to/aiunplugged/claude-api-vs-openai-api-for-developers-2026-practical-comparison-22c0</guid>
      <description>&lt;p&gt;This post was originally published on &lt;a href="https://aiunplugged.in/blog/claude-api-vs-openai-api-developers/" rel="noopener noreferrer"&gt;aiunplugged.in&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Two dominant APIs power most 2026 AI apps: Anthropic's Claude and OpenAI's GPT. Both are excellent. The right choice depends on specific use cases. Here is a practical comparison based on daily developer use, with current 2026 model families and pricing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30-second summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude&lt;/strong&gt; — better at long-form reasoning, careful code, safety-critical outputs, honest limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI&lt;/strong&gt; — better at general-purpose tasks, mature ecosystem, plugins, image generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Both&lt;/strong&gt; — production-ready, well-documented, actively improved&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For code + reasoning tasks, Claude tends to win in 2026. For general apps, either works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model comparison (2026)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Claude models (Anthropic)
&lt;/h3&gt;

&lt;p&gt;The current Claude 5 family plus Haiku 4.5:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Fable 5.1&lt;/strong&gt; — newest release, high-capability with faster response times&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Opus 5&lt;/strong&gt; — most capable for deep reasoning, slower, most expensive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Sonnet 5&lt;/strong&gt; — balanced default recommendation, strong on code and reasoning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Haiku 4.5&lt;/strong&gt; — fast, cheap, still highly capable for lighter tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  OpenAI models
&lt;/h3&gt;

&lt;p&gt;The GPT-5.6 family is OpenAI's current flagship, with older GPT-5 and GPT-5.4 still available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GPT-5.6-sol&lt;/strong&gt; — flagship, most capable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPT-5.6-terra&lt;/strong&gt; — mid-tier, balanced cost/quality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPT-5.6-luna&lt;/strong&gt; — cheapest of the flagship family, high volume&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPT-5.4&lt;/strong&gt; — previous generation, still supported&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPT-5&lt;/strong&gt; — original GPT-5, cheapest input pricing in the modern lineup&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pricing per million tokens (verified Sep 2026)
&lt;/h2&gt;

&lt;p&gt;Input / Output pricing (USD per million tokens):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus 5&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 5&lt;/td&gt;
&lt;td&gt;$2 - $3&lt;/td&gt;
&lt;td&gt;$10 - $15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Haiku 4.5&lt;/td&gt;
&lt;td&gt;$1&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.6-sol&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;td&gt;$30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.6-terra&lt;/td&gt;
&lt;td&gt;$2&lt;/td&gt;
&lt;td&gt;$12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.6-luna&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;$1.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.4&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;td&gt;$15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5&lt;/td&gt;
&lt;td&gt;$0.625&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sonnet 5 launched at $2/$10 as introductory pricing; sources report the standard rate as either $2/$10 or $3/$15 depending on when tracked&lt;/li&gt;
&lt;li&gt;OpenAI: batch mode halves rates, fast mode doubles them, long-context (&amp;gt;272K tokens) bills roughly double&lt;/li&gt;
&lt;li&gt;Cached input gets ~90% discount on GPT-5.6 and GPT-5; 50-75% on legacy models&lt;/li&gt;
&lt;li&gt;Prices change frequently. Always verify current pricing at anthropic.com/pricing and openai.com/pricing before committing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cheapest quality option:&lt;/strong&gt; GPT-5.6-luna for high volume, Claude Haiku 4.5 for balance. GPT-5 at $0.625 input is the cheapest input-heavy option in the modern lineup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Claude wins
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Long context handling
&lt;/h3&gt;

&lt;p&gt;Claude Sonnet 5 and Opus 5 handle 200K tokens (~500 pages) effectively. Their recall over long context is measurably better than comparable OpenAI models at similar sizes.&lt;/p&gt;

&lt;p&gt;Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analyzing entire codebases&lt;/li&gt;
&lt;li&gt;Processing long documents&lt;/li&gt;
&lt;li&gt;Multi-turn conversations that accumulate&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Careful code output
&lt;/h3&gt;

&lt;p&gt;Claude tends to be more precise on code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fewer hallucinated APIs&lt;/li&gt;
&lt;li&gt;Better adherence to instructions&lt;/li&gt;
&lt;li&gt;More self-aware ("I'm not sure about X" instead of confident wrong answers)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refactoring&lt;/li&gt;
&lt;li&gt;Debugging&lt;/li&gt;
&lt;li&gt;Code review&lt;/li&gt;
&lt;li&gt;Complex logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Following complex instructions
&lt;/h3&gt;

&lt;p&gt;Claude is more likely to follow detailed multi-step instructions accurately.&lt;/p&gt;

&lt;p&gt;Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agentic workflows&lt;/li&gt;
&lt;li&gt;Multi-step pipelines&lt;/li&gt;
&lt;li&gt;Structured output requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Honest uncertainty
&lt;/h3&gt;

&lt;p&gt;When Claude doesn't know something, it more often says so. OpenAI models tend to answer confidently even when wrong.&lt;/p&gt;

&lt;p&gt;Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fact-critical outputs&lt;/li&gt;
&lt;li&gt;User-facing apps where wrong answers cause harm&lt;/li&gt;
&lt;li&gt;Safety-important use cases&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where OpenAI wins
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Multimodal (image + audio)
&lt;/h3&gt;

&lt;p&gt;The GPT-5.6 family handles images, audio, and text in a single call. Voice mode is polished.&lt;/p&gt;

&lt;p&gt;Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Image analysis apps&lt;/li&gt;
&lt;li&gt;Voice-driven apps&lt;/li&gt;
&lt;li&gt;Screenshot understanding&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Ecosystem maturity
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;More libraries, examples, SDKs&lt;/li&gt;
&lt;li&gt;More Stack Overflow answers&lt;/li&gt;
&lt;li&gt;More third-party tools built on OpenAI first&lt;/li&gt;
&lt;li&gt;Assistants API (built-in state, tools, threads)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prototyping with existing tools&lt;/li&gt;
&lt;li&gt;When leveraging community examples matters&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Image generation (DALL-E)
&lt;/h3&gt;

&lt;p&gt;Native image generation via API. Claude doesn't offer image generation.&lt;/p&gt;

&lt;p&gt;Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Image gen features&lt;/li&gt;
&lt;li&gt;Combined text + image workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Streaming reliability
&lt;/h3&gt;

&lt;p&gt;Both stream. OpenAI's streaming has slightly better tooling support (more libraries).&lt;/p&gt;

&lt;p&gt;Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chat UIs needing token-by-token streaming&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practical patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Building a chat interface
&lt;/h3&gt;

&lt;p&gt;Both work. Choice comes down to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model quality per dollar (Claude Sonnet 5 currently strong for reasoning quality)&lt;/li&gt;
&lt;li&gt;Streaming library support (both fine)&lt;/li&gt;
&lt;li&gt;Tool use / function calling (both mature)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code generation for developer tools
&lt;/h3&gt;

&lt;p&gt;Claude Sonnet 5 or Opus 5. Consistently better code quality with fewer hallucinated APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summarization
&lt;/h3&gt;

&lt;p&gt;Both. GPT-5.6-luna is cheapest. Claude Haiku 4.5 is competitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  RAG (Retrieval-Augmented Generation)
&lt;/h3&gt;

&lt;p&gt;Claude's long context wins for RAG where you stuff many documents into context. Better recall.&lt;/p&gt;

&lt;p&gt;For traditional RAG (small chunks retrieved, then generation), both work equally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agentic workflows
&lt;/h3&gt;

&lt;p&gt;Claude's better instruction-following makes it easier to build reliable agents. OpenAI Assistants API provides more built-in scaffolding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Safety-critical applications
&lt;/h3&gt;

&lt;p&gt;Claude has stronger built-in safety training. Refuses harmful requests more reliably. Better for consumer-facing products.&lt;/p&gt;

&lt;h2&gt;
  
  
  API differences that matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  System prompts
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; dedicated &lt;code&gt;system&lt;/code&gt; parameter separate from messages&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claude-sonnet-5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"system"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You are a helpful assistant."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;OpenAI:&lt;/strong&gt; system message inside messages array&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.6-sol"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"system"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You are a helpful assistant."&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tool use / function calling
&lt;/h3&gt;

&lt;p&gt;Both support tool calling with similar patterns. Claude's tool use is more forgiving of schema variations. OpenAI's is stricter but has better docs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Streaming
&lt;/h3&gt;

&lt;p&gt;Both use Server-Sent Events. Response formats differ slightly. All popular SDKs abstract this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt caching
&lt;/h3&gt;

&lt;p&gt;Both offer prompt caching to reduce cost on repeated inputs. Different implementations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; &lt;code&gt;cache_control&lt;/code&gt; on message blocks, 5-minute default TTL&lt;br&gt;
&lt;strong&gt;OpenAI:&lt;/strong&gt; automatic caching on longer prompts (~500+ tokens); ~90% discount on GPT-5.6 cached input&lt;/p&gt;

&lt;p&gt;For repeated system prompts, both cut costs significantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which SDK to use
&lt;/h2&gt;

&lt;p&gt;Both providers offer official SDKs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python: &lt;code&gt;pip install anthropic&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;JavaScript: &lt;code&gt;npm install @anthropic-ai/sdk&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;OpenAI:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python: &lt;code&gt;pip install openai&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;JavaScript: &lt;code&gt;npm install openai&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are well-maintained, well-documented, and stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limits
&lt;/h2&gt;

&lt;p&gt;Both providers have tier-based rate limits that scale with spend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tier 1&lt;/strong&gt; (new accounts): low limits, may hit quickly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Higher tiers:&lt;/strong&gt; unlock automatically as you spend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither is friendlier than the other on rate limits. If building high-volume, budget for tier progression.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use both
&lt;/h2&gt;

&lt;p&gt;Some applications benefit from using both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Primary:&lt;/strong&gt; Claude for reasoning-heavy tasks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback:&lt;/strong&gt; OpenAI if Claude is rate-limited or down&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specialized:&lt;/strong&gt; OpenAI DALL-E for images, Claude for text&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A/B testing:&lt;/strong&gt; compare quality on your specific tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;LiteLLM&lt;/strong&gt; or &lt;strong&gt;OpenRouter&lt;/strong&gt; provide unified interfaces to both (and other providers). Useful for multi-model apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing quality for your use case
&lt;/h2&gt;

&lt;p&gt;Do not rely on benchmarks. Test on YOUR actual prompts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick 10 real prompts from your use case&lt;/li&gt;
&lt;li&gt;Run each in Claude Sonnet 5, GPT-5.6-sol, and a cheaper option (Haiku 4.5 or GPT-5.6-luna)&lt;/li&gt;
&lt;li&gt;Grade output quality subjectively&lt;/li&gt;
&lt;li&gt;Note cost and latency&lt;/li&gt;
&lt;li&gt;Pick winner based on quality/cost balance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This 30-minute test beats reading 100 comparison articles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce this yourself
&lt;/h2&gt;

&lt;p&gt;Get free credits to try both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic&lt;/strong&gt; — sign up at console.anthropic.com; check current signup credit offers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI&lt;/strong&gt; — sign up at platform.openai.com; check current signup credit offers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or use browser interfaces (no API required for testing):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude.ai&lt;/strong&gt; — free tier available&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ChatGPT&lt;/strong&gt; — free tier available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test the same prompt in both. Note which handles your specific case better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendation by profile
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Building a code assistant / copilot:&lt;/strong&gt; Claude Sonnet 5&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chat app for general users:&lt;/strong&gt; either (OpenAI edges on ecosystem)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAG on long documents:&lt;/strong&gt; Claude Sonnet 5 (200K context)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-modal image analysis app:&lt;/strong&gt; GPT-5.6-sol&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image generation feature:&lt;/strong&gt; OpenAI (Claude doesn't offer)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety-critical consumer product:&lt;/strong&gt; Claude (better refusals)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice AI:&lt;/strong&gt; OpenAI (native voice mode)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agentic workflow:&lt;/strong&gt; Claude (better instruction-following)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cheapest possible for high volume:&lt;/strong&gt; GPT-5.6-luna or GPT-5&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best free-tier interface:&lt;/strong&gt; both are good&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Neither Claude nor OpenAI is universally better. Both are excellent APIs in 2026.&lt;/p&gt;

&lt;p&gt;For most developer-focused apps, Claude Sonnet 5 is the current best-in-class for quality-per-cost. For general apps needing multimodal or image gen, OpenAI wins.&lt;/p&gt;

&lt;p&gt;Test on your specific use case. Do not choose based on marketing or benchmarks. And re-verify pricing before committing to production — this space moves monthly.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>openai</category>
      <category>api</category>
    </item>
    <item>
      <title>AWS Free Tier in 2026 — what actually stays free</title>
      <dc:creator>aiunplugged</dc:creator>
      <pubDate>Sat, 12 Sep 2026 16:35:21 +0000</pubDate>
      <link>https://dev.to/aiunplugged/aws-free-tier-in-2026-what-actually-stays-free-5bcp</link>
      <guid>https://dev.to/aiunplugged/aws-free-tier-in-2026-what-actually-stays-free-5bcp</guid>
      <description>&lt;p&gt;AWS Free Tier changed significantly on July 15, 2025 — the old "12-month free trial" model was replaced for new accounts with a 6-month plan plus credits. Existing accounts kept the legacy tier. On top of that, AWS began charging for public IPv4 addresses in Feb 2024. If your article on AWS Free Tier is older than that, its numbers are probably outdated. This guide covers the 2026 reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two versions of AWS Free Tier that exist today
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Legacy (accounts created before July 15, 2025):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Traditional 12-month free tier still applies&lt;/li&gt;
&lt;li&gt;Access to Always Free services with monthly limits&lt;/li&gt;
&lt;li&gt;Nothing changed for these accounts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;New AWS Free Plan (accounts created on or after July 15, 2025):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100 USD credits at signup, up to 200 USD through onboarding activities&lt;/li&gt;
&lt;li&gt;Plan lasts 6 months OR until credits are exhausted, whichever comes first&lt;/li&gt;
&lt;li&gt;Account auto-closes when the plan ends&lt;/li&gt;
&lt;li&gt;Also includes Always Free services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Always Free services (both plans):&lt;/strong&gt; 30+ AWS services stay free forever within monthly usage limits, regardless of which plan you're on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Always Free services — free forever within limits
&lt;/h2&gt;

&lt;p&gt;These services stay free regardless of account age or plan type, subject to monthly usage limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compute
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lambda&lt;/strong&gt; — 1 million requests + 400,000 GB-seconds compute per month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB&lt;/strong&gt; — 25 GB storage + 25 read/write capacity units&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SNS / SQS&lt;/strong&gt; — 1 million requests per month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step Functions&lt;/strong&gt; — 4,000 state transitions per month&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Networking
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront&lt;/strong&gt; — 1 TB data transfer + 10 million requests per month&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AI / ML
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rekognition&lt;/strong&gt; — 5,000 image analyses per month (first 12 months)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comprehend&lt;/strong&gt; — 50,000 units of text per month (first 12 months)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polly&lt;/strong&gt; — 5 million standard characters per month (first 12 months)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Monitoring
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch&lt;/strong&gt; — 10 custom metrics, 5 GB log ingestion, 1 million API requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;X-Ray&lt;/strong&gt; — 100,000 traces per month&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Storage
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Glacier&lt;/strong&gt; — 10 GB retrieval per month (data storage costs extra)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Always Free service limits are per AWS account, not per region. Once you exceed the monthly quota, standard billing applies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Legacy 12-month tier (pre-July 2025 accounts only)
&lt;/h2&gt;

&lt;p&gt;If your account was created before July 15, 2025, the traditional 12-month free tier still applies to these services. After 12 months, standard rates kick in.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EC2&lt;/strong&gt; — 750 hours/month of t2.micro or t3.micro&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3&lt;/strong&gt; — 5 GB standard storage + 20,000 GET + 2,000 PUT requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RDS&lt;/strong&gt; — 750 hours/month of db.t2.micro / db.t3.micro&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EBS&lt;/strong&gt; — 30 GB storage + 2 million I/O&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elastic Load Balancer&lt;/strong&gt; — 750 hours/month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ElastiCache&lt;/strong&gt; — 750 hours/month of cache.t3.micro&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Transfer&lt;/strong&gt; — 15 GB out per month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route 53&lt;/strong&gt; — first hosted zone free for 12 months (0.50 USD/month per zone after)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt; — 1 million API calls per month (first 12 months)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real cost after 12 months:&lt;/strong&gt; if leaving these running, a small always-on EC2 + RDS + ELB stack costs around 40 to 80 USD per month.&lt;/p&gt;

&lt;h2&gt;
  
  
  New AWS Free Plan (post-July 2025 accounts)
&lt;/h2&gt;

&lt;p&gt;New accounts don't get a 12-month allowance. Instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with 100 USD credits&lt;/li&gt;
&lt;li&gt;Complete onboarding activities (up to 100 USD more)&lt;/li&gt;
&lt;li&gt;Credits expire 12 months from account creation&lt;/li&gt;
&lt;li&gt;Free Plan status ends after 6 months or when credits hit zero, whichever comes first&lt;/li&gt;
&lt;li&gt;Account auto-closes at that point unless you upgrade to a Paid plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use those credits on any AWS service, so it's more flexible than the old fixed 750-hour allocations. But the runway is shorter — most experimentation projects that used to fit in 12 months now need to fit in 6.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hidden costs that surprise everyone
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Public IPv4 addresses — charged since Feb 2024
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;This is the biggest change most guides miss.&lt;/strong&gt; Since February 1, 2024, AWS charges 0.005 USD/hour (~3.60 USD/month) for every public IPv4 address, whether attached to a running EC2 instance or sitting idle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free tier exception:&lt;/strong&gt; legacy free tier accounts (pre-July 2025) get 750 hours/month of public IPv4 usage free for the first 12 months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this means practically:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every running EC2 with a public IP costs at least 3.60 USD/month beyond free tier&lt;/li&gt;
&lt;li&gt;NAT Gateways, Load Balancers, and VPN connections with public IPs — all charged&lt;/li&gt;
&lt;li&gt;New accounts on the AWS Free Plan don't get the 750-hour IPv4 exemption&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Data transfer OUT
&lt;/h3&gt;

&lt;p&gt;The second most common surprise. Free tier includes limited outbound bandwidth. Beyond that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internet egress: 0.09 USD/GB (US regions), 0.12 USD/GB (many others)&lt;/li&gt;
&lt;li&gt;Cross-region: 0.02 USD/GB&lt;/li&gt;
&lt;li&gt;Cross-AZ within same region: 0.01 USD/GB per direction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A hobbyist blog serving 1 TB/month costs 90 USD or more just in bandwidth.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. RDS storage
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;RDS free tier includes 20 GB storage (legacy accounts only)&lt;/li&gt;
&lt;li&gt;Beyond that: 0.115 USD/GB/month for gp2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backup storage&lt;/strong&gt; — first 100% of allocated is free, additional charged&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. NAT Gateway
&lt;/h3&gt;

&lt;p&gt;Popular for private subnets. Costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0.045 USD/hour (~32 USD/month per gateway)&lt;/li&gt;
&lt;li&gt;0.045 USD/GB data processed&lt;/li&gt;
&lt;li&gt;Plus 0.005 USD/hour for its public IPv4 address&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not in free tier at all. Common surprise.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Support plans
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Basic support: free&lt;/li&gt;
&lt;li&gt;Developer support: 29 USD/month minimum&lt;/li&gt;
&lt;li&gt;Business support: 100 USD/month or 3-10% of AWS bill&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Free tier includes only Basic support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up billing alerts (critical)
&lt;/h2&gt;

&lt;p&gt;Never skip this step. Set alerts BEFORE deploying anything.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AWS Console → Billing → Billing preferences&lt;/li&gt;
&lt;li&gt;Enable "Receive Free Tier Usage Alerts"&lt;/li&gt;
&lt;li&gt;Enter your email&lt;/li&gt;
&lt;li&gt;Go to CloudWatch → Alarms → Create Alarm&lt;/li&gt;
&lt;li&gt;Metric: EstimatedCharges (in USD)&lt;/li&gt;
&lt;li&gt;Set threshold: 5, 10, 25 USD (three alarms recommended)&lt;/li&gt;
&lt;li&gt;Send notification to your email&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now if usage spikes, you get emails before the bill arrives.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to avoid on free tier
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Don't run these on always-on free tier:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NAT Gateway&lt;/strong&gt; — expensive, not free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple availability zone deployments&lt;/strong&gt; — cross-AZ transfer charges&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudTrail data events&lt;/strong&gt; — logging every S3 object access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detailed monitoring&lt;/strong&gt; — 1-minute EC2 metrics cost extra&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPC endpoints&lt;/strong&gt; (7.30 USD/month per endpoint)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple public IPv4 addresses&lt;/strong&gt; — each one is 3.60 USD/month post-2024&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Realistic free/cheap stack for a personal project
&lt;/h2&gt;

&lt;p&gt;Given the 2024 IPv4 charges and the 2025 Free Tier restructure, here's what actually stays cheap for a personal project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A — Static Website (essentially free)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;S3 for storage&lt;/li&gt;
&lt;li&gt;CloudFront for CDN&lt;/li&gt;
&lt;li&gt;Route 53 for DNS (0.50 USD/month after 12 months)&lt;/li&gt;
&lt;li&gt;Certificate Manager for SSL (free)&lt;/li&gt;
&lt;li&gt;Total: essentially 0 USD for personal traffic (CloudFront's 1 TB always-free covers most personal blogs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Option B — Full Stack Web App (legacy accounts only)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1x t3.micro EC2 (running Docker/Node) — free for 12 months, includes 750 hrs public IPv4&lt;/li&gt;
&lt;li&gt;1x db.t3.micro RDS — free for 12 months&lt;/li&gt;
&lt;li&gt;5 GB S3&lt;/li&gt;
&lt;li&gt;CloudFront in front&lt;/li&gt;
&lt;li&gt;Total: around 0 USD for first 12 months, then 40 to 80 USD/month + IPv4 charges&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Option C — Serverless (works on both plan types)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda for compute (1M requests always free)&lt;/li&gt;
&lt;li&gt;DynamoDB for storage (25 GB always free)&lt;/li&gt;
&lt;li&gt;API Gateway for HTTP endpoints (first 12 months free, then 3.50 USD per million calls)&lt;/li&gt;
&lt;li&gt;CloudFront for CDN (1 TB always free)&lt;/li&gt;
&lt;li&gt;Total: often 0 USD for hobbyist traffic, indefinitely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Serverless is the best free-tier fit today because it avoids the always-on EC2 costs and the IPv4 charge entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives worth comparing
&lt;/h2&gt;

&lt;p&gt;For personal projects, AWS free tier is not always the best deal. Verified 2026 limits:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Free Compute&lt;/th&gt;
&lt;th&gt;Free Bandwidth&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS (Always Free)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lambda 1M req/month&lt;/td&gt;
&lt;td&gt;15 GB egress + CloudFront 1 TB&lt;/td&gt;
&lt;td&gt;Always Free services never expire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS (Legacy 12-mo)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;750 hrs t3.micro&lt;/td&gt;
&lt;td&gt;15 GB egress&lt;/td&gt;
&lt;td&gt;Pre-July 2025 accounts only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS (New Free Plan)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100 to 200 USD credits&lt;/td&gt;
&lt;td&gt;Included in credits&lt;/td&gt;
&lt;td&gt;6 months or credit exhaustion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloudflare Pages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unlimited static requests&lt;/td&gt;
&lt;td&gt;Unlimited (unmetered)&lt;/td&gt;
&lt;td&gt;500 builds/month; Functions billed via Workers plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vercel Hobby&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1M function invocations&lt;/td&gt;
&lt;td&gt;100 GB/month&lt;/td&gt;
&lt;td&gt;Also 1M edge requests, 4 CPU hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Netlify (legacy)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;300 build minutes&lt;/td&gt;
&lt;td&gt;100 GB/month&lt;/td&gt;
&lt;td&gt;Pre-Sep 2025 accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Netlify (new)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;300 credits/month&lt;/td&gt;
&lt;td&gt;Credits shared with bandwidth&lt;/td&gt;
&lt;td&gt;Post-Sep 2025 accounts, unified pool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Oracle Cloud&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4 vCPU + 24 GB RAM ARM&lt;/td&gt;
&lt;td&gt;10 TB egress&lt;/td&gt;
&lt;td&gt;Always Free tier (verify with Oracle)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google Cloud&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 e2-micro&lt;/td&gt;
&lt;td&gt;200 GB egress&lt;/td&gt;
&lt;td&gt;Always Free, region-restricted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Fly.io was previously listed as having a free tier. As of late 2024, Fly.io no longer offers a free tier — new users pay from usage.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For blogs and small static sites in 2026, Cloudflare Pages or Vercel Hobby typically beat AWS on both cost and simplicity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce this yourself (safely)
&lt;/h2&gt;

&lt;p&gt;Sign up for AWS free tier: &lt;a href="https://aws.amazon.com/free" rel="noopener noreferrer"&gt;https://aws.amazon.com/free&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before deploying anything:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Complete the billing alert setup above&lt;/li&gt;
&lt;li&gt;Never enter production credit card if just experimenting — use a low-limit card&lt;/li&gt;
&lt;li&gt;Delete resources after testing to avoid accidental charges&lt;/li&gt;
&lt;li&gt;Note whether your account is legacy or new — the free tier you get depends on this&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;For learning AWS without risk:&lt;/strong&gt; use AWS Skill Builder (free courses) or Amazon-provided sandboxes at re:Invent events. These have hard spending limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common surprise-bill scenarios
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Left an EC2 instance running for 8 months → around 60 to 100 USD bill (plus IPv4 charges)&lt;/li&gt;
&lt;li&gt;CloudFront hit by scraping bot → 500 GB egress = 45 USD&lt;/li&gt;
&lt;li&gt;Forgot to delete RDS → around 15 USD/month × months&lt;/li&gt;
&lt;li&gt;Set up NAT Gateway "just to try" → 32 USD/month + IPv4&lt;/li&gt;
&lt;li&gt;Multiple EIPs allocated but not attached → 3.60 USD each per month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real AWS bills for someone experimenting without alerts typically range 20 to 200 USD/month unexpectedly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;AWS Free Tier is real but requires attention, and the rules changed twice recently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Feb 2024:&lt;/strong&gt; public IPv4 addresses became a paid resource&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;July 2025:&lt;/strong&gt; the 12-month free tier was replaced (for new accounts) with a 6-month credit-based plan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always Free services remain the safest bet&lt;/strong&gt; — they never expire and cover Lambda, DynamoDB, CloudFront, and 30+ others&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data transfer OUT is still the biggest hidden cost&lt;/strong&gt; across everything&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Billing alerts are mandatory, not optional&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most personal projects in 2026, Cloudflare Pages or Vercel Hobby offer more predictable cost profiles than AWS. Use AWS when you specifically need its services (Lambda, DynamoDB, ML APIs), not for basic web hosting.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://aiunplugged.in/blog/aws-free-tier-2026-reality/" rel="noopener noreferrer"&gt;aiunplugged.in&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>cloud</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
