<?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: DatanestDigital</title>
    <description>The latest articles on DEV Community by DatanestDigital (@datanestdigital).</description>
    <link>https://dev.to/datanestdigital</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%2F3835307%2Fc0f62441-93d5-47d0-8c94-149af7c53177.png</url>
      <title>DEV Community: DatanestDigital</title>
      <link>https://dev.to/datanestdigital</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/datanestdigital"/>
    <language>en</language>
    <item>
      <title>Data Engineering Interviews: SQL and ETL Questions That Separate Senior from Mid-Level</title>
      <dc:creator>DatanestDigital</dc:creator>
      <pubDate>Tue, 21 Jul 2026 10:39:19 +0000</pubDate>
      <link>https://dev.to/datanestdigital/data-engineering-interviews-sql-and-etl-questions-that-separate-senior-from-mid-level-329j</link>
      <guid>https://dev.to/datanestdigital/data-engineering-interviews-sql-and-etl-questions-that-separate-senior-from-mid-level-329j</guid>
      <description>&lt;p&gt;Most data engineering interview guides throw a list of trivia questions at you — "What's a partition key?", "Explain the difference between a fact and a dimension." That stuff is table stakes. The gap between mid-level and senior is whether you can write the SQL cold and reason through an ETL pipeline on the spot, not whether you can define terms.&lt;/p&gt;

&lt;p&gt;Here are the patterns that interviewers actually use to make that call.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQL: three problems that reveal whether you think in sets
&lt;/h2&gt;

&lt;p&gt;The trick isn't knowing &lt;code&gt;ROW_NUMBER()&lt;/code&gt; exists. It's reaching for it without being told.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Window functions without being prompted
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You have a table of user events. Find each user's most recent event, plus the timestamp of the event immediately before it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- events: user_id, event_type, event_ts&lt;/span&gt;

&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;event_ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;event_ts&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;LAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;event_ts&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;prev_ts&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prev_ts&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;rn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A mid-level candidate writes a self-join with a subquery. A senior candidate uses &lt;code&gt;ROW_NUMBER()&lt;/code&gt; and &lt;code&gt;LAG()&lt;/code&gt; in one pass. The difference is whether you reach for window functions as a default tool, not a last resort.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Cumulative aggregation with a condition
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Given daily sales per product, compute the running total of sales for each product, but only include days where the product sold at least one unit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- daily_sales: product_id, sale_date, units_sold, revenue&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sale_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;units_sold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sale_date&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;running_revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sale_date&lt;/span&gt;
        &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;running_revenue_sold_days_only&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;daily_sales&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;units_sold&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sale_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;WHERE&lt;/code&gt; before the window function does the work. Candidates who try to filter inside the window function itself (which you can't do without a subquery or CTE first) reveal they haven't internalized SQL's order of operations: &lt;code&gt;WHERE&lt;/code&gt; filters rows before the window function sees them.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Deduplication that preserves business logic
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You have a table with duplicate rows where &lt;code&gt;updated_at&lt;/code&gt; differs. Keep the most recent version of each row, but if two rows have the same &lt;code&gt;updated_at&lt;/code&gt;, keep the one with the highest &lt;code&gt;version&lt;/code&gt; field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;entity_id&lt;/span&gt;
            &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rn&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;staging_table&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;rn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;ROW_NUMBER()&lt;/code&gt; with a multi-column &lt;code&gt;ORDER BY&lt;/code&gt; is the cleanest dedup pattern in SQL. &lt;code&gt;DISTINCT&lt;/code&gt; fails when rows differ by a column you care about but aren't "the key." &lt;code&gt;GROUP BY&lt;/code&gt; with &lt;code&gt;MAX(updated_at)&lt;/code&gt; loses columns you want to keep. The interviewer is testing whether you reach for &lt;code&gt;ROW_NUMBER()&lt;/code&gt; — and whether you know to partition by the entity concept, not the table's primary key.&lt;/p&gt;

&lt;h2&gt;
  
  
  ETL design: the 4-step framework
&lt;/h2&gt;

&lt;p&gt;When an interviewer asks "Design an ETL pipeline to ingest data from X into Y," they don't want a list of tools. They want structured thinking. Here's the framework:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Source analysis
&lt;/h3&gt;

&lt;p&gt;Answer these before touching transformation logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shape:&lt;/strong&gt; What does the source schema look like? Is it flat or nested?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volume:&lt;/strong&gt; How much data? Is it batch (nightly dump) or streaming?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cadence:&lt;/strong&gt; How frequently does it arrive? Is it append-only, upsert, or full refresh?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quirks:&lt;/strong&gt; Known data quality issues — nulls masquerading as &lt;code&gt;'N/A'&lt;/code&gt;, duplicate events, timezone mismatches.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Transformation logic
&lt;/h3&gt;

&lt;p&gt;State the transformations as a sequence, not a diagram:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Normalize&lt;/strong&gt; — flatten nested JSON, split multi-value columns, cast types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean&lt;/strong&gt; — null handling, deduplication, outlier flagging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enrich&lt;/strong&gt; — join with dimension tables, derive computed columns (e.g., &lt;code&gt;order_age_days&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate&lt;/strong&gt; — constraints that must hold before loading (&lt;code&gt;NOT NULL&lt;/code&gt; on key columns, referential integrity on FK joins)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3: Error handling
&lt;/h3&gt;

&lt;p&gt;The interviewer wants to hear that you don't let one bad row kill the pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dead-letter queue&lt;/strong&gt; — route unparseable records to a quarantine table with the raw payload + error reason + timestamp&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry logic&lt;/strong&gt; — transient failures (network, API rate limits) get exponential backoff&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alert threshold&lt;/strong&gt; — if &amp;gt;X% of records land in quarantine, page the on-call&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: Scheduling and scale
&lt;/h3&gt;

&lt;p&gt;Close with the operational layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration&lt;/strong&gt; — what triggers the pipeline? Cron, Airflow, event-driven (S3 bucket notification)?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute&lt;/strong&gt; — Spark for large transforms, SQL for small ones. Justify the choice with the volume from Step 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency&lt;/strong&gt; — can the pipeline run twice on the same input without corrupting the target? If not, how do you guard against it?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Data modeling: what they're actually testing
&lt;/h2&gt;

&lt;p&gt;Data modeling questions in DE interviews aren't about normal forms. They're about whether you can take a vague business question and produce a schema that answers it efficiently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt; "Design a schema for a subscription business that needs to report monthly recurring revenue (MRR) by plan tier."&lt;/p&gt;

&lt;p&gt;Good answer (star schema):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Fact: one row per subscription per month&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;fact_mrr&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;subscription_sk&lt;/span&gt;  &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;-- FK to dim_subscription&lt;/span&gt;
    &lt;span class="n"&gt;date_sk&lt;/span&gt;          &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;-- FK to dim_date&lt;/span&gt;
    &lt;span class="n"&gt;plan_sk&lt;/span&gt;          &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;-- FK to dim_plan&lt;/span&gt;
    &lt;span class="n"&gt;customer_sk&lt;/span&gt;      &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;-- FK to dim_customer&lt;/span&gt;
    &lt;span class="n"&gt;mrr_amount&lt;/span&gt;       &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;is_active&lt;/span&gt;        &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Dimensions&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dim_subscription&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subscription_sk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subscription_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;started_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dim_customer&lt;/span&gt;     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_sk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dim_plan&lt;/span&gt;         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan_sk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan_tier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;monthly_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dim_date&lt;/span&gt;         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_sk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quarter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;year&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;What this shows the interviewer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You know star schema means "fact at the center, dimensions around it"&lt;/li&gt;
&lt;li&gt;You understand surrogate keys (&lt;code&gt;_sk&lt;/code&gt;) and why they're safer than natural keys in a data warehouse&lt;/li&gt;
&lt;li&gt;You pre-computed &lt;code&gt;mrr_amount&lt;/code&gt; into the fact table rather than joining to &lt;code&gt;dim_plan.monthly_price&lt;/code&gt; on every query — you understand that warehouse performance comes from denormalization at write time&lt;/li&gt;
&lt;li&gt;You included &lt;code&gt;is_active&lt;/code&gt; as a flag, which means you're thinking about slowly changing dimensions (subscriptions get cancelled, but historical MRR shouldn't recalculate retroactively)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A mid-level answer is: "Subscription table, customer table, plan table, join them." A senior answer is: "Star schema with pre-computed MRR in the fact table, surrogate keys, and an active flag so historical reports don't shift."&lt;/p&gt;




&lt;p&gt;The patterns above — SQL with window functions, structured ETL reasoning, and dimensional modeling with a star schema — are the consistent differentiator in DE interviews. If you want the full set of SQL challenges, ETL scenarios, data modeling exercises, and system design walkthroughs with worked solutions for each, the &lt;a href="https://datanest-stores.pages.dev/interview-prep/preview-data-engineering-interview.html" rel="noopener noreferrer"&gt;Data Engineering Interview Guide&lt;/a&gt; packages them into a structured study plan.&lt;/p&gt;

</description>
      <category>career</category>
      <category>dataengineering</category>
      <category>sql</category>
      <category>interview</category>
    </item>
    <item>
      <title>Build a Static Site Generator in Pure Python (stdlib Only)</title>
      <dc:creator>DatanestDigital</dc:creator>
      <pubDate>Wed, 15 Jul 2026 13:21:01 +0000</pubDate>
      <link>https://dev.to/datanestdigital/build-a-static-site-generator-in-pure-python-stdlib-only-20fm</link>
      <guid>https://dev.to/datanestdigital/build-a-static-site-generator-in-pure-python-stdlib-only-20fm</guid>
      <description>&lt;p&gt;Liquid syntax error: Unknown tag 'endraw'&lt;/p&gt;
</description>
      <category>python</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>ssg</category>
    </item>
    <item>
      <title>7 Terraform Patterns That Survive Production</title>
      <dc:creator>DatanestDigital</dc:creator>
      <pubDate>Tue, 14 Jul 2026 10:39:38 +0000</pubDate>
      <link>https://dev.to/datanestdigital/7-terraform-patterns-that-survive-production-4clf</link>
      <guid>https://dev.to/datanestdigital/7-terraform-patterns-that-survive-production-4clf</guid>
      <description>&lt;h1&gt;
  
  
  7 Terraform Patterns That Survive Production
&lt;/h1&gt;

&lt;p&gt;Nobody's Terraform stays clean past the second sprint. You start with a tidy &lt;code&gt;main.tf&lt;/code&gt; and a handful of resources. Six months later, you've got a 4000-line monolith, state files you're afraid to touch, and a &lt;code&gt;terraform plan&lt;/code&gt; that takes long enough to go make coffee.&lt;/p&gt;

&lt;p&gt;After five years of writing infrastructure-as-code across three cloud providers and more refactors than I'd like to admit, here are the seven patterns that actually hold up when your infrastructure stops being a demo and starts being production.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Remote State From Day One
&lt;/h2&gt;

&lt;p&gt;If you only remember one thing from this list, make it this one. Local state is a ticking time bomb—it sits on one person's laptop, drifts from reality, and can't be shared across a team.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"myorg-terraform-state"&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prod/networking/terraform.tfstate"&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eu-west-1"&lt;/span&gt;
    &lt;span class="nx"&gt;dynamodb_table&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-locks"&lt;/span&gt;
    &lt;span class="nx"&gt;encrypt&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;The DynamoDB table gives you state locking—no two engineers running &lt;code&gt;terraform apply&lt;/code&gt; at the same time and racing each other's changes. Set this up before your first &lt;code&gt;terraform init&lt;/code&gt;. Migrating existing local state to remote is doable but painful, and the longer you wait the scarier that migration becomes.&lt;/p&gt;

&lt;p&gt;Pro tip: use separate state files per logical environment and per component. One monolithic state file for everything is almost as bad as no remote state at all. Small blast radius, small plan output, small anxiety.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Module Composition, Not Module Explosion
&lt;/h2&gt;

&lt;p&gt;The pendulum swung hard. First we had no modules. Then the community decided &lt;em&gt;everything&lt;/em&gt; should be a module, and we ended up with 40 tiny modules that each wrap a single resource and add nothing but indirection.&lt;/p&gt;

&lt;p&gt;The sweet spot: &lt;strong&gt;modules that represent an opinionated deployment unit&lt;/strong&gt;, not a resource wrapper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Good: a module that deploys a complete RDS instance&lt;/span&gt;
&lt;span class="c1"&gt;# with encryption, backups, monitoring, and subnet group&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"primary_db"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/rds-postgres"&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"orders-db"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.r6g.large"&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_ids&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;database_subnet_ids&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_id&lt;/span&gt;
  &lt;span class="nx"&gt;multi_az&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Avoid: a module that just wraps aws_security_group&lt;/span&gt;
&lt;span class="c1"&gt;# with no added value&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"sg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/security-group"&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"allow-http"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A good module encodes your organisation's standards—it ensures every database gets encryption, backup retention, and the right monitoring alarms. A bad module is just a &lt;code&gt;for_each&lt;/code&gt; with extra steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Terraform Workspaces or Directory Separation—Pick One and Commit
&lt;/h2&gt;

&lt;p&gt;There are two camps for managing environments (dev, staging, prod):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workspace-based:&lt;/strong&gt; One config, multiple workspaces, differentiated by &lt;code&gt;terraform.workspace&lt;/code&gt; lookups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;instance_sizes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;dev&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.small"&lt;/span&gt;
    &lt;span class="nx"&gt;staging&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.medium"&lt;/span&gt;
    &lt;span class="nx"&gt;prod&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.large"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_sizes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace&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;&lt;strong&gt;Directory-based:&lt;/strong&gt; Separate directories per environment, each with its own &lt;code&gt;terraform.tfvars&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;infra/
  dev/
    main.tf
    terraform.tfvars
  staging/
    main.tf
    terraform.tfvars
  prod/
    main.tf
    terraform.tfvars
  modules/
    app/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both work. Pick one and enforce it with CI. The only unforgivable sin is doing both at the same time and confusing the entire team.&lt;/p&gt;

&lt;p&gt;I lean toward directory separation for production: it makes the blast radius explicit, gives you independent state files per environment, and makes it impossible to accidentally run &lt;code&gt;terraform destroy&lt;/code&gt; in prod because you had the wrong workspace selected.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The &lt;code&gt;.tfvars&lt;/code&gt; Hierarchy
&lt;/h2&gt;

&lt;p&gt;Variables spread across three layers beats a single &lt;code&gt;terraform.tfvars&lt;/code&gt; that nobody can read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Layer 1: terraform.tfvars (committed, shared defaults)&lt;/span&gt;
&lt;span class="nx"&gt;region&lt;/span&gt;     &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eu-west-1"&lt;/span&gt;
&lt;span class="nx"&gt;node_count&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="c1"&gt;# Layer 2: env.tfvars (committed, environment-specific)&lt;/span&gt;
&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt;
&lt;span class="nx"&gt;node_count&lt;/span&gt;  &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"c5.2xlarge"&lt;/span&gt;

&lt;span class="c1"&gt;# Layer 3: secrets.auto.tfvars (gitignored, CI-injected)&lt;/span&gt;
&lt;span class="nx"&gt;db_password&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;from vault&amp;gt;"&lt;/span&gt;
&lt;span class="nx"&gt;api_key&lt;/span&gt;     &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;from secrets manager&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform automatically loads any &lt;code&gt;.auto.tfvars&lt;/code&gt; files, so naming your secret file &lt;code&gt;secrets.auto.tfvars&lt;/code&gt; keeps the apply command clean (&lt;code&gt;terraform apply&lt;/code&gt;, not &lt;code&gt;terraform apply -var-file=secrets.tfvars -var-file=prod.tfvars&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Keep committed tfvars lean—configuration that changes per environment, not credentials. Sensitive values should hit your state file with the &lt;code&gt;sensitive = true&lt;/code&gt; attribute so they don't leak into plan output or CI logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Plan Output as a PR Gate
&lt;/h2&gt;

&lt;p&gt;A Terraform plan is worth a thousand code review comments. Wire it into your CI so that every pull request posts the plan output as a PR comment, and &lt;code&gt;terraform apply&lt;/code&gt; runs only on merge to &lt;code&gt;main&lt;/code&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="c1"&gt;# GitHub Actions sketch&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;Terraform Plan&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;infra/**'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform init&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plan&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform plan -no-color -out=plan.tfplan&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v7&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;const output = `### Terraform Plan\n\`\`\`\n${plan.stdout}\n\`\`\``;&lt;/span&gt;
            &lt;span class="s"&gt;github.rest.issues.createComment({&lt;/span&gt;
              &lt;span class="s"&gt;issue_number: context.issue.number,&lt;/span&gt;
              &lt;span class="s"&gt;owner: context.repo.owner,&lt;/span&gt;
              &lt;span class="s"&gt;repo: context.repo.repo,&lt;/span&gt;
              &lt;span class="s"&gt;body: output&lt;/span&gt;
            &lt;span class="s"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catches drift before it reaches production. It also means the person reviewing your PR can see exactly what infrastructure changes your code will make—no guessing, no "seems fine, ship it."&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Use &lt;code&gt;moved&lt;/code&gt; Blocks Instead of State Surgery
&lt;/h2&gt;

&lt;p&gt;Terraform 1.1 introduced &lt;code&gt;moved&lt;/code&gt; blocks, and they replaced the most terrifying operation in the Terraform workflow: &lt;code&gt;terraform state mv&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before &lt;code&gt;moved&lt;/code&gt; blocks, renaming a resource or moving it into a module meant carefully running state manipulation commands. Get it wrong and Terraform would destroy the old resource and create a new one—fine for a security group, catastrophic for a database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Rename a resource without destroying it&lt;/span&gt;
&lt;span class="nx"&gt;moved&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web_server&lt;/span&gt;
  &lt;span class="nx"&gt;to&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Move into a module&lt;/span&gt;
&lt;span class="nx"&gt;moved&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;old_name&lt;/span&gt;
  &lt;span class="nx"&gt;to&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;new_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit &lt;code&gt;moved&lt;/code&gt; blocks alongside the rename. They're declarative, reviewable, and safe. After the apply succeeds and the state has migrated, you can remove them—or leave them in for documentation. They're inert once the move is complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The &lt;code&gt;terraform plan&lt;/code&gt; Litmus Test
&lt;/h2&gt;

&lt;p&gt;Here's a single rule that catches most Terraform hygiene problems: &lt;strong&gt;after every apply, run &lt;code&gt;terraform plan&lt;/code&gt; again. If it doesn't come back clean ("No changes"), you have a bug.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A non-empty plan after apply means one of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A resource has a computed attribute that isn't stored in state yet (common with AWS default values)&lt;/li&gt;
&lt;li&gt;A data source is non-deterministic (&lt;code&gt;timestamp()&lt;/code&gt;, &lt;code&gt;uuid()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;You have a &lt;code&gt;null_resource&lt;/code&gt; trigger that changes every run&lt;/li&gt;
&lt;li&gt;A provider default is being applied as an explicit value on the next refresh&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is fixable, and each of them adds noise to real plans that should only show your actual changes. A noisy plan trains the team to ignore plan output—exactly what you don't want when someone accidentally adds &lt;code&gt;force_destroy = true&lt;/code&gt; to an S3 bucket.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;terraform plan -detailed-exitcode&lt;/code&gt; to your CI pipeline after every apply and fail the pipeline if the exit code isn't &lt;code&gt;0&lt;/code&gt; (clean).&lt;/p&gt;




&lt;p&gt;These patterns aren't theoretical—they're what's left after the tutorials end and the on-call rotations begin. Each one addresses a real failure mode I've watched teams stumble into, and each one takes almost no effort to adopt from the start.&lt;/p&gt;

&lt;p&gt;The best time to add remote state was before your first commit. The second-best time is now.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For more infrastructure and DevOps patterns, check out &lt;a href="https://theshiplog.blogspot.com" rel="noopener noreferrer"&gt;The Ship Log&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
      <category>infrastructureascode</category>
      <category>aws</category>
    </item>
    <item>
      <title>Social Media Content Kit</title>
      <dc:creator>DatanestDigital</dc:creator>
      <pubDate>Thu, 25 Jun 2026 12:54:51 +0000</pubDate>
      <link>https://dev.to/datanestdigital/social-media-content-kit-1j3a</link>
      <guid>https://dev.to/datanestdigital/social-media-content-kit-1j3a</guid>
      <description>&lt;h1&gt;
  
  
  Social Media Content Kit
&lt;/h1&gt;

&lt;p&gt;200+ social post templates for Twitter/X, LinkedIn, Instagram, and Threads. Includes fill-in-the-blank frameworks organized by goal (engagement, authority, promotion, storytelling), hashtag strategy guides, scheduling systems, and an engagement playbook. Never run out of content ideas again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;200+ post templates&lt;/strong&gt; across 4 platforms, organized by content goal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform-specific formatting&lt;/strong&gt; — character limits, line breaks, and hashtag rules per network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content calendar integration&lt;/strong&gt; — 30-day posting schedules for each platform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hashtag strategy guide&lt;/strong&gt; — research methods, volume tiers, and per-post limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engagement playbook&lt;/strong&gt; — comment strategies, DM templates, and community-building tactics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics tracking template&lt;/strong&gt; — weekly metrics for reach, engagement rate, and follower growth&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Included
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;social-media-content-kit/
├── templates/
│   ├── twitter/
│   │   ├── educational-threads.md      # Step-by-step, mental models, how-to
│   │   ├── storytelling-posts.md       # Personal stories, origin stories
│   │   ├── hot-takes-opinions.md       # Contrarian views, debates
│   │   ├── list-posts.md               # Numbered lists, resource compilations
│   │   ├── build-in-public.md          # Progress updates, transparency
│   │   ├── promotional-posts.md        # Product launches, value-first promo
│   │   └── engagement-posts.md         # Questions, polls, conversations
│   ├── linkedin/
│   │   ├── professional-posts.md       # Career insights, framework posts
│   │   └── story-posts.md              # Personal narratives, lessons
│   ├── threads/
│   │   └── conversation-starters.md    # Casual, authentic, community-focused
│   └── instagram/
│       ├── educational-carousels.md    # Carousel templates, step-by-step
│       ├── storytelling-posts.md       # Caption templates, narratives
│       └── engagement-promotional.md   # Growth, community, soft selling
├── guides/
│   ├── hook-formulas.md                # 50+ opening line formulas
│   ├── hashtag-strategy.md             # Platform-specific hashtag guide
│   ├── engagement-playbook.md          # Community growth tactics
│   └── content-calendar-strategy.md    # Content pillar planning
├── tools/
│   ├── posting-schedule.csv            # 4-week multi-platform calendar
│   ├── post-performance-tracker.csv    # Track what works
│   ├── hashtag-bank.csv                # Organized hashtag collections
│   └── content-ideas-bank.csv          # Idea capture system
├── examples/
│   ├── sample_twitter_week.md          # 7 days of Twitter content
│   └── sample_linkedin_week.md         # 7 days of LinkedIn content
├── README.md
└── LICENSE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Pick your &lt;strong&gt;primary platform&lt;/strong&gt; (start with one, expand later)&lt;/li&gt;
&lt;li&gt;Choose 5-7 templates from the matching template file&lt;/li&gt;
&lt;li&gt;Fill in placeholders with your content and expertise&lt;/li&gt;
&lt;li&gt;Schedule posts using the &lt;strong&gt;30-Day Calendar&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Apply the &lt;strong&gt;Engagement Playbook&lt;/strong&gt; daily (15 minutes)&lt;/li&gt;
&lt;li&gt;Track results weekly with the &lt;strong&gt;Analytics Tracker&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Template Categories
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Templates&lt;/th&gt;
&lt;th&gt;Best Platforms&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Authority&lt;/td&gt;
&lt;td&gt;Build credibility&lt;/td&gt;
&lt;td&gt;40+&lt;/td&gt;
&lt;td&gt;LinkedIn, Twitter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engagement&lt;/td&gt;
&lt;td&gt;Drive replies and shares&lt;/td&gt;
&lt;td&gt;50+&lt;/td&gt;
&lt;td&gt;Twitter, Threads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storytelling&lt;/td&gt;
&lt;td&gt;Build personal brand&lt;/td&gt;
&lt;td&gt;35+&lt;/td&gt;
&lt;td&gt;LinkedIn, Instagram&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Promotion&lt;/td&gt;
&lt;td&gt;Drive traffic/sales&lt;/td&gt;
&lt;td&gt;25+&lt;/td&gt;
&lt;td&gt;All platforms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Education&lt;/td&gt;
&lt;td&gt;Share expertise&lt;/td&gt;
&lt;td&gt;35+&lt;/td&gt;
&lt;td&gt;LinkedIn, Twitter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;td&gt;Start conversations&lt;/td&gt;
&lt;td&gt;20+&lt;/td&gt;
&lt;td&gt;Threads, Twitter&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Twitter/X Templates (Samples)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Authority Post
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;[TOPIC] isn't as complicated as people make it.

Here's the framework I use:

Step 1: [ACTION]
Step 2: [ACTION]
Step 3: [ACTION]

The key most people miss: [INSIGHT].

[OPTIONAL: thread continues below]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Engagement Post
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Hot take: [CONTRARIAN_OPINION about TOPIC].

[REASON_1 — one sentence]
[REASON_2 — one sentence]

Agree or disagree? Quote tweet with your take.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Thread Opener
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;I spent [TIME] [DOING_THING] and learned [NUMBER] lessons the hard way.

Here's what I wish someone told me on day 1:

Thread ↓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  LinkedIn Templates (Samples)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Value Post
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;I've [DONE_THING] for [TIME_PERIOD].

Here are [NUMBER] things I'd do differently:
&lt;span class="p"&gt;
1.&lt;/span&gt; [LESSON] → [WHY_IT_MATTERS]
&lt;span class="p"&gt;2.&lt;/span&gt; [LESSON] → [WHY_IT_MATTERS]
&lt;span class="p"&gt;3.&lt;/span&gt; [LESSON] → [WHY_IT_MATTERS]

The biggest one? Number [X].

Because [EXPLANATION — 2-3 sentences].

What would you add to this list?

&lt;span class="gh"&gt;#[HASHTAG_1] #[HASHTAG_2] #[HASHTAG_3]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Story Post
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Last [TIMEFRAME], something happened that changed how I think about [TOPIC].

[STORY — 4-6 sentences, specific details, emotional arc]

The takeaway?

[LESSON — one clear sentence]

This applies to [BROADER_CONTEXT] too.

Have you experienced something similar? I'd love to hear your story.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  30-Day Posting Schedule (Twitter/X)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Post Type&lt;/th&gt;
&lt;th&gt;Template Category&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;Mon&lt;/td&gt;
&lt;td&gt;Thread (5-8 tweets)&lt;/td&gt;
&lt;td&gt;Education&lt;/td&gt;
&lt;td&gt;Deep-dive on one topic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tue&lt;/td&gt;
&lt;td&gt;Single tweet&lt;/td&gt;
&lt;td&gt;Engagement&lt;/td&gt;
&lt;td&gt;Question or hot take&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wed&lt;/td&gt;
&lt;td&gt;Quote tweet + commentary&lt;/td&gt;
&lt;td&gt;Authority&lt;/td&gt;
&lt;td&gt;React to industry news&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thu&lt;/td&gt;
&lt;td&gt;Story tweet&lt;/td&gt;
&lt;td&gt;Storytelling&lt;/td&gt;
&lt;td&gt;Personal experience&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fri&lt;/td&gt;
&lt;td&gt;List tweet&lt;/td&gt;
&lt;td&gt;Education&lt;/td&gt;
&lt;td&gt;"[NUMBER] tools/tips for [TOPIC]"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sat&lt;/td&gt;
&lt;td&gt;Repost best performer&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Share your top tweet from the week&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sun&lt;/td&gt;
&lt;td&gt;Reflection / question&lt;/td&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;td&gt;Ask your audience something&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Repeat weekly. Rotate topics to avoid repetition.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hashtag Quick Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Max Hashtags&lt;/th&gt;
&lt;th&gt;Best Practice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Twitter/X&lt;/td&gt;
&lt;td&gt;2-3&lt;/td&gt;
&lt;td&gt;Less is more — 1-2 targeted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LinkedIn&lt;/td&gt;
&lt;td&gt;3-5&lt;/td&gt;
&lt;td&gt;Mix broad + niche, end of post&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instagram&lt;/td&gt;
&lt;td&gt;5-15&lt;/td&gt;
&lt;td&gt;Mix size tiers, first comment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Threads&lt;/td&gt;
&lt;td&gt;0-2&lt;/td&gt;
&lt;td&gt;Very sparingly, inline only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use 3 tiers: &lt;strong&gt;Large&lt;/strong&gt; (100K+ posts, broad reach), &lt;strong&gt;Medium&lt;/strong&gt; (10K-100K, niche), &lt;strong&gt;Small&lt;/strong&gt; (1K-10K, targeted). Mix tiers per post for optimal reach vs. discoverability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Batch-create a week of content in 60-90 minutes.&lt;/strong&gt; Fill 7 templates, schedule, and forget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customize every template.&lt;/strong&gt; The framework is the skeleton — your voice and expertise are the flesh.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post consistently before posting frequently.&lt;/strong&gt; 3 great posts/week beats 3 mediocre posts/day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engage 15 minutes before and after posting.&lt;/strong&gt; The algorithm rewards active users.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lead with value, not promotion.&lt;/strong&gt; Follow the 80/20 rule — 80% value, 20% promotional.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the first line as a hook.&lt;/strong&gt; On every platform, you have 1-2 seconds to stop the scroll.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repurpose across platforms but reformat.&lt;/strong&gt; A LinkedIn post is not a tweet. Adapt the template per platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track your top 5 posts monthly.&lt;/strong&gt; Identify what content type and topic resonates — double down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engage on others' posts as much as you create.&lt;/strong&gt; Commenting builds relationships that sharing alone can't.&lt;/li&gt;
&lt;/ol&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This is 1 of 11 resources in the &lt;a href="https://datanest-stores.pages.dev/content-creator/" rel="noopener noreferrer"&gt;Content Creator Toolkit&lt;/a&gt; toolkit.&lt;/strong&gt; Get the complete [Social Media Content Kit] with all files, templates, and documentation for $25.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://buy.stripe.com/14A5kD9uTc98dNEdSFgjG3P" rel="noopener noreferrer"&gt;Get the Full Kit →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Or grab the entire Content Creator Toolkit bundle (11 products) for $129 — save 30%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://buy.stripe.com/6oUdR95eD7SSeRI3e1gjG3W" rel="noopener noreferrer"&gt;Get the Complete Bundle →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Related Articles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/datanestdigital/technical-blog-post-templates-4mol"&gt;Technical Blog Post Templates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/datanestdigital/content-repurposing-system-46lb"&gt;Content Repurposing System&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/datanestdigital/online-course-creation-framework-2g8l"&gt;Online Course Creation Framework&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>writing</category>
      <category>marketing</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Choosing a Vector Database in 2026: pgvector vs Pinecone vs Qdrant vs Weaviate</title>
      <dc:creator>DatanestDigital</dc:creator>
      <pubDate>Sat, 20 Jun 2026 11:30:48 +0000</pubDate>
      <link>https://dev.to/datanestdigital/choosing-a-vector-database-in-2026-pgvector-vs-pinecone-vs-qdrant-vs-weaviate-lef</link>
      <guid>https://dev.to/datanestdigital/choosing-a-vector-database-in-2026-pgvector-vs-pinecone-vs-qdrant-vs-weaviate-lef</guid>
      <description>&lt;p&gt;Once your RAG prototype works, the next decision has real consequences: where do the embeddings live? Pick wrong and you either over-pay for a managed service you didn't need, or you bolt a vector index onto a database that buckles at scale. This is the comparison I wish existed when I had to choose.&lt;/p&gt;

&lt;p&gt;We'll look at four representative options — &lt;strong&gt;pgvector&lt;/strong&gt;, &lt;strong&gt;Pinecone&lt;/strong&gt;, &lt;strong&gt;Qdrant&lt;/strong&gt;, and &lt;strong&gt;Weaviate&lt;/strong&gt; — and, more usefully, the questions that should drive the decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, do you even need a dedicated vector database?
&lt;/h2&gt;

&lt;p&gt;If you have under a few hundred thousand vectors and already run Postgres, the answer is often &lt;strong&gt;no&lt;/strong&gt;. &lt;code&gt;pgvector&lt;/code&gt; adds vector search to a database you already operate, back up, and trust.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&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;id&lt;/span&gt; &lt;span class="n"&gt;bigserial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1536&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;hnsw&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;vector_cosine_ops&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[...]'&lt;/span&gt;   &lt;span class="c1"&gt;-- cosine distance&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;One datastore, transactional consistency with your real data, no new service.&lt;/strong&gt; The trade-off is that very large or very high-QPS workloads will eventually outgrow it, and its filtering/index tuning is less specialized than purpose-built engines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four, at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;pgvector&lt;/th&gt;
&lt;th&gt;Pinecone&lt;/th&gt;
&lt;th&gt;Qdrant&lt;/th&gt;
&lt;th&gt;Weaviate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Postgres extension&lt;/td&gt;
&lt;td&gt;fully managed SaaS&lt;/td&gt;
&lt;td&gt;open-source + managed&lt;/td&gt;
&lt;td&gt;open-source + managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run it yourself&lt;/td&gt;
&lt;td&gt;yes (it's Postgres)&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best when&lt;/td&gt;
&lt;td&gt;already on Postgres, modest scale&lt;/td&gt;
&lt;td&gt;want zero ops, fast scale&lt;/td&gt;
&lt;td&gt;want control + strong filtering&lt;/td&gt;
&lt;td&gt;want built-in hybrid/modules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata filtering&lt;/td&gt;
&lt;td&gt;SQL &lt;code&gt;WHERE&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;good&lt;/td&gt;
&lt;td&gt;excellent&lt;/td&gt;
&lt;td&gt;good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops burden&lt;/td&gt;
&lt;td&gt;low (existing DB)&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Pinecone — pay to not think about infrastructure
&lt;/h2&gt;

&lt;p&gt;Pinecone is fully managed: no servers, no index tuning, scales horizontally on demand. You trade money and vendor lock-in for never paging anyone about your vector store.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose it when&lt;/strong&gt; ops time is more expensive than service fees, you need to scale fast, and you don't want to own infrastructure. &lt;strong&gt;Be wary when&lt;/strong&gt; cost at scale matters or you need to self-host for data-residency reasons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qdrant — open-source with serious filtering
&lt;/h2&gt;

&lt;p&gt;Qdrant is a purpose-built vector engine (Rust) with excellent &lt;strong&gt;payload filtering&lt;/strong&gt; — combining vector similarity with structured conditions efficiently, which is exactly what production RAG needs ("similar chunks, but only from this tenant, in the last 90 days").&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;collection_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;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;query_vector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;query_filter&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;must&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;key&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;tenant&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;match&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;value&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;acme&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}]},&lt;/span&gt;
    &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&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;&lt;strong&gt;Choose it when&lt;/strong&gt; you want to self-host, need strong metadata filtering, and want a managed option available later. Run it in Docker for dev, scale to a cluster for prod.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weaviate — batteries-included with hybrid search
&lt;/h2&gt;

&lt;p&gt;Weaviate bundles more out of the box: built-in &lt;strong&gt;hybrid search&lt;/strong&gt; (combining keyword BM25 with vector similarity) and optional modules that generate embeddings for you. It's the "more than a vector store" option.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose it when&lt;/strong&gt; you want hybrid search without wiring it yourself, or want the database to handle vectorization. &lt;strong&gt;Be wary when&lt;/strong&gt; you prefer a minimal, do-one-thing component you fully control.&lt;/p&gt;

&lt;h2&gt;
  
  
  The questions that actually decide it
&lt;/h2&gt;

&lt;p&gt;Forget feature checklists; answer these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scale.&lt;/strong&gt; Under ~1M vectors and modest QPS? pgvector likely wins on simplicity. Tens of millions and growing? Look at the purpose-built engines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ops appetite.&lt;/strong&gt; Want zero infrastructure? Pinecone (or a managed Qdrant/Weaviate). Happy to run a container? Self-host and save.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filtering.&lt;/strong&gt; If most queries are "similar &lt;em&gt;and&lt;/em&gt; matching these attributes," weight filtering quality heavily — Qdrant is strong here, and pgvector gets full SQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data gravity.&lt;/strong&gt; If your source data is already in Postgres, keeping vectors there removes a sync pipeline and a consistency headache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid search.&lt;/strong&gt; Need keyword + semantic together? Weaviate gives it natively; others need extra work.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  My default recommendation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with pgvector.&lt;/strong&gt; Most teams over-engineer this decision and reach for a distributed vector database to store 50,000 chunks. Ship on Postgres, measure real query latency and recall, and migrate to a dedicated engine &lt;em&gt;only&lt;/em&gt; when you have evidence you've outgrown it. The migration is straightforward once you know your actual access patterns — and you'll choose far better with real numbers than with a benchmark blog post.&lt;/p&gt;

&lt;p&gt;If you'd rather skip the boilerplate of wiring up embeddings, indexing, and a swappable retrieval layer, the &lt;a href="https://datanest-stores.pages.dev/ai-llm-toolkit/preview-vector-database-toolkit.html" rel="noopener noreferrer"&gt;Vector Database Toolkit&lt;/a&gt; provides adapters and ingestion patterns across these backends so you can prototype on pgvector and switch engines later without rewriting your app.&lt;/p&gt;

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

&lt;p&gt;There's no universally best vector database — there's the one that matches your scale, ops appetite, and filtering needs. Resist premature optimization: begin with the simplest thing that could work (often pgvector), instrument it, and let real usage, not hype, tell you when to graduate.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>machinelearning</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop Hardcoding Secrets: A Practical Secrets-Management Guide (2026)</title>
      <dc:creator>DatanestDigital</dc:creator>
      <pubDate>Sat, 20 Jun 2026 11:30:12 +0000</pubDate>
      <link>https://dev.to/datanestdigital/stop-hardcoding-secrets-a-practical-secrets-management-guide-2026-43n6</link>
      <guid>https://dev.to/datanestdigital/stop-hardcoding-secrets-a-practical-secrets-management-guide-2026-43n6</guid>
      <description>&lt;p&gt;Every secret leak starts the same way: someone, under deadline pressure, pastes an API key directly into the code "just to test it," and forgets. Six months later it's in your git history, on a laptop backup, and in a screenshot in a Slack thread. By the time you notice, it has been public for weeks.&lt;/p&gt;

&lt;p&gt;Secrets management isn't a product you buy once — it's a set of habits that make leaking a secret hard and recovering from one fast. Here's the practical version.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one rule: secrets never touch source code
&lt;/h2&gt;

&lt;p&gt;If a credential is in a file you commit, it's compromised the moment that repo is cloned, forked, or leaked — and rewriting git history rarely fully erases it. So the entire game is keeping secrets &lt;em&gt;out&lt;/em&gt; of the code and &lt;em&gt;out&lt;/em&gt; of the repo.&lt;/p&gt;

&lt;p&gt;Start with a &lt;code&gt;.gitignore&lt;/code&gt; and a &lt;code&gt;.dockerignore&lt;/code&gt; that exclude them, and commit a &lt;code&gt;.env.example&lt;/code&gt; with empty placeholders so contributors know what's needed without seeing real values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# .gitignore
&lt;/span&gt;.&lt;span class="n"&gt;env&lt;/span&gt;
*.&lt;span class="n"&gt;pem&lt;/span&gt;
*.&lt;span class="n"&gt;key&lt;/span&gt;
**/&lt;span class="n"&gt;secrets&lt;/span&gt;*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Level 1: environment variables (the floor, not the ceiling)
&lt;/h2&gt;

&lt;p&gt;The minimum viable approach: load secrets from the environment at runtime, never from a committed file.&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;os&lt;/span&gt;
&lt;span class="n"&gt;DB_PASSWORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# fail loudly if missing
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is fine for small apps, but env vars have real limits: they leak through crash dumps, child processes, and &lt;code&gt;/proc&lt;/code&gt;; they're hard to rotate; and there's no audit trail. Treat them as a starting point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 2: a real secret manager
&lt;/h2&gt;

&lt;p&gt;For anything production, use a dedicated secret store — HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, or your platform's built-in store. The app authenticates with a short-lived identity and fetches secrets at startup or on demand.&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;boto3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&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;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secretsmanager&lt;/span&gt;&lt;span class="sh"&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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&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;get_secret_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SecretId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SecretString&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;What you gain over plain env vars: centralized storage, access control per identity, an audit log of who read what, and rotation support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rotate on a schedule, not after a breach
&lt;/h2&gt;

&lt;p&gt;A secret that never changes is a secret that's one leak away from a long, quiet compromise. Rotate credentials regularly and automatically. The pattern that makes rotation painless is &lt;strong&gt;two active versions at once&lt;/strong&gt;: issue the new secret, let both work during an overlap window, deploy, then retire the old one. No big-bang cutover, no downtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prefer short-lived credentials over long-lived keys
&lt;/h2&gt;

&lt;p&gt;The best secret is one that expires on its own. Where you can, replace static API keys with short-lived tokens: cloud IAM roles, OIDC workload identity, and database credentials minted on demand with a TTL. A token that's valid for fifteen minutes is far less valuable to steal than a key that's valid forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scan for leaks before they ship
&lt;/h2&gt;

&lt;p&gt;Add automated secret detection so a hardcoded key is caught in review, not in production. Run a scanner as a pre-commit hook &lt;em&gt;and&lt;/em&gt; in CI.&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;# pre-commit / CI&lt;/span&gt;
gitleaks detect &lt;span class="nt"&gt;--no-banner&lt;/span&gt;
&lt;span class="c"&gt;# or: trufflehog, detect-secrets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pre-commit catches it on the developer's machine; CI catches what slipped past. Use both — defense in depth costs almost nothing here.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a secret leaks: revoke first, investigate second
&lt;/h2&gt;

&lt;p&gt;Speed beats elegance. The moment you suspect exposure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Revoke/rotate the secret immediately.&lt;/strong&gt; A removed commit is still public; assume it's compromised.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue a replacement&lt;/strong&gt; through your secret manager.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the audit logs&lt;/strong&gt; for use you didn't expect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then&lt;/strong&gt; do the post-mortem — how it leaked, and which guardrail would have caught it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The instinct to "quietly remove the commit and hope" is exactly wrong. A leaked key is compromised; treat it that way.&lt;/p&gt;

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

&lt;p&gt;Secrets out of code and git? ✅ Real secret manager in production? ✅ Rotation automated with an overlap window? ✅ Short-lived credentials where possible? ✅ Leak scanning in pre-commit and CI? ✅ A revoke-first incident plan? ✅ Six yeses and the "key in a screenshot" disaster mostly can't happen to you.&lt;/p&gt;

&lt;p&gt;If you'd rather adopt this as a ready-made setup than assemble it piece by piece, the &lt;a href="https://datanest-stores.pages.dev/security-engineer/preview-secrets-management-guide.html" rel="noopener noreferrer"&gt;Secrets Management Guide&lt;/a&gt; packages the patterns, config templates, rotation runbooks, and leak-scanning hooks so your team standardizes on the safe path.&lt;/p&gt;

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

&lt;p&gt;You can't prevent every mistake, but you can make the safe way the easy way and the unsafe way loud. Keep secrets out of the repo, manage them centrally, rotate them automatically, and rehearse revocation — and a leaked credential becomes a non-event instead of a breach.&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
    <item>
      <title>pytest Patterns That Make Tests Actually Maintainable (2026)</title>
      <dc:creator>DatanestDigital</dc:creator>
      <pubDate>Sat, 20 Jun 2026 11:29:36 +0000</pubDate>
      <link>https://dev.to/datanestdigital/pytest-patterns-that-make-tests-actually-maintainable-2026-5heo</link>
      <guid>https://dev.to/datanestdigital/pytest-patterns-that-make-tests-actually-maintainable-2026-5heo</guid>
      <description>&lt;p&gt;Most Python test suites don't fail because the team can't write tests. They fail because the tests become a maintenance burden: copy-pasted setup, brittle mocks, and a suite so slow nobody runs it locally. pytest gives you the tools to avoid all of that — if you use them deliberately.&lt;/p&gt;

&lt;p&gt;Here are the patterns that keep a suite fast, readable, and cheap to change as the code evolves.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Fixtures over setup/teardown
&lt;/h2&gt;

&lt;p&gt;Fixtures are pytest's superpower: reusable, composable setup that's requested by name. A test declares what it needs as an argument and pytest builds it.&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;pytest&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;db_session&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;            &lt;span class="c1"&gt;# test runs here
&lt;/span&gt;    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;       &lt;span class="c1"&gt;# teardown after yield
&lt;/span&gt;    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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;test_create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_session&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_session&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;Ada&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything after &lt;code&gt;yield&lt;/code&gt; is teardown — guaranteed to run even if the test fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Scope fixtures to control cost
&lt;/h2&gt;

&lt;p&gt;A fixture that spins up a database shouldn't rebuild it for every test. Set &lt;code&gt;scope&lt;/code&gt; so expensive setup is shared.&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="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session&lt;/span&gt;&lt;span class="sh"&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;engine&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;eng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql://...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;eng&lt;/span&gt;
    &lt;span class="n"&gt;eng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&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;function&lt;/code&gt; (default), &lt;code&gt;class&lt;/code&gt;, &lt;code&gt;module&lt;/code&gt;, &lt;code&gt;session&lt;/code&gt; — widen the scope for expensive resources, keep it narrow for anything that holds mutable state.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Parametrize instead of copy-pasting tests
&lt;/h2&gt;

&lt;p&gt;When the only thing changing is the inputs and expected output, &lt;code&gt;parametrize&lt;/code&gt; turns ten near-identical tests into one.&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="nd"&gt;@pytest.mark.parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount, expected&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="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.00&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.00&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="mi"&gt;99999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;999.99&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="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-0.50&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_format_cents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;format_cents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each row reports as its own test, so a failure points straight at the offending case.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Factory fixtures for flexible test data
&lt;/h2&gt;

&lt;p&gt;When tests need objects with slight variations, return a &lt;em&gt;function&lt;/em&gt; from the fixture instead of a fixed object.&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="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_session&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;_make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;data&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;name&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;Test&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;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;viewer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;data&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;_make&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_admin_can_delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;make_user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;admin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;can_delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more ten fixtures for ten slightly different users.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Mock at the boundary, not everywhere
&lt;/h2&gt;

&lt;p&gt;Mock the things you don't control — network, time, third-party APIs — and let your own code run for real. Over-mocking produces tests that pass while the app is broken.&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;test_sends_welcome_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;make_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mocker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;send&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mocker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;myapp.email.send&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# patch where it's USED
&lt;/span&gt;    &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;make_user&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert_called_once&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The classic gotcha: patch the name in the module that &lt;em&gt;uses&lt;/em&gt; it, not where it's defined.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Markers to slice the suite
&lt;/h2&gt;

&lt;p&gt;Tag tests so you can run a fast subset locally and the full suite in CI.&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="nd"&gt;@pytest.mark.slow&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_full_import_pipeline&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"not slow"&lt;/span&gt;     &lt;span class="c"&gt;# quick feedback loop&lt;/span&gt;
pytest                   &lt;span class="c"&gt;# everything, in CI&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register markers in &lt;code&gt;pyproject.toml&lt;/code&gt; so a typo doesn't silently skip tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Put shared fixtures in &lt;code&gt;conftest.py&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Fixtures in a &lt;code&gt;conftest.py&lt;/code&gt; are auto-discovered by every test in that directory and below — no imports. Keep global fixtures at the root &lt;code&gt;conftest.py&lt;/code&gt;, and package-specific ones in nested ones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tests/
├── conftest.py          # engine, app, client
├── api/
│   ├── conftest.py      # authed_client
│   └── test_users.py
└── unit/
    └── test_format.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  8. Assert on behavior, not implementation
&lt;/h2&gt;

&lt;p&gt;Test what the function &lt;em&gt;does&lt;/em&gt;, not how. &lt;code&gt;assert result == expected&lt;/code&gt; survives refactors; asserting that an internal helper was called three times breaks the moment you tidy the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payoff
&lt;/h2&gt;

&lt;p&gt;Fixtures kill setup duplication, scopes keep the suite fast, parametrize collapses repetition, and factory fixtures make varied data trivial. Together they produce the property that matters most: a suite people actually run, that fails for real reasons, and that doesn't fight you when the code changes.&lt;/p&gt;

&lt;p&gt;Standing this up cleanly — &lt;code&gt;conftest&lt;/code&gt; layout, database and HTTP-client fixtures, factories, coverage config — is the same scaffolding every project needs. The &lt;a href="https://datanest-stores.pages.dev/python-developer/preview-python-testing-toolkit.html" rel="noopener noreferrer"&gt;Python Testing Toolkit&lt;/a&gt; bundles these patterns as ready-to-use fixtures and config so a new repo has a maintainable suite from the first commit.&lt;/p&gt;

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

&lt;p&gt;Maintainable tests aren't about discipline alone — they're about using pytest's composition tools so the easy path is also the clean one. Lean on fixtures, parametrize, and good &lt;code&gt;conftest&lt;/code&gt; structure, and your test suite becomes an asset instead of a chore.&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>pytest</category>
      <category>programming</category>
    </item>
    <item>
      <title>Kubernetes for Developers: The 8 Objects You Actually Need to Know (2026)</title>
      <dc:creator>DatanestDigital</dc:creator>
      <pubDate>Sat, 20 Jun 2026 11:29:00 +0000</pubDate>
      <link>https://dev.to/datanestdigital/kubernetes-for-developers-the-8-objects-you-actually-need-to-know-2026-47h5</link>
      <guid>https://dev.to/datanestdigital/kubernetes-for-developers-the-8-objects-you-actually-need-to-know-2026-47h5</guid>
      <description>&lt;p&gt;Kubernetes has hundreds of object types, and the documentation introduces them like you're about to operate the cluster. As an application developer, you don't need most of them. You need maybe eight, and once they click, deploying to Kubernetes stops feeling like sorcery.&lt;/p&gt;

&lt;p&gt;Here are the eight objects that cover almost everything you'll touch, in the order they build on each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Pod — the unit that actually runs
&lt;/h2&gt;

&lt;p&gt;A Pod is one or more containers that share a network and lifecycle. It's the smallest thing Kubernetes schedules. You'll rarely create Pods directly — but everything else exists to manage them, so it's the mental foundation.&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;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;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&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;web&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp:1.4.0&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt; &lt;span class="nv"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8080&lt;/span&gt; &lt;span class="pi"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key fact: Pods are &lt;strong&gt;cattle, not pets&lt;/strong&gt;. They get killed, rescheduled, and replaced. Never assume one stays alive or keeps its IP.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Deployment — how you actually run Pods
&lt;/h2&gt;

&lt;p&gt;A Deployment manages a set of identical Pods: it keeps N replicas running, replaces crashed ones, and rolls out new versions without downtime. This is what you write 90% of the time.&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;apps/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;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&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;web&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp:1.4.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the image, &lt;code&gt;kubectl apply&lt;/code&gt;, and Kubernetes rolls Pods one batch at a time. Bad release? &lt;code&gt;kubectl rollout undo&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Service — a stable address for unstable Pods
&lt;/h2&gt;

&lt;p&gt;Pods come and go, so you never talk to them directly. A Service gives a fixed DNS name and load-balances across whichever Pods currently match its selector.&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;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;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;80&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8080&lt;/span&gt; &lt;span class="pi"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now other Pods reach it at &lt;code&gt;http://web&lt;/code&gt;. The Service tracks the Pods for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Ingress — getting traffic in from the outside
&lt;/h2&gt;

&lt;p&gt;A Service is internal by default. An Ingress exposes HTTP routes to the world and handles host/path routing and TLS, so one load balancer can serve many services.&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;networking.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;Ingress&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&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;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app.example.com&lt;/span&gt;
      &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
            &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
            &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;80&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. ConfigMap — configuration that isn't secret
&lt;/h2&gt;

&lt;p&gt;Keep config out of your image so the same image runs in dev, staging, and prod. A ConfigMap holds non-sensitive key/values you inject as env vars or files.&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;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;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;web-config&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;info"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;FEATURE_X&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="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Secret — configuration that &lt;em&gt;is&lt;/em&gt; sensitive
&lt;/h2&gt;

&lt;p&gt;Same idea, for credentials and tokens. Reference them as env vars or mounted files and keep them out of your image and your git history. (Enable encryption-at-rest and RBAC — a base64 Secret isn't encrypted by default.)&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;env&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;DB_PASSWORD&lt;/span&gt;
    &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;password&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Liveness &amp;amp; readiness probes — let the cluster heal you
&lt;/h2&gt;

&lt;p&gt;These aren't separate objects, but they live in your Deployment and they're the difference between "self-healing" and "silently broken." &lt;strong&gt;Readiness&lt;/strong&gt; decides if a Pod should receive traffic; &lt;strong&gt;liveness&lt;/strong&gt; decides if it should be restarted.&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;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;/healthz&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8080&lt;/span&gt; &lt;span class="pi"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;5&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;/healthz&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8080&lt;/span&gt; &lt;span class="pi"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;10&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Skip these and Kubernetes will happily route traffic to a Pod that's still booting or wedged.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Resource requests &amp;amp; limits — be a good neighbor
&lt;/h2&gt;

&lt;p&gt;Tell the scheduler what each Pod needs (&lt;code&gt;requests&lt;/code&gt;) and the ceiling it can't exceed (&lt;code&gt;limits&lt;/code&gt;). Without these, one runaway Pod can starve the node.&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;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;128Mi"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The mental model
&lt;/h2&gt;

&lt;p&gt;Read top to bottom and it's a pipeline: a &lt;strong&gt;Deployment&lt;/strong&gt; keeps &lt;strong&gt;Pods&lt;/strong&gt; running, a &lt;strong&gt;Service&lt;/strong&gt; gives them a stable address, an &lt;strong&gt;Ingress&lt;/strong&gt; lets the outside reach them, and &lt;strong&gt;ConfigMaps/Secrets&lt;/strong&gt; feed them configuration — while &lt;strong&gt;probes&lt;/strong&gt; and &lt;strong&gt;resource limits&lt;/strong&gt; keep the whole thing healthy. Master these eight and you can deploy and debug the vast majority of real workloads without ever touching the cluster internals.&lt;/p&gt;

&lt;p&gt;The remaining friction is writing all that YAML correctly and consistently. If you'd rather start from working, commented manifests than a blank file, the &lt;a href="https://datanest-stores.pages.dev/devops-toolkit/preview-kubernetes-manifests-pack.html" rel="noopener noreferrer"&gt;Kubernetes Manifests Pack&lt;/a&gt; ships production-ready templates for exactly these objects — Deployment, Service, Ingress, probes, and resource policies — so your first deploy is a fill-in-the-blanks job.&lt;/p&gt;

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

&lt;p&gt;Kubernetes is wide but shallow for developers: a small set of objects does the heavy lifting for application workloads. Learn these eight, wire up probes and limits, and the platform becomes a tool you use instead of a system you fear.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>docker</category>
      <category>backend</category>
    </item>
    <item>
      <title>REST API Design: 12 Rules for Endpoints Developers Won't Hate (2026)</title>
      <dc:creator>DatanestDigital</dc:creator>
      <pubDate>Sat, 20 Jun 2026 11:28:24 +0000</pubDate>
      <link>https://dev.to/datanestdigital/rest-api-design-12-rules-for-endpoints-developers-wont-hate-2026-141b</link>
      <guid>https://dev.to/datanestdigital/rest-api-design-12-rules-for-endpoints-developers-wont-hate-2026-141b</guid>
      <description>&lt;p&gt;A good REST API feels obvious. You guess the URL and you're right; you read one error and you know exactly what to fix. A bad one makes you keep the docs open in a second monitor and reverse-engineer behavior from 200-responses that secretly contain failures.&lt;/p&gt;

&lt;p&gt;The difference is rarely the framework. It's a handful of conventions applied consistently. Here are twelve rules that, followed together, produce an API integrators don't complain about.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Name resources with plural nouns, not verbs
&lt;/h2&gt;

&lt;p&gt;The HTTP method is the verb. The path is the thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET    /users           # list
POST   /users           # create
GET    /users/42        # read
PATCH  /users/42        # partial update
DELETE /users/42        # delete
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not &lt;code&gt;/getUser&lt;/code&gt;, &lt;code&gt;/createUser&lt;/code&gt;, &lt;code&gt;/user/delete&lt;/code&gt;. The method already says what you're doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Nest only to show ownership — then stop
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;GET /users/42/orders&lt;/code&gt; is fine: orders belong to a user. But don't go three levels deep. Past one level of nesting, switch to query parameters: &lt;code&gt;GET /orders?user=42&amp;amp;status=open&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use HTTP status codes honestly
&lt;/h2&gt;

&lt;p&gt;Return the code that's true, not &lt;code&gt;200&lt;/code&gt; with &lt;code&gt;{"success": false}&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;200&lt;/code&gt; OK, &lt;code&gt;201&lt;/code&gt; Created, &lt;code&gt;204&lt;/code&gt; No Content&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;400&lt;/code&gt; bad request, &lt;code&gt;401&lt;/code&gt; unauthenticated, &lt;code&gt;403&lt;/code&gt; unauthorized, &lt;code&gt;404&lt;/code&gt; not found, &lt;code&gt;409&lt;/code&gt; conflict, &lt;code&gt;422&lt;/code&gt; validation failed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;429&lt;/code&gt; rate limited, &lt;code&gt;500&lt;/code&gt; server error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A client should be able to branch on the status line alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Return a consistent error shape
&lt;/h2&gt;

&lt;p&gt;Every error, everywhere, in the same structure. Machine-readable &lt;code&gt;code&lt;/code&gt;, human &lt;code&gt;message&lt;/code&gt;, and field-level detail when relevant.&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;"error"&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="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"validation_failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Email is not valid."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"details"&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="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"issue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"format"&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;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;Pick this shape once and never deviate. Clients build error handling around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. &lt;code&gt;PATCH&lt;/code&gt; for partial, &lt;code&gt;PUT&lt;/code&gt; for full replacement
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;PATCH /users/42&lt;/code&gt; with &lt;code&gt;{ "email": "..." }&lt;/code&gt; changes one field. &lt;code&gt;PUT&lt;/code&gt; means "replace the whole resource with this representation." Don't make &lt;code&gt;PUT&lt;/code&gt; behave like &lt;code&gt;PATCH&lt;/code&gt;; you'll surprise everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Make writes idempotent where you can
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;PUT&lt;/code&gt; and &lt;code&gt;DELETE&lt;/code&gt; should be safe to retry. For &lt;code&gt;POST&lt;/code&gt; (create), support an &lt;code&gt;Idempotency-Key&lt;/code&gt; header so a client that retries after a network blip doesn't create two orders.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Paginate from day one
&lt;/h2&gt;

&lt;p&gt;A collection that returns "all rows" works in dev and falls over in production. Decide on a strategy early. Cursor pagination scales better than offset for large, changing datasets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /users?limit=50&amp;amp;cursor=eyJpZCI6MTAwfQ
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"data"&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="err"&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="nl"&gt;"next_cursor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJpZCI6MTUwfQ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"has_more"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;h2&gt;
  
  
  8. Filter, sort, and select with query params
&lt;/h2&gt;

&lt;p&gt;Keep the path about identity; put the query in the query string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /orders?status=open&amp;amp;sort=-created_at&amp;amp;fields=id,total
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-created_at&lt;/code&gt; for descending is a tiny convention that saves a lot of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Version at the edge, before you need to
&lt;/h2&gt;

&lt;p&gt;Put a version in the URL (&lt;code&gt;/v1/...&lt;/code&gt;) or a header from the very first release. The day you must make a breaking change, &lt;code&gt;/v2&lt;/code&gt; lets existing clients keep working instead of breaking in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Be consistent about field casing and dates
&lt;/h2&gt;

&lt;p&gt;Pick &lt;code&gt;snake_case&lt;/code&gt; or &lt;code&gt;camelCase&lt;/code&gt; and apply it to every field in every response. Use ISO 8601 UTC for timestamps (&lt;code&gt;2026-06-20T11:00:00Z&lt;/code&gt;). Mixed casing and ambiguous local dates are a thousand tiny paper cuts.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Don't leak your database
&lt;/h2&gt;

&lt;p&gt;Your API is a contract, not a SQL dump. Don't expose internal auto-increment IDs if they reveal volume, don't return columns clients shouldn't see, and don't let the response shape change because you refactored a table. The representation is yours to design.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Document with real examples, and keep them true
&lt;/h2&gt;

&lt;p&gt;The best docs are copy-pasteable requests and their exact responses. Generate an OpenAPI spec from the code so the docs can't drift from behavior — a lying example is worse than none.&lt;/p&gt;

&lt;h2&gt;
  
  
  The through-line
&lt;/h2&gt;

&lt;p&gt;Every rule above is really one rule: &lt;strong&gt;be predictable.&lt;/strong&gt; When naming, status codes, errors, pagination, and dates are uniform across every endpoint, a developer who learns one part of your API has effectively learned all of it.&lt;/p&gt;

&lt;p&gt;If you want these conventions as a ready-made starting point — endpoint templates, the standard error envelope, pagination helpers, and an OpenAPI skeleton — the &lt;a href="https://datanest-stores.pages.dev/api-developer/preview-rest-api-design-guide.html" rel="noopener noreferrer"&gt;REST API Design Guide&lt;/a&gt; packages them so a new service is consistent on day one instead of after the third refactor.&lt;/p&gt;

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

&lt;p&gt;APIs are read far more often than they're written. Spend the design effort up front on consistency, and every future integrator — including future you — inherits an interface they can guess their way through.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>10 TypeScript Utility Types That Will Clean Up Your Codebase (2026)</title>
      <dc:creator>DatanestDigital</dc:creator>
      <pubDate>Sat, 20 Jun 2026 11:27:48 +0000</pubDate>
      <link>https://dev.to/datanestdigital/10-typescript-utility-types-that-will-clean-up-your-codebase-2026-2em2</link>
      <guid>https://dev.to/datanestdigital/10-typescript-utility-types-that-will-clean-up-your-codebase-2026-2em2</guid>
      <description>&lt;p&gt;Most TypeScript codebases are full of types that are copies of other types with one field changed. A &lt;code&gt;User&lt;/code&gt;, then a &lt;code&gt;UserUpdate&lt;/code&gt; that's the same minus &lt;code&gt;id&lt;/code&gt;, then a &lt;code&gt;UserResponse&lt;/code&gt; that's the same plus a timestamp. Maintaining those by hand is how types drift out of sync with reality.&lt;/p&gt;

&lt;p&gt;TypeScript ships a set of built-in &lt;strong&gt;utility types&lt;/strong&gt; that derive new types from existing ones, so a change in one place propagates everywhere. Here are the ten I reach for constantly, with the real situations they solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;Partial&amp;lt;T&amp;gt;&lt;/code&gt; — everything optional
&lt;/h2&gt;

&lt;p&gt;Perfect for update payloads and patch functions where the caller supplies only the fields they're changing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;updateUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;   &lt;span class="c1"&gt;// ✅ no need to pass name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. &lt;code&gt;Required&amp;lt;T&amp;gt;&lt;/code&gt; — everything mandatory
&lt;/h2&gt;

&lt;p&gt;The inverse. Useful when you receive a loosely-typed config and want to assert that defaults have filled every hole.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Options&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Required&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Options&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* every field guaranteed */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. &lt;code&gt;Readonly&amp;lt;T&amp;gt;&lt;/code&gt; — freeze the shape at the type level
&lt;/h2&gt;

&lt;p&gt;Signals intent and catches accidental mutation at compile time — great for state objects and function parameters you don't own.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Readonly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// ❌ Cannot assign to 'name', it is read-only&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. &lt;code&gt;Pick&amp;lt;T, K&amp;gt;&lt;/code&gt; — keep only the keys you need
&lt;/h2&gt;

&lt;p&gt;Stop hand-copying a subset of fields. &lt;code&gt;Pick&lt;/code&gt; derives it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserPreview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Pick&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// { id: string; name: string }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. &lt;code&gt;Omit&amp;lt;T, K&amp;gt;&lt;/code&gt; — everything except these keys
&lt;/h2&gt;

&lt;p&gt;The one I use most. "A &lt;code&gt;User&lt;/code&gt; but without the server-generated &lt;code&gt;id&lt;/code&gt;" becomes a one-liner that updates itself when &lt;code&gt;User&lt;/code&gt; changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NewUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Omit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// { name: string; email: string }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. &lt;code&gt;Record&amp;lt;K, V&amp;gt;&lt;/code&gt; — typed dictionaries
&lt;/h2&gt;

&lt;p&gt;Replaces the vague &lt;code&gt;{ [key: string]: V }&lt;/code&gt; with something precise, and can constrain the keys to a union.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;viewer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;write&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;delete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;write&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;viewer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;   &lt;span class="c1"&gt;// miss a role and the compiler tells you&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. &lt;code&gt;ReturnType&amp;lt;T&amp;gt;&lt;/code&gt; — infer what a function returns
&lt;/h2&gt;

&lt;p&gt;Invaluable when a function's return type is complex or inferred, and you want a variable typed to match without restating it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createStore&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;count&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="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;createStore&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  8. &lt;code&gt;Parameters&amp;lt;T&amp;gt;&lt;/code&gt; — capture an argument list as a tuple
&lt;/h2&gt;

&lt;p&gt;Great for wrappers, decorators, and higher-order functions that must forward arguments faithfully.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;LogArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;updateUser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// [string, Partial&amp;lt;User&amp;gt;]&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;withLogging&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;updateUser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;calling updateUser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;updateUser&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&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;h2&gt;
  
  
  9. &lt;code&gt;NonNullable&amp;lt;T&amp;gt;&lt;/code&gt; — strip &lt;code&gt;null&lt;/code&gt; and &lt;code&gt;undefined&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;After a guard, narrow a type so the rest of the function doesn't have to keep checking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MaybeUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;DefiniteUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NonNullable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MaybeUser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// User&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  10. &lt;code&gt;Exclude&amp;lt;T, U&amp;gt;&lt;/code&gt; and &lt;code&gt;Extract&amp;lt;T, U&amp;gt;&lt;/code&gt; — filter unions
&lt;/h2&gt;

&lt;p&gt;Filter members out of (or into) a union type — the set operations of the type system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;archived&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deleted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Exclude&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deleted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// "active" | "archived"&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Gone&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Extract&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deleted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// "deleted"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Compose them for real power
&lt;/h2&gt;

&lt;p&gt;The utilities shine when combined. "A draft is a &lt;code&gt;User&lt;/code&gt; without its &lt;code&gt;id&lt;/code&gt;, with everything optional" is one expression that tracks &lt;code&gt;User&lt;/code&gt; forever:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserDraft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Omit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// A response type derived from the model, plus a server field:&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Readonly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your derived types are &lt;em&gt;computed&lt;/em&gt; from a single source of truth, an edit to &lt;code&gt;User&lt;/code&gt; ripples through &lt;code&gt;UserDraft&lt;/code&gt;, &lt;code&gt;UserPreview&lt;/code&gt;, and &lt;code&gt;UserResponse&lt;/code&gt; automatically — and the compiler flags every place that needs attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  A working reference beats memorizing
&lt;/h2&gt;

&lt;p&gt;These ten cover the overwhelming majority of day-to-day needs, but the combinations (mapped types, conditional types, template literal types) are where teams reinvent the same helpers. If you'd rather pull from a vetted set, the &lt;a href="https://datanest-stores.pages.dev/frontend-developer/preview-typescript-utility-library.html" rel="noopener noreferrer"&gt;TypeScript Utility Library&lt;/a&gt; collects production-ready type helpers and patterns — deep-partial, strict-omit, branded types, and more — so you stop rewriting them per project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;The fastest way to a cleaner TypeScript codebase isn't more types — it's fewer &lt;em&gt;hand-written&lt;/em&gt; ones. Derive types from a single source with &lt;code&gt;Pick&lt;/code&gt;, &lt;code&gt;Omit&lt;/code&gt;, &lt;code&gt;Partial&lt;/code&gt;, and friends, and let the compiler keep them honest as your models evolve.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Hardening Docker Containers: A Practical 2026 Security Checklist</title>
      <dc:creator>DatanestDigital</dc:creator>
      <pubDate>Sat, 20 Jun 2026 11:18:24 +0000</pubDate>
      <link>https://dev.to/datanestdigital/hardening-docker-containers-a-practical-2026-security-checklist-jop</link>
      <guid>https://dev.to/datanestdigital/hardening-docker-containers-a-practical-2026-security-checklist-jop</guid>
      <description>&lt;p&gt;A container is not a security boundary by default. Out of the box, the typical image runs as root, ships a full distro's worth of attack surface, can write anywhere, and keeps every Linux capability it was born with. None of that is required to run your app — it's just the path of least resistance, and attackers love it.&lt;/p&gt;

&lt;p&gt;This is the checklist I run before anything goes to production. Every item is concrete, most are one or two lines, and together they turn a default container into a genuinely hard target. Copy the snippets, adapt, ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Don't run as root
&lt;/h2&gt;

&lt;p&gt;If your process is root inside the container and an attacker escapes the process, they're root in the container — and root in the container is one misconfiguration away from root on the host. Create and switch to an unprivileged user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--ingroup&lt;/span&gt; app app
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At runtime, enforce it so a bad image can't override the intent:&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="c1"&gt;# docker-compose / k8s securityContext&lt;/span&gt;
&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10001:10001"&lt;/span&gt;
&lt;span class="na"&gt;read_only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;cap_drop&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;ALL"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;security_opt&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;no-new-privileges:true"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;no-new-privileges&lt;/code&gt; blocks setuid binaries from escalating — a cheap, high-value flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Start from the smallest base you can
&lt;/h2&gt;

&lt;p&gt;Every package in your base image is attack surface and another CVE to patch. &lt;code&gt;python:3.12&lt;/code&gt; is ~1 GB; &lt;code&gt;python:3.12-slim&lt;/code&gt; is a fraction of that; distroless or Alpine smaller still. Fewer files means fewer vulnerabilities and a smaller blast radius.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# from this...&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12&lt;/span&gt;
&lt;span class="c"&gt;# ...to this&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="c"&gt;# ...or, for compiled/static apps, distroless with no shell at all&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; gcr.io/distroless/static-debian12&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A distroless image has no shell and no package manager, so even after a breakout the attacker has almost nothing to work with.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use multi-stage builds to leave the toolchain behind
&lt;/h2&gt;

&lt;p&gt;Build tools, compilers, and dev dependencies should never reach production. Build in one stage, copy only the artifact into a clean final stage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/install &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /install /usr/local&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /app&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final image carries your app and its runtime deps — and nothing you only needed to build it.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Make the filesystem read-only
&lt;/h2&gt;

&lt;p&gt;A process that can't write can't drop a webshell, tamper with binaries, or persist. Run the root filesystem read-only and mount explicit, narrow &lt;code&gt;tmpfs&lt;/code&gt; volumes for the few paths that genuinely need writes.&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;read_only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;tmpfs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/tmp&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your app refuses to start read-only, that's a useful finding: it's writing somewhere it shouldn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Drop every capability, then add back only what you need
&lt;/h2&gt;

&lt;p&gt;Containers inherit a default set of Linux capabilities most apps never use. Drop them all and re-add the rare exceptions explicitly (a web app almost never needs any).&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;cap_drop&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;ALL"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# cap_add: ["NET_BIND_SERVICE"]   # only if you must bind to a port &amp;lt; 1024&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better: don't bind to a privileged port at all — listen on 8080 and let the load balancer map 443.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Keep secrets out of the image and the environment
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ENV API_KEY=...&lt;/code&gt; and &lt;code&gt;COPY .env .&lt;/code&gt; bake secrets into image layers forever — anyone who pulls the image can &lt;code&gt;docker history&lt;/code&gt; them out. Inject secrets at runtime via your orchestrator's secret store or mounted files, and add &lt;code&gt;.env&lt;/code&gt;, keys, and &lt;code&gt;.git&lt;/code&gt; to &lt;code&gt;.dockerignore&lt;/code&gt; so they never enter the build context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# .dockerignore
&lt;/span&gt;.&lt;span class="n"&gt;env&lt;/span&gt;
*.&lt;span class="n"&gt;pem&lt;/span&gt;
.&lt;span class="n"&gt;git&lt;/span&gt;
**/&lt;span class="n"&gt;secrets&lt;/span&gt;*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Environment variables leak through crash dumps, child processes, and &lt;code&gt;/proc&lt;/code&gt;; prefer mounted secret files with tight permissions where you can.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Pin versions and scan every image
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;FROM node:latest&lt;/code&gt; means your build is non-reproducible and silently pulls tomorrow's unpatched surprise. Pin to a digest, and run a scanner in CI that &lt;strong&gt;fails the build&lt;/strong&gt; on high/critical CVEs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim@sha256:&amp;lt;digest&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# in CI — fail on serious findings&lt;/span&gt;
trivy image &lt;span class="nt"&gt;--severity&lt;/span&gt; HIGH,CRITICAL &lt;span class="nt"&gt;--exit-code&lt;/span&gt; 1 myapp:sha-1234
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scanning once is theater; scanning on every build is control. Rebuild regularly so base-image patches actually reach production.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Add a HEALTHCHECK and handle signals
&lt;/h2&gt;

&lt;p&gt;A container that can't report health hides failures; a process that ignores SIGTERM gets SIGKILLed on every deploy, dropping in-flight work. Both are reliability &lt;em&gt;and&lt;/em&gt; security concerns — zombie or wedged containers are where bad things hide.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=3s \&lt;/span&gt;
  CMD curl -fsS http://localhost:8080/healthz || exit 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure your app traps SIGTERM and shuts down gracefully (or run it under an init like &lt;code&gt;tini&lt;/code&gt; to reap zombies).&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Constrain resources
&lt;/h2&gt;

&lt;p&gt;Set memory and CPU limits so a single compromised or buggy container can't starve its neighbors or the host — a trivial denial-of-service otherwise.&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;deploy&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="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;
      &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.5"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The quick audit
&lt;/h2&gt;

&lt;p&gt;Before you ship, ask: Is it &lt;strong&gt;non-root&lt;/strong&gt;? Is the base &lt;strong&gt;minimal and pinned&lt;/strong&gt;? Is the filesystem &lt;strong&gt;read-only&lt;/strong&gt;? Are capabilities &lt;strong&gt;dropped&lt;/strong&gt;? Are secrets &lt;strong&gt;out of the image&lt;/strong&gt;? Does CI &lt;strong&gt;scan and fail&lt;/strong&gt; on critical CVEs? Six yeses and you've eliminated the overwhelming majority of real-world container attacks.&lt;/p&gt;

&lt;p&gt;If you want this enforced as ready-to-use templates rather than a checklist you re-implement per project, the &lt;a href="https://datanest-stores.pages.dev/devops-toolkit/preview-container-security-toolkit.html" rel="noopener noreferrer"&gt;Container Security Toolkit&lt;/a&gt; ships hardened Dockerfiles, secure &lt;code&gt;securityContext&lt;/code&gt; presets, and a CI scanning pipeline you can drop into an existing repo.&lt;/p&gt;

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

&lt;p&gt;Container hardening isn't one big effort — it's a stack of small, boring defaults that each remove an attacker's option. Non-root, minimal, read-only, capability-dropped, secrets-externalized, and continuously scanned. Set them once, bake them into your base templates, and every service you ship inherits the security instead of relitigating it.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>security</category>
      <category>devops</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>9 Battle-Tested Tactics to Cut Your LLM API Bill (2026)</title>
      <dc:creator>DatanestDigital</dc:creator>
      <pubDate>Sat, 20 Jun 2026 11:17:20 +0000</pubDate>
      <link>https://dev.to/datanestdigital/9-battle-tested-tactics-to-cut-your-llm-api-bill-2026-4799</link>
      <guid>https://dev.to/datanestdigital/9-battle-tested-tactics-to-cut-your-llm-api-bill-2026-4799</guid>
      <description>&lt;p&gt;The demo was cheap. Then you shipped, traffic grew, and the monthly model bill quietly became one of your largest infrastructure line items. LLM spend scales linearly with usage, and most teams leave 50–90% of it on the table because the easy wins are invisible until you go looking.&lt;/p&gt;

&lt;p&gt;Here are nine tactics that actually move the number, ordered roughly from highest to lowest leverage. None of them require switching providers, and most are a few hours of work.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Stop paying twice for the same answer (exact-match caching)
&lt;/h2&gt;

&lt;p&gt;A surprising share of production traffic is duplicate prompts: the same FAQ, the same summarization of the same document, the same system-prompted classification. Hash the full request (model + messages + params) and cache the response.&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;hashlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Redis&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;cached_completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&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="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;msgs&lt;/span&gt;&lt;span class="sh"&gt;"&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="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&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="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&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="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# 24h TTL
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For deterministic calls (&lt;code&gt;temperature=0&lt;/code&gt;) this is free money. Cache hit = zero tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Catch near-duplicates too (semantic caching)
&lt;/h2&gt;

&lt;p&gt;Exact-match misses "What's your refund policy?" vs "How do refunds work?". Embed the query, search a vector store, and if the nearest cached question is above a similarity threshold (~0.95), return its answer. Embeddings cost a fraction of a completion, so the math works strongly in your favor at scale. Tune the threshold carefully — too loose and you'll serve wrong answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Route by difficulty (model cascades)
&lt;/h2&gt;

&lt;p&gt;You do not need your most expensive model for "is this sentiment positive or negative?". Send everything to a small/cheap model first; escalate to the frontier model only when the cheap one signals low confidence or the task is genuinely hard.&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;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;CHEAP_TASKS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                       &lt;span class="c1"&gt;# classification, extraction, routing
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;small-cheap-model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;frontier-model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# reasoning, long-form, code
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A well-tuned cascade routinely cuts blended cost-per-request by 60–80% because the long tail of simple requests stops hitting the premium tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Compress the prompt, not the quality
&lt;/h2&gt;

&lt;p&gt;You pay for every input token, and most prompts are bloated. Three high-ROI trims:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shrink the system prompt.&lt;/strong&gt; A 1,500-token system prompt sent on every request is a tax on every call. Move static instructions into a fine-tune or a shorter canonical version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prune RAG context.&lt;/strong&gt; Retrieving 20 chunks "to be safe" when 4 answer the question multiplies input cost. Re-rank and keep the top few.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summarize history.&lt;/strong&gt; In long chats, replace old turns with a running summary instead of resending the entire transcript every time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Cap and control output tokens
&lt;/h2&gt;

&lt;p&gt;Output tokens usually cost more than input tokens, and an unbounded &lt;code&gt;max_tokens&lt;/code&gt; invites rambling. Set a sensible ceiling, and ask for structured/terse output when you don't need prose:&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="nf"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&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="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;# bound the worst case
&lt;/span&gt;           &lt;span class="n"&gt;response_format&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;type&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;json_object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;  &lt;span class="c1"&gt;# no filler, easy to parse
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Answer in one sentence" or "return only JSON" is a real cost lever, not just a UX choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Batch when latency allows
&lt;/h2&gt;

&lt;p&gt;Many workloads — nightly enrichment, backfills, evals, bulk classification — don't need real-time responses. Most providers offer an asynchronous &lt;strong&gt;batch API at a steep discount (commonly ~50%)&lt;/strong&gt; for jobs you can wait hours on. Split your traffic: interactive requests go to the real-time endpoint, everything deferrable goes to the batch lane.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Use provider-side prompt caching
&lt;/h2&gt;

&lt;p&gt;Several providers now cache a static prompt prefix server-side and bill the cached portion at a large discount on subsequent calls. If you send the same long system prompt or document context repeatedly, order your messages so the stable part comes first and opt into prompt caching. This stacks with tactic #4.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Fine-tune to delete the prompt
&lt;/h2&gt;

&lt;p&gt;When a task is narrow and high-volume, a small fine-tuned model can match a big model's quality on that task — with a fraction of the prompt tokens, because the instructions and few-shot examples are baked into the weights. You trade a one-time training cost for a permanently smaller per-request bill. Run the break-even math: above some daily volume, fine-tuning a cheaper base model wins decisively.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Measure cost per request, or you're flying blind
&lt;/h2&gt;

&lt;p&gt;You can't cut what you don't see. Log tokens and dollar cost on &lt;strong&gt;every&lt;/strong&gt; call, tagged by feature, model, and user tier. The first time you do this you'll find one endpoint quietly burning a third of the budget.&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;log_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;PRICE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; \
           &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completion_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;PRICE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm.cost_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&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;feature:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;feature&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;model&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch cost-per-successful-request as your north-star metric — it normalizes for traffic and exposes regressions a raw total hides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put it together
&lt;/h2&gt;

&lt;p&gt;Stack these and the savings compound: caching removes duplicate work, routing moves the bulk of traffic to cheap models, prompt compression and output caps shrink what's left, batching discounts the deferrable tail, and observability keeps it all honest. Teams that apply the top four typically see their bill drop by more than half without any user-visible quality loss.&lt;/p&gt;

&lt;p&gt;If you'd rather not build the caching layer, router, and cost-tracking dashboard from scratch, the &lt;a href="https://datanest-stores.pages.dev/ai-llm-toolkit/preview-llm-cost-optimizer.html" rel="noopener noreferrer"&gt;LLM Cost Optimizer&lt;/a&gt; bundles these patterns — semantic cache, model-routing logic, token accounting, and ready-to-wire dashboards — so you can start saving this week instead of next quarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mindset shift
&lt;/h2&gt;

&lt;p&gt;LLM cost optimization isn't a one-time cleanup; it's a habit. Treat tokens like you treat database queries — something you profile, budget, and watch. The cheapest token is the one you never send, and the second cheapest is the one a small model handles.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>machinelearning</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
