<?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: Alok Ranjan Daftuar</title>
    <description>The latest articles on DEV Community by Alok Ranjan Daftuar (@aloknecessary).</description>
    <link>https://dev.to/aloknecessary</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%2F3791551%2F62fbfeb5-1fba-4e79-bc4b-780b7ce52748.jpg</url>
      <title>DEV Community: Alok Ranjan Daftuar</title>
      <link>https://dev.to/aloknecessary</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aloknecessary"/>
    <language>en</language>
    <item>
      <title>Enforcing Modular Monolith Boundaries in .NET: NDepend, Parallel Pipelines, and the Architecture That Holds</title>
      <dc:creator>Alok Ranjan Daftuar</dc:creator>
      <pubDate>Mon, 31 Aug 2026 06:03:32 +0000</pubDate>
      <link>https://dev.to/aloknecessary/enforcing-modular-monolith-boundaries-in-net-ndepend-parallel-pipelines-and-the-architecture-37e1</link>
      <guid>https://dev.to/aloknecessary/enforcing-modular-monolith-boundaries-in-net-ndepend-parallel-pipelines-and-the-architecture-37e1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A modular monolith without enforcement is not an architecture — it is a monolith with good intentions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Most teams skip the modular monolith and jump straight to microservices. The ones that do attempt a modular monolith rely on convention — "don't cross module boundaries" — which fails the moment deadlines hit.&lt;/p&gt;

&lt;p&gt;The difference between a well-structured modular monolith and a mess is whether boundaries are maintained by &lt;strong&gt;tooling&lt;/strong&gt; or by convention.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution Structure
&lt;/h2&gt;

&lt;p&gt;Each module is a pair of .NET projects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/Modules/
  Orders/
    YourApp.Orders/              ← internal: domain, application, infrastructure
    YourApp.Orders.Contracts/    ← public: DTOs, interfaces, events
  Payments/
    YourApp.Payments/
    YourApp.Payments.Contracts/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The rule&lt;/strong&gt;: modules may only reference each other's &lt;code&gt;*.Contracts&lt;/code&gt; projects. The compiler enforces this physically — no project reference means no type access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four Layers of Enforcement
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Compiler&lt;/strong&gt; — project references prevent cross-module type access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NetArchTest&lt;/strong&gt; — architecture tests fail the build on namespace-level violations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NDepend CQLinq&lt;/strong&gt; — catches dependency cycles and coupling the compiler can't see&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality Gates&lt;/strong&gt; — block PRs that introduce &lt;em&gt;new&lt;/em&gt; boundary violations&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Module-Scoped Data
&lt;/h2&gt;

&lt;p&gt;Each module owns a dedicated &lt;code&gt;DbContext&lt;/code&gt; with a schema prefix (&lt;code&gt;orders.*&lt;/code&gt;, &lt;code&gt;payments.*&lt;/code&gt;). No module queries another module's tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-Module Communication
&lt;/h2&gt;

&lt;p&gt;Modules communicate via MediatR in-process events. Orders publishes &lt;code&gt;OrderPlaced&lt;/code&gt;; Payments subscribes — without Orders knowing Payments exists.&lt;/p&gt;

&lt;p&gt;This is also the extraction seam: when you eventually extract a module into a service, MediatR becomes a message broker. The event contract stays the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parallel CI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Orders&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Payments&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Inventory&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each module's tests run in parallel. CI time scales with the slowest module, not the total count.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Extraction Path
&lt;/h2&gt;

&lt;p&gt;When a module genuinely needs independence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add outbox table → publish to real broker&lt;/li&gt;
&lt;li&gt;Replace MediatR handlers with broker consumers&lt;/li&gt;
&lt;li&gt;Deploy module as separate service&lt;/li&gt;
&lt;li&gt;Publish &lt;code&gt;*.Contracts&lt;/code&gt; as NuGet package&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The boundary was already clean. Extraction is a deployment change, not a redesign.&lt;/p&gt;




&lt;p&gt;The full post covers NDepend CQLinq rule examples, Quality Gate configuration, GitHub Actions pipeline YAML, test isolation patterns, and a production checklist.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://aloknecessary.in/blogs/modular-monolith-dotnet-ndepend/?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=modular-monolith-dotnet" rel="noopener noreferrer"&gt;Read the complete implementation guide&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>devops</category>
      <category>dotnet</category>
      <category>csharp</category>
    </item>
    <item>
      <title>AWS VPC Networking Fundamentals: VPCs, Subnets, CIDR, Route Tables, IGW, and NAT Gateways</title>
      <dc:creator>Alok Ranjan Daftuar</dc:creator>
      <pubDate>Fri, 28 Aug 2026 03:49:00 +0000</pubDate>
      <link>https://dev.to/aloknecessary/aws-vpc-networking-fundamentals-vpcs-subnets-cidr-route-tables-igw-and-nat-gateways-19h1</link>
      <guid>https://dev.to/aloknecessary/aws-vpc-networking-fundamentals-vpcs-subnets-cidr-route-tables-igw-and-nat-gateways-19h1</guid>
      <description>&lt;p&gt;If you've provisioned a VPC from a Terraform module without fully internalising what each piece is doing, that's fine — right up until something breaks. An instance that should be reachable isn't. A private instance can't pull a package update. And you're left checking five different resources with no clear mental model of how they connect.&lt;/p&gt;

&lt;p&gt;This post builds that mental model from the ground up. Not just definitions — the &lt;em&gt;why&lt;/em&gt; behind each piece, so troubleshooting becomes deduction instead of guesswork.&lt;/p&gt;




&lt;h2&gt;
  
  
  CIDR math you actually need
&lt;/h2&gt;

&lt;p&gt;A CIDR block is &lt;code&gt;IP address / prefix length&lt;/code&gt;. The prefix length fixes the network portion; the remaining bits are your host space.&lt;/p&gt;

&lt;p&gt;Formula: &lt;code&gt;2^(32 - prefix) = total addresses&lt;/code&gt;. AWS reserves 5 per subnet (network address, VPC router, DNS, reserved, broadcast).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CIDR&lt;/th&gt;
&lt;th&gt;Total addresses&lt;/th&gt;
&lt;th&gt;Usable&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;/16&lt;/td&gt;
&lt;td&gt;65,536&lt;/td&gt;
&lt;td&gt;65,531&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/20&lt;/td&gt;
&lt;td&gt;4,096&lt;/td&gt;
&lt;td&gt;4,091&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/24&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;251&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/28&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To reverse-engineer a prefix from a required host count: round up to the next power of two, subtract the exponent from 32. Need 300 hosts? Next power of two is 512 (2⁹), so prefix = 32 - 9 = &lt;code&gt;/23&lt;/code&gt;. Run this before sizing any subnet that will host an autoscaling group or EKS node group.&lt;/p&gt;

&lt;p&gt;Start with &lt;code&gt;/16&lt;/code&gt; for the VPC itself. VPC CIDR is difficult to resize after the fact — once you have subnets, peering connections, or Transit Gateway attachments built against it, renumbering becomes a migration project. &lt;code&gt;/16&lt;/code&gt; costs nothing up front and avoids that corner.&lt;/p&gt;




&lt;h2&gt;
  
  
  Subnet allocation: carving up the VPC
&lt;/h2&gt;

&lt;p&gt;A practical three-AZ production layout from &lt;code&gt;10.0.0.0/16&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;AZ-a&lt;/th&gt;
&lt;th&gt;AZ-b&lt;/th&gt;
&lt;th&gt;AZ-c&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Typical use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public&lt;/td&gt;
&lt;td&gt;10.0.0.0/24&lt;/td&gt;
&lt;td&gt;10.0.1.0/24&lt;/td&gt;
&lt;td&gt;10.0.2.0/24&lt;/td&gt;
&lt;td&gt;/24&lt;/td&gt;
&lt;td&gt;ALB, NAT gateway, bastion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private/app&lt;/td&gt;
&lt;td&gt;10.0.16.0/20&lt;/td&gt;
&lt;td&gt;10.0.32.0/20&lt;/td&gt;
&lt;td&gt;10.0.48.0/20&lt;/td&gt;
&lt;td&gt;/20&lt;/td&gt;
&lt;td&gt;EKS nodes, ECS, EC2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;10.0.64.0/24&lt;/td&gt;
&lt;td&gt;10.0.65.0/24&lt;/td&gt;
&lt;td&gt;10.0.66.0/24&lt;/td&gt;
&lt;td&gt;/24&lt;/td&gt;
&lt;td&gt;RDS, ElastiCache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reserved&lt;/td&gt;
&lt;td&gt;10.0.128.0/17&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;/17&lt;/td&gt;
&lt;td&gt;Future tiers, Transit Gateway, VPN&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The jump from &lt;code&gt;/24&lt;/code&gt; in the public tier to &lt;code&gt;/20&lt;/code&gt; in the app tier is intentional. ALBs and NAT gateways consume very few IPs; the app tier is where consumption scales with autoscaling groups, rolling deployments, and pod density.&lt;/p&gt;

&lt;p&gt;For EKS specifically: with the VPC CNI, every pod can consume an ENI-backed IP. IP exhaustion is one of the most common EKS production incidents. &lt;code&gt;/20&lt;/code&gt; per AZ for worker subnets is the standard starting point.&lt;/p&gt;

&lt;p&gt;The deliberate gaps between tiers (0–2, then 16–48, then 64–66) leave room to insert new tiers later without renumbering anything already deployed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Route tables: the actual decision maker
&lt;/h2&gt;

&lt;p&gt;A subnet is "public" or "private" because of its route table — not any inherent property of the subnet itself. The table is a list of &lt;code&gt;destination → target&lt;/code&gt; rules evaluated by &lt;strong&gt;most specific match&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every route table gets an implicit &lt;code&gt;local&lt;/code&gt; route for the full VPC CIDR — this can't be removed, and it's what lets every subnet reach every other subnet inside the VPC by default.&lt;/p&gt;

&lt;p&gt;A public subnet route table in Terraform:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table"&lt;/span&gt; &lt;span class="s2"&gt;"public"&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;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&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;"rtb-public"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"public"&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_route"&lt;/span&gt; &lt;span class="s2"&gt;"public_internet"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;destination_cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
  &lt;span class="nx"&gt;gateway_id&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&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="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table_association"&lt;/span&gt; &lt;span class="s2"&gt;"public_a"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_az_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating the route table does nothing on its own — the association step is what binds it to a subnet and makes routing take effect.&lt;/p&gt;




&lt;h2&gt;
  
  
  Internet Gateway and the three conditions for inbound access
&lt;/h2&gt;

&lt;p&gt;An IGW is horizontally scaled, redundant, and AZ-agnostic — one per VPC, no capacity to configure. It does two things: 1:1 NAT between public and private IPs (the public IP mapping lives at the IGW, not on the instance — which is why &lt;code&gt;ip addr&lt;/code&gt; on an EC2 instance never shows its public IP), and serves as a route table target.&lt;/p&gt;

&lt;p&gt;All three of these must be true simultaneously for inbound internet access to work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The instance has a public or Elastic IP on its ENI.&lt;/li&gt;
&lt;li&gt;The subnet's route table has &lt;code&gt;0.0.0.0/0 → IGW&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Both the security group and the NACL allow the inbound traffic on that port.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Any one missing produces the same symptom: a silent timeout with no obvious pointer to the actual cause. This is where most "why can't I reach my instance" tickets originate.&lt;/p&gt;




&lt;h2&gt;
  
  
  NAT Gateway: outbound only
&lt;/h2&gt;

&lt;p&gt;A NAT gateway lives in a specific subnet in a specific AZ, performs source NAT for private instances, and has real hourly and per-GB cost. The packet walk for a private instance at &lt;code&gt;10.0.2.15&lt;/code&gt; requesting a public registry:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Private subnet route table: &lt;code&gt;0.0.0.0/0 → nat-0abc...&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;NAT gateway rewrites source to its own Elastic IP + ephemeral port&lt;/li&gt;
&lt;li&gt;NAT gateway's public subnet route table: &lt;code&gt;0.0.0.0/0 → IGW&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;IGW performs its own separate 1:1 NAT translation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two distinct NAT translations — easy to collapse into one mental step, but they're separate resources doing separate jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it lives in the public subnet:&lt;/strong&gt; the NAT gateway needs its own route to the IGW, so it must sit in a subnet whose route table already points to the IGW. The private subnet's route table then points &lt;code&gt;0.0.0.0/0&lt;/code&gt; at the NAT gateway. Two different route tables, two different subnets, one resource bridging them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HA pattern:&lt;/strong&gt; one NAT gateway per AZ, each AZ's private subnet routing to the NAT gateway in its own AZ. One NAT gateway for the whole VPC is cheaper but creates a single point of failure — if that AZ has an outage, every private subnet in every other AZ loses outbound internet access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost trap worth auditing:&lt;/strong&gt; traffic to S3 and DynamoDB from private subnets doesn't need to go through NAT at all if you use VPC Gateway Endpoints. Routing S3 traffic through NAT is billed per GB with no benefit over a free Gateway Endpoint.&lt;/p&gt;




&lt;h2&gt;
  
  
  Read the Full Article
&lt;/h2&gt;

&lt;p&gt;The summary covers the core mental model. The full article goes deeper on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The reverse-engineering formula for subnet sizing applied to autoscaling groups and EKS node groups, with the specific IP exhaustion failure mode explained&lt;/li&gt;
&lt;li&gt;The full route table example with VPC peering and S3 Gateway Endpoint entries, and why most-specific-match matters for overlapping routes&lt;/li&gt;
&lt;li&gt;IGW statelessness and why NACLs require explicit ephemeral port rules (&lt;code&gt;1024–65535&lt;/code&gt;) that security groups handle automatically&lt;/li&gt;
&lt;li&gt;NAT Gateway connection tracking limits: 55,000 concurrent connections per unique destination, &lt;code&gt;PortAllocationErrors&lt;/code&gt; in CloudWatch as the signal, and when to reconsider architecture vs. adding more NAT gateways&lt;/li&gt;
&lt;li&gt;The full security group vs. NACL comparison — stateful vs. stateless evaluation, allow-only vs. allow-and-deny, and why the default NACL and default security group behave differently out of the box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://aloknecessary.in/blogs/aws-vpc-networking-fundamentals/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_syndication&amp;amp;utm_content=aws-vpc-networking-fundamentals" rel="noopener noreferrer"&gt;AWS VPC Networking Fundamentals — Full Article&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>networking</category>
      <category>vpc</category>
      <category>terraform</category>
      <category>aws</category>
    </item>
    <item>
      <title>Microservices by Default: The Organizational Constraints Nobody Puts in the Architecture Diagram</title>
      <dc:creator>Alok Ranjan Daftuar</dc:creator>
      <pubDate>Tue, 25 Aug 2026 06:29:39 +0000</pubDate>
      <link>https://dev.to/aloknecessary/microservices-by-default-the-organizational-constraints-nobody-puts-in-the-architecture-diagram-258l</link>
      <guid>https://dev.to/aloknecessary/microservices-by-default-the-organizational-constraints-nobody-puts-in-the-architecture-diagram-258l</guid>
      <description>&lt;p&gt;"We're moving to microservices" has been the architectural ambition of engineering teams for a decade. More than 40% of organisations now report regretting at least some of those decisions. One team's consolidation back to a monolith: response times improved 13x, AWS costs dropped from $18K to $2.4K/month, deployment time fell from 45 minutes to 6 minutes.&lt;/p&gt;

&lt;p&gt;These are not teams that failed at microservices. They implemented them correctly — and discovered the operational premium was not proportional to the benefit.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Distributed System Tax
&lt;/h2&gt;

&lt;p&gt;Every network boundary introduces failure modes that don't exist in-process: retries, circuit breakers, idempotency, distributed tracing, timeout budgets, schema versioning. This tax consumes 30–50% of engineering capacity once you cross the process boundary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What you pay per service boundary:

Network:      Retries, circuit breakers, timeouts, idempotency keys
Observability: Distributed tracing, per-service dashboards, N on-call rotations
Deployment:   N pipelines, N rollback procedures, compatibility windows
Data:         No foreign keys, eventual consistency, data duplication, sagas

Multiply by number of services. Then ask: does the benefit justify this?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conway's Law Is a Constraint, Not a Suggestion
&lt;/h2&gt;

&lt;p&gt;Microservices deliver their promise only when the team structure supports end-to-end ownership without cross-team coordination for routine changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer:  "We're splitting into order-service, inventory-service,
             and payment-service."
Architect:  "Which team owns each?"
Developer:  "The same team. Us."
Architect:  "So you're paying the distributed system tax for three
             services, with no autonomy dividend, because one team
             owns all three."
Developer:  "..."
Architect:  "How many engineers do you have?"
Developer:  "Seven."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Inverse Conway Maneuver: restructure teams first. The architecture follows. Decomposing architecture first and hoping teams catch up is how seven engineers end up debugging a distributed trace across five services for a bug that would have been a five-line stack trace in a monolith.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Microservices Are Premature
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fewer than three independent teams&lt;/strong&gt; — communication overhead of service boundaries exceeds coordination overhead of a shared codebase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domains still evolving&lt;/strong&gt; — wrong service boundaries require data migration and interface deprecation; wrong module boundaries are a refactoring task&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Primary bottleneck isn't deployment frequency&lt;/strong&gt; — microservices won't fix slow tests, hiring, or infrastructure cost&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When Microservices ARE Worth It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Multiple autonomous teams with clear domain ownership&lt;/li&gt;
&lt;li&gt;Genuinely divergent scaling requirements (&amp;gt;5x difference between components)&lt;/li&gt;
&lt;li&gt;Compliance isolation requirements (PCI-DSS, HIPAA)&lt;/li&gt;
&lt;li&gt;High deployment frequency at scale (dozens of deploys/day across teams)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Modular Monolith — The Default That Gets Skipped
&lt;/h2&gt;

&lt;p&gt;A single deployable with enforced module boundaries aligned to business domains. Not a monolith with good intentions — a monolith where boundaries are enforced by build tooling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/src/modules
  /orders/api          ← public interface (what other modules may call)
  /orders/domain       ← internal (not accessible from other modules)
  /payments/api
  /payments/domain
  /inventory/api
  /inventory/domain

Rule: orders may only import from payments/api
      Enforced by ArchUnit, NDepend, or equivalent

Benefit: when you extract payments into a service,
         the boundary is already clean. The API is already defined.
         The migration is a deployment change, not a redesign.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Decomposition Decision Framework
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: What specific problem is decomposition solving?
Step 2: Do the organisational prerequisites exist?
        - Team exists to own the service end-to-end?
        - Team can deploy without coordinating with others?
        - Service interface is stable?
Step 3: Use the strangler fig — extract one bounded context at a time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Warning Signs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You have a distributed monolith:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Services deployed together in fixed order&lt;/li&gt;
&lt;li&gt;Changes to Service A always require changes to Service B&lt;/li&gt;
&lt;li&gt;Production incidents involve debugging 4+ services simultaneously&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Your monolith doesn't need decomposing:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy in under 10 minutes&lt;/li&gt;
&lt;li&gt;Teams work in clearly separated namespaces&lt;/li&gt;
&lt;li&gt;On-call incidents debuggable with a single log stream&lt;/li&gt;
&lt;li&gt;Can scale horizontally without issues&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Read the Full Article
&lt;/h2&gt;

&lt;p&gt;This is a summary of the fourth post in the Cloud Defaults Reconsidered series. The full article includes the complete distributed system tax breakdown, Conway's Law analysis, modular monolith implementation patterns, and the full decomposition decision framework:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://aloknecessary.in/blogs/microservices-by-default/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_syndication&amp;amp;utm_content=microservices-by-default" rel="noopener noreferrer"&gt;Microservices by Default — Full Article&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The full article includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete distributed system tax enumeration (network, observability, deployment, data)&lt;/li&gt;
&lt;li&gt;Conway's Law and the Inverse Conway Maneuver in depth&lt;/li&gt;
&lt;li&gt;Three common misconceptions debunked (scaling, deployment safety, "get boundaries right later")&lt;/li&gt;
&lt;li&gt;Modular monolith implementation with enforcement tooling&lt;/li&gt;
&lt;li&gt;Warning signs for distributed monolith vs well-structured monolith&lt;/li&gt;
&lt;li&gt;Full decomposition decision framework (pain point identification, prerequisite checklist, strangler fig extraction)&lt;/li&gt;
&lt;li&gt;Key takeaways with actionable thresholds&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>microservices</category>
      <category>architecture</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>GitOps Repo Structure and Application Patterns: App-of-Apps, ApplicationSets, and AppProjects</title>
      <dc:creator>Alok Ranjan Daftuar</dc:creator>
      <pubDate>Wed, 19 Aug 2026 09:34:10 +0000</pubDate>
      <link>https://dev.to/aloknecessary/gitops-repo-structure-and-application-patterns-app-of-apps-applicationsets-and-appprojects-2ae4</link>
      <guid>https://dev.to/aloknecessary/gitops-repo-structure-and-application-patterns-app-of-apps-applicationsets-and-appprojects-2ae4</guid>
      <description>&lt;p&gt;Once you have ArgoCD running across clusters — hub-and-spoke or per-cluster — the next question is how to organize what it deploys. How should Git repositories be structured? When does App-of-Apps break down? What actually stops one team from deploying into another team's namespace?&lt;/p&gt;

&lt;p&gt;Those decisions compound. Get the repo structure wrong and every later choice about ApplicationSets and AppProjects inherits the mess.&lt;/p&gt;




&lt;h2&gt;
  
  
  Repo Structure: Where Change Friction Lives
&lt;/h2&gt;

&lt;p&gt;Three shapes dominate in practice. The choice isn't aesthetic — it determines where friction lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mono-repo&lt;/strong&gt; — one repository, all teams, all environments, organized by directory.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wins: one place to search, one PR history, easy to see what's deployed where&lt;/li&gt;
&lt;li&gt;Costs: PR review load and merge contention scale with team count; access control is directory-level CODEOWNERS, not repo permissions — weaker isolation than compliance-conscious teams want&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Repo-per-team&lt;/strong&gt; — each team owns a repository for everything it deploys.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wins: access control is a repo permission; a team's deploy cadence doesn't create merge contention with others; blast radius of a bad commit is scoped to one team&lt;/li&gt;
&lt;li&gt;Costs: cross-cutting changes (shared base image bump, cluster-wide policy) now touch N repos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Repo-per-app&lt;/strong&gt; — finest grain, one repo per service.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wins: tightest blast radius and access control&lt;/li&gt;
&lt;li&gt;Costs: repo sprawl; cross-app changes are the worst version of the mono-repo problem — N repos, each requiring a separate PR&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service count&lt;/th&gt;
&lt;th&gt;Team count&lt;/th&gt;
&lt;th&gt;Access control need&lt;/th&gt;
&lt;th&gt;Recommended structure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Under 8&lt;/td&gt;
&lt;td&gt;1 platform team&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Mono-repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8–30&lt;/td&gt;
&lt;td&gt;2–5 independent teams&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Repo-per-team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30+&lt;/td&gt;
&lt;td&gt;5+ teams, some with regulatory isolation&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Repo-per-app, or repo-per-team with sensitive services split out&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  App-of-Apps: Simple, Until It Isn't
&lt;/h2&gt;

&lt;p&gt;The simplest multi-Application pattern: a root Application whose only job is to deploy more Applications.&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;argoproj.io/v1alpha1&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;Application&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root-checkout-team&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&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;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-team&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/example-org/checkout-team-gitops.git&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&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;apps&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://kubernetes.default.svc&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
  &lt;span class="na"&gt;syncPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;automated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;prune&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;selfHeal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It breaks down in two predictable ways at scale:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sync-wave ordering is not a dependency gate.&lt;/strong&gt; A &lt;code&gt;shared-config&lt;/code&gt; Application at &lt;code&gt;sync-wave: "0"&lt;/code&gt; and a &lt;code&gt;checkout-service&lt;/code&gt; at &lt;code&gt;sync-wave: "1"&lt;/code&gt; looks correct — until &lt;code&gt;shared-config&lt;/code&gt; fails to sync. ArgoCD's wave ordering governs &lt;em&gt;when&lt;/em&gt; a sync is attempted, not whether it's a hard prerequisite. &lt;code&gt;checkout-service&lt;/code&gt; still syncs on schedule, its Deployment references a ConfigMap key that was never written, and the failure surfaces as a CrashLoopBackOff with no obvious link to the upstream cause.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding a new cluster or environment means hand-writing a new Application manifest every time.&lt;/strong&gt; At 3 clusters and 5 services that's 15 manifests. At 12 clusters and 30 services it's 360 — every one written, not generated.&lt;/p&gt;




&lt;h2&gt;
  
  
  ApplicationSets: The Scaling Answer
&lt;/h2&gt;

&lt;p&gt;Instead of writing N Application manifests, write one ApplicationSet with a generator that produces them.&lt;/p&gt;

&lt;p&gt;The generators that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cluster&lt;/strong&gt; — generates one Application per registered cluster, optionally filtered by label. Register a new spoke cluster and Applications for it appear automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git directory&lt;/strong&gt; — generates Applications from repo structure: one per subdirectory. Add a new service directory and a new Application appears — no ApplicationSet edit required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Matrix&lt;/strong&gt; — combines two generators, producing the cross-product. Cluster × Git directory is the combination that matters most: every service in the repo, deployed to every cluster carrying a matching label.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&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;ApplicationSet&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-team-services&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&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;generators&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;generators&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;git&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/example-org/checkout-team-gitops.git&lt;/span&gt;
              &lt;span class="na"&gt;revision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
              &lt;span class="na"&gt;directories&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;apps/*&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;clusters&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="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout&lt;/span&gt;
                  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{path.basename}}-{{name}}'&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;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-team&lt;/span&gt;
      &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/example-org/checkout-team-gitops.git&lt;/span&gt;
        &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{path}}'&lt;/span&gt;
      &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{server}}'&lt;/span&gt;
        &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout&lt;/span&gt;
      &lt;span class="na"&gt;syncPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;automated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;prune&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;selfHeal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a new service directory — it deploys to every matching cluster automatically. Register a new cluster with the right labels — every existing service deploys to it automatically. Neither event requires touching the ApplicationSet.&lt;/p&gt;

&lt;p&gt;That same automatic fan-out is why an ApplicationSet change deserves a dry run before it's applied — a mistyped label selector doesn't fail one Application, it fails every Application it would have generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;argocd appset generate applicationset.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This renders the full set of Applications the ApplicationSet would produce without applying anything — catching "this now matches four more clusters than intended" before it becomes a live sync.&lt;/p&gt;




&lt;h2&gt;
  
  
  AppProjects: The Multi-Tenancy Boundary
&lt;/h2&gt;

&lt;p&gt;Everything so far describes how Applications get created. It says nothing about what stops one team's ApplicationSet from deploying into another team's namespace. That boundary is &lt;code&gt;AppProject&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="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&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;AppProject&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-team&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&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;sourceRepos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;https://github.com/example-org/checkout-team-gitops.git&lt;/span&gt;
  &lt;span class="na"&gt;destinations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://spoke-eks-prod-us-east-1&lt;/span&gt;
      &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://spoke-eks-prod-us-east-1&lt;/span&gt;
      &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cart&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://spoke-aks-prod-westeurope&lt;/span&gt;
      &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout&lt;/span&gt;
  &lt;span class="na"&gt;clusterResourceWhitelist&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
  &lt;span class="na"&gt;namespaceResourceBlacklist&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ResourceQuota&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LimitRange&lt;/span&gt;
  &lt;span class="na"&gt;roles&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;checkout-team-sync&lt;/span&gt;
      &lt;span class="na"&gt;policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;p, proj:checkout-team:checkout-team-sync, applications, sync, checkout-team/*, allow&lt;/span&gt;
      &lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;checkout-team-engineers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things worth being deliberate about:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;sourceRepos&lt;/code&gt; should list the team's actual repos, not &lt;code&gt;*&lt;/code&gt;.&lt;/strong&gt; A wildcarded source list means any Application in this project can pull manifests from anywhere — defeating the point of repo-per-team access control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;destinations&lt;/code&gt; should be an explicit cluster/namespace list, not a wildcard.&lt;/strong&gt; This is the line that stops a checkout-team ApplicationSet, misconfigured or compromised, from deploying into a payments-team namespace. A Matrix generator with a slightly-too-broad label selector (&lt;code&gt;env: prod&lt;/code&gt; instead of &lt;code&gt;team: checkout, env: prod&lt;/code&gt;) will happily generate Applications targeting every production cluster it can see. Without an AppProject destinations list, ArgoCD will attempt every one of them. The label selector mistake is the proximate cause; the AppProject destinations list is what turns that mistake into a rejected sync instead of an actual cross-team deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;clusterResourceWhitelist: []&lt;/code&gt;&lt;/strong&gt; is a deliberately restrictive default. Most application teams have no legitimate reason to create &lt;code&gt;ClusterRole&lt;/code&gt;s or &lt;code&gt;Namespace&lt;/code&gt;s through their own AppProject. Reserve that capability for a separate, tightly held platform-team project.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the Three Pieces Compose
&lt;/h2&gt;

&lt;p&gt;Repo structure determines what the ApplicationSet's Git generator sees. The ApplicationSet's Matrix generator combines that with cluster registration to produce Applications — if you haven't read &lt;a href="https://aloknecessary.in/blogs/multi-cluster-argocd-architecture/" rel="noopener noreferrer"&gt;Multi-Cluster ArgoCD Architecture&lt;/a&gt;, that's the foundation this builds on. The AppProject referenced by every one of those Applications keeps the resulting sync operations inside the boundary that repo was ever supposed to have.&lt;/p&gt;

&lt;p&gt;Skip AppProjects and the first two pieces still function — they just function without a backstop, which tends to be fine until the day it very much isn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  Read the Full Article
&lt;/h2&gt;

&lt;p&gt;The full article covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detailed cost/benefit analysis for all three repo structure shapes with the decision table&lt;/li&gt;
&lt;li&gt;App-of-Apps sync-wave failure mode explained in full with the CrashLoopBackOff scenario&lt;/li&gt;
&lt;li&gt;Complete Matrix generator YAML with all four ApplicationSet generator types explained&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;argocd appset generate&lt;/code&gt; dry-run workflow for catching label selector mistakes before they go live&lt;/li&gt;
&lt;li&gt;Full AppProject YAML with &lt;code&gt;sourceRepos&lt;/code&gt;, &lt;code&gt;destinations&lt;/code&gt;, &lt;code&gt;clusterResourceWhitelist&lt;/code&gt;, &lt;code&gt;namespaceResourceBlacklist&lt;/code&gt;, and role definitions&lt;/li&gt;
&lt;li&gt;The cross-team deployment scenario that AppProject destinations prevent — and why the label selector mistake alone isn't enough to cause it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://aloknecessary.in/blogs/gitops-repo-structure-application-patterns/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_syndication&amp;amp;utm_content=gitops-repo-structure-application-patterns" rel="noopener noreferrer"&gt;GitOps Repo Structure and Application Patterns — Full Article&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>argocd</category>
      <category>gitops</category>
    </item>
    <item>
      <title>Multi-Agent Systems Architecture: Patterns, Pitfalls, and Production Reality</title>
      <dc:creator>Alok Ranjan Daftuar</dc:creator>
      <pubDate>Mon, 17 Aug 2026 06:32:23 +0000</pubDate>
      <link>https://dev.to/aloknecessary/multi-agent-systems-architecture-patterns-pitfalls-and-production-reality-35h3</link>
      <guid>https://dev.to/aloknecessary/multi-agent-systems-architecture-patterns-pitfalls-and-production-reality-35h3</guid>
      <description>&lt;p&gt;An agent stuck in an infinite retry loop doesn't show up in your error rate. It shows up in your AWS bill — eleven days later.&lt;/p&gt;

&lt;p&gt;A team ran a multi-agent system for eleven days. Latency normal. Error rate 0.0%. Every dashboard green. Cloud bill: $47,000. The agents were stuck in an infinite retry loop the entire time — because "wrong" doesn't show up in Grafana.&lt;/p&gt;

&lt;p&gt;This incident is representative of a failure class unique to multi-agent architecture: agents operating correctly by every infrastructure metric while producing incorrect, looping, or redundant behavior at the application layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Multi-Agent Is Not the Answer
&lt;/h2&gt;

&lt;p&gt;Most teams reach for multi-agent too early. Stay with a single agent when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The task fits in one context window (200K Claude / 1M Gemini)&lt;/li&gt;
&lt;li&gt;Latency SLA is under 10 seconds&lt;/li&gt;
&lt;li&gt;Your single-agent baseline isn't tuned yet&lt;/li&gt;
&lt;li&gt;The work is sequential (no parallelism gain)&lt;/li&gt;
&lt;li&gt;You don't have observability infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multi-agent earns its complexity for: parallel subtasks, distinct tool access requirements, or failure isolation needs.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Four Coordination Patterns
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Orchestrator-Worker&lt;/strong&gt; (~70% of production deployments): central planner decomposes tasks, dispatches to specialist workers, synthesizes results. Single point of failure but best observability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sequential Pipeline&lt;/strong&gt;: fixed linear chain. Deterministic, easy to debug, but latency stacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dynamic Handoff (Swarm)&lt;/strong&gt;: no central coordinator — agents decide who handles what at runtime. Flexible but prone to infinite handoff loops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hierarchical&lt;/strong&gt;: orchestrators managing sub-orchestrators. For large-scale decomposition where a single orchestrator can't hold the full planning state.&lt;/p&gt;




&lt;h2&gt;
  
  
  State Persistence: Demo vs Production
&lt;/h2&gt;

&lt;p&gt;The single capability that separates them: persisting intermediate results so workflows can pause, resume, and retry individual failed agents without restarting from scratch. Checkpoint at every task-level status transition to durable storage (Postgres, Temporal).&lt;/p&gt;




&lt;h2&gt;
  
  
  Inter-Agent Contracts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MCP&lt;/strong&gt; for agent-to-tool communication. &lt;strong&gt;A2A&lt;/strong&gt; for agent-to-agent. Beyond protocols: every handoff needs a typed, validated Pydantic model. Validation runs at the boundary — not three agents downstream when bad data causes inscrutable errors.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cost Controls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Budget per workflow run&lt;/strong&gt; — total token budget tracked and enforced across all agents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model tier routing&lt;/strong&gt; — frontier model only for reasoning-heavy steps; Haiku for routing/classification/formatting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard retry ceiling&lt;/strong&gt; — &lt;code&gt;attempt_count &amp;gt;= 3&lt;/code&gt; terminates, not retries forever&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequential before parallel&lt;/strong&gt; — fan-out increases total spend even when it reduces latency&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Observability: Detecting "Wrong"
&lt;/h2&gt;

&lt;p&gt;Standard metrics detect whether agents are &lt;em&gt;running&lt;/em&gt;. Multi-agent systems need metrics that detect whether agents are &lt;em&gt;making progress&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workflow completion rate&lt;/strong&gt; — not error rate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry anomaly detection&lt;/strong&gt; — total retries significantly exceeding task count = stuck loop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost per workflow run&lt;/strong&gt; — token counts aggregated per workflow ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-agent failure rate&lt;/strong&gt; — which role is the common failure point&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The $47K alert: flag any workflow where &lt;code&gt;sum(attempt_count) &amp;gt; 2 * len(tasks)&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Read the Full Article
&lt;/h2&gt;

&lt;p&gt;This is a summary of my deep dive into multi-agent production architecture. The full article covers all patterns with implementation examples:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://aloknecessary.in/blogs/multi-agent-systems-architecture/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_syndication&amp;amp;utm_content=multi-agent-systems-architecture" rel="noopener noreferrer"&gt;Multi-Agent Systems Architecture — Full Article&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The full article includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When multi-agent is NOT the answer (5 heuristics)&lt;/li&gt;
&lt;li&gt;Four coordination patterns with architecture diagrams&lt;/li&gt;
&lt;li&gt;Orchestration vs choreography comparison table&lt;/li&gt;
&lt;li&gt;WorkflowState persistence with checkpoint-level granularity (Python dataclasses)&lt;/li&gt;
&lt;li&gt;Inter-agent contracts with Pydantic validation&lt;/li&gt;
&lt;li&gt;MCP and A2A protocol roles explained&lt;/li&gt;
&lt;li&gt;Circuit breaker implementation for agent calls&lt;/li&gt;
&lt;li&gt;Saga pattern for side-effectful agent workflows&lt;/li&gt;
&lt;li&gt;Cost control patterns (budget caps, model tiers, retry ceilings)&lt;/li&gt;
&lt;li&gt;OTel tracing decorator for agent calls&lt;/li&gt;
&lt;li&gt;Retry anomaly detection alert logic&lt;/li&gt;
&lt;li&gt;Production deployment checklist (15 items)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>distributedsystems</category>
      <category>python</category>
    </item>
    <item>
      <title>Service Mesh Everywhere? The Operational Cost of Cluster-Wide mTLS</title>
      <dc:creator>Alok Ranjan Daftuar</dc:creator>
      <pubDate>Fri, 14 Aug 2026 07:05:14 +0000</pubDate>
      <link>https://dev.to/aloknecessary/service-mesh-everywhere-the-operational-cost-of-cluster-wide-mtls-4pop</link>
      <guid>https://dev.to/aloknecessary/service-mesh-everywhere-the-operational-cost-of-cluster-wide-mtls-4pop</guid>
      <description>&lt;p&gt;"Add a service mesh" has become the Kubernetes equivalent of "make everything private." The reasoning seems unassailable: mTLS, observability, traffic management — all handled transparently. But the recommendation rarely comes with the operational bill attached.&lt;/p&gt;

&lt;p&gt;At 1,000 pods, traditional Envoy sidecars consume approximately 70 GB of memory — before a single byte of application traffic. The latency overhead of Istio's traditional sidecar mTLS: +166% at high load. Compared to 8% for Istio Ambient and 33% for Linkerd.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Service Mesh Actually Provides
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;mTLS&lt;/strong&gt; — encrypted, mutually authenticated pod-to-pod communication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traffic management&lt;/strong&gt; — weighted routing, canary, fault injection, circuit breaking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt; — automatic golden signals for every service-to-service call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy&lt;/strong&gt; — authorisation rules enforced at the network layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it does NOT provide: free performance, simpler operations, automatic security, or a substitute for application-level observability.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Cost
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Traditional sidecar overhead (Istio + Envoy):
  1,000 pods: ~72 GB memory overhead
  Monthly cost for proxy memory alone: ~$4,284

Latency at high load:
  Istio sidecar:  +166%
  Linkerd:        +33%
  Istio Ambient:  +8%
  Cilium (L7):    +99%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus: operational overhead of debugging Envoy config, pilot reconciliation, sidecar injection, and mesh upgrade coordination.&lt;/p&gt;




&lt;h2&gt;
  
  
  When a Mesh Is Unnecessary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&amp;lt; 10 services&lt;/strong&gt; — NetworkPolicy + cert-manager covers it at a fraction of the cost&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dev/staging clusters&lt;/strong&gt; — adds debugging surface without production benefit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monolithic deployments&lt;/strong&gt; — one service-to-service call doesn't justify a mesh&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teams without dedicated platform ownership&lt;/strong&gt; — the mesh will add more incidents than it prevents&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When a Mesh IS Worth It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;50+ services across multiple teams with genuine policy requirements&lt;/li&gt;
&lt;li&gt;Canary deployments and traffic shaping at scale&lt;/li&gt;
&lt;li&gt;Regulated environments with explicit mTLS and auditability requirements&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The 2026 Landscape Has Changed
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Istio Ambient Mode&lt;/strong&gt; (GA in 1.25): per-node ztunnel handles L4 mTLS, 90%+ memory reduction, 8% latency overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cilium Service Mesh&lt;/strong&gt;: eBPF at kernel level, no sidecar, 40–60% network overhead reduction vs traditional proxies.&lt;/p&gt;

&lt;p&gt;If overhead was your objection before mid-2025, re-evaluate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lighter Alternatives
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;mTLS&lt;/strong&gt;: cert-manager + NetworkPolicy + IRSA/Workload Identity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt;: OpenTelemetry with DaemonSet collector&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traffic management&lt;/strong&gt;: Argo Rollouts + NGINX Ingress weighted routing&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Read the Full Article
&lt;/h2&gt;

&lt;p&gt;This is a summary of the third post in the Cloud Defaults Reconsidered series. The full article includes the complete decision framework, detailed cost breakdowns, operational overhead analysis, and architecture selection guide:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://aloknecessary.in/blogs/service-mesh-everywhere/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_syndication&amp;amp;utm_content=service-mesh-everywhere" rel="noopener noreferrer"&gt;Service Mesh Everywhere? — Full Article&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The full article includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory and latency overhead calculations at scale&lt;/li&gt;
&lt;li&gt;Operational debugging scenarios and upgrade coordination costs&lt;/li&gt;
&lt;li&gt;Common misconceptions debunked (mTLS ≠ mesh, "free" observability, upgrade safety)&lt;/li&gt;
&lt;li&gt;When a mesh is unnecessary vs when it's justified&lt;/li&gt;
&lt;li&gt;Istio Ambient vs Cilium vs Linkerd vs traditional sidecars comparison&lt;/li&gt;
&lt;li&gt;Complete 3-step decision framework&lt;/li&gt;
&lt;li&gt;Lighter alternatives that close 80% of the gap&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>architecture</category>
      <category>servicemesh</category>
    </item>
    <item>
      <title>Multi-Cluster ArgoCD Architecture: Hub-and-Spoke vs. Per-Cluster, Done Right</title>
      <dc:creator>Alok Ranjan Daftuar</dc:creator>
      <pubDate>Mon, 10 Aug 2026 05:48:33 +0000</pubDate>
      <link>https://dev.to/aloknecessary/multi-cluster-argocd-architecture-hub-and-spoke-vs-per-cluster-done-right-p17</link>
      <guid>https://dev.to/aloknecessary/multi-cluster-argocd-architecture-hub-and-spoke-vs-per-cluster-done-right-p17</guid>
      <description>&lt;p&gt;Every ArgoCD tutorial ends with one cluster, one ArgoCD instance, and &lt;code&gt;kubectl config current-context&lt;/code&gt; pointing at the same place ArgoCD is installed. That works fine — until you have two clusters. By the time you're at 10 or 15, across AWS and Azure, the architecture you picked on day one is either quietly paying for itself or quietly costing you an incident a quarter.&lt;/p&gt;

&lt;p&gt;The two dominant patterns — hub-and-spoke and ArgoCD-per-cluster — are both reasonable in the right context and both wrong when applied past the cluster count and team topology they were designed for.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Two Bad Defaults Teams Back Into
&lt;/h2&gt;

&lt;p&gt;Rather than choosing deliberately, most teams end up at one of two failure modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ArgoCD-per-cluster sprawl&lt;/strong&gt; — every cluster gets its own ArgoCD because that's what the getting-started guide showed. Nobody has a single view of what's deployed where. Upgrading ArgoCD becomes N separate change requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A single hub that becomes a bottleneck&lt;/strong&gt; — one team stands up ArgoCD once, registers every cluster against it, and doesn't revisit that decision until the application controller is falling behind on reconciliation or a hub outage takes down deployments for every team at once.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Pattern 1: Hub-and-Spoke
&lt;/h2&gt;

&lt;p&gt;One management cluster runs ArgoCD. Every other cluster is registered as a remote destination via a &lt;code&gt;Secret&lt;/code&gt; containing that cluster's API server address and credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you get:&lt;/strong&gt; single pane of glass, centralized RBAC and SSO, one audit trail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it costs you:&lt;/strong&gt; the hub becomes a scaling bottleneck at high cluster/Application count; blast radius covers every spoke if the hub is compromised; network reachability to every spoke's API server is a real design requirement, not just an IAM policy.&lt;/p&gt;

&lt;p&gt;Two blast-radius shapes worth planning for specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Redis under memory pressure&lt;/strong&gt; — ArgoCD's application controller caches live-vs-desired state in Redis across every Application it manages. An OOM or eviction storm on that single Redis instance stalls reconciliation for every cluster the hub manages simultaneously. From the outside it looks like "ArgoCD is stuck everywhere" — the root cause is capacity planning on a component most teams treat as an implementation detail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A quietly broken peering path&lt;/strong&gt; — a security group rule tightened on one spoke's cluster security group doesn't fail loudly. It shows up as that spoke going &lt;code&gt;Unknown&lt;/code&gt; in the UI while everything else stays green, easy to dismiss as a blip until someone needs to ship a fix to that cluster and can't.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Network Reachability — The Part Tutorials Skip
&lt;/h3&gt;

&lt;p&gt;On &lt;strong&gt;AWS&lt;/strong&gt;: VPC peering or Transit Gateway gets the hub's control plane traffic to each spoke's private EKS API server endpoint. Private API server endpoints are the right default for spoke clusters, but every private-by-default decision adds a network path the hub now has to be deliberately connected to.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;Azure&lt;/strong&gt;: VNet peering or Azure Private Link between the hub's VNet and each spoke AKS cluster's VNet. Same shape of problem — the hub's egress needs a routable, authorized path to a spoke's control plane.&lt;/p&gt;

&lt;h3&gt;
  
  
  Controller Sharding Past 10 Clusters
&lt;/h3&gt;

&lt;p&gt;A hub managing 12 spoke clusters with ~50 Applications each (600 total) on a single unsharded controller will start lagging live cluster state by 3-4 minutes during normal operation, and considerably longer after a bulk change. The fix is multiple controller replicas with shard annotations on cluster secrets:&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;# argocd-application-controller StatefulSet&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;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Cluster Secret with shard annotation&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;argocd.argoproj.io/shard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;ARGOCD_CONTROLLER_REPLICAS=4&lt;/code&gt; and shard annotations spread across 12 spoke secrets, each replica watches 3 clusters instead of all 12 — reconciliation lag drops back to single-digit seconds. This change isn't complete without also moving to a Redis HA (Sentinel-backed) deployment, since a single Redis becomes the new bottleneck once four controller replicas hit it concurrently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pattern 2: ArgoCD-per-Cluster
&lt;/h2&gt;

&lt;p&gt;Each cluster runs its own ArgoCD instance and manages only itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you get:&lt;/strong&gt; fault isolation, no cross-cluster network dependency, clean blast radius.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it costs you:&lt;/strong&gt; N places to upgrade and patch; fragmented visibility; RBAC and project config duplicated N times with all the drift that implies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where this genuinely wins:&lt;/strong&gt; regulated environments where a cluster's isolation boundary is a compliance requirement; air-gapped clusters with no viable network path back to a central hub; edge deployments where each site operates independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cluster Registration — EKS
&lt;/h2&gt;

&lt;p&gt;The clean way to register a spoke EKS cluster is IRSA or EKS access entries — not a static kubeconfig with a long-lived token. The cluster secret uses the &lt;code&gt;aws eks get-token&lt;/code&gt; exec plugin:&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;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spoke-eks-prod-us-east-1&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;argocd.argoproj.io/secret-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cluster&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Opaque&lt;/span&gt;
&lt;span class="na"&gt;stringData&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;spoke-eks-prod-us-east-1&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://EXAMPLE1234567890.gr7.us-east-1.eks.amazonaws.com&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;"execProviderConfig": {&lt;/span&gt;
        &lt;span class="s"&gt;"command": "aws",&lt;/span&gt;
        &lt;span class="s"&gt;"args": [&lt;/span&gt;
          &lt;span class="s"&gt;"eks", "get-token",&lt;/span&gt;
          &lt;span class="s"&gt;"--cluster-name", "spoke-eks-prod",&lt;/span&gt;
          &lt;span class="s"&gt;"--region", "us-east-1",&lt;/span&gt;
          &lt;span class="s"&gt;"--role-arn", "arn:aws:iam::111122223333:role/argocd-hub-spoke-access"&lt;/span&gt;
        &lt;span class="s"&gt;],&lt;/span&gt;
        &lt;span class="s"&gt;"apiVersion": "client.authentication.k8s.io/v1beta1"&lt;/span&gt;
      &lt;span class="s"&gt;},&lt;/span&gt;
      &lt;span class="s"&gt;"tlsClientConfig": { "insecure": false, "caData": "&amp;lt;base64 cluster CA&amp;gt;" }&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full chain: create the IAM role on the spoke account with a trust policy scoped to the hub's IRSA role → create an EKS access entry mapping that role to a Kubernetes group → bind that group to a scoped &lt;code&gt;ClusterRole&lt;/code&gt; (not cluster-admin) → grant the hub's IRSA service account &lt;code&gt;sts:AssumeRole&lt;/code&gt; → apply the Secret → verify with &lt;code&gt;argocd cluster list&lt;/code&gt;. A &lt;code&gt;Successful&lt;/code&gt; status confirms the exec plugin chain worked end to end.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cluster Registration — AKS
&lt;/h2&gt;

&lt;p&gt;On AKS, the equivalent is Azure AD Workload Identity with the &lt;code&gt;kubelogin&lt;/code&gt; exec plugin:&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;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spoke-aks-prod-westeurope&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;argocd.argoproj.io/secret-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cluster&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Opaque&lt;/span&gt;
&lt;span class="na"&gt;stringData&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;spoke-aks-prod-westeurope&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://spoke-aks-prod-dns-a1b2c3d4.hcp.westeurope.azmk8s.io&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;"execProviderConfig": {&lt;/span&gt;
        &lt;span class="s"&gt;"command": "kubelogin",&lt;/span&gt;
        &lt;span class="s"&gt;"args": [&lt;/span&gt;
          &lt;span class="s"&gt;"get-token",&lt;/span&gt;
          &lt;span class="s"&gt;"--login", "workloadidentity",&lt;/span&gt;
          &lt;span class="s"&gt;"--server-id", "6dae42f8-4368-4678-94ff-3960e28e3630"&lt;/span&gt;
        &lt;span class="s"&gt;],&lt;/span&gt;
        &lt;span class="s"&gt;"apiVersion": "client.authentication.k8s.io/v1beta1"&lt;/span&gt;
      &lt;span class="s"&gt;},&lt;/span&gt;
      &lt;span class="s"&gt;"tlsClientConfig": { "insecure": false, "caData": "&amp;lt;base64 cluster CA&amp;gt;" }&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full chain: create an Azure AD application + federated identity credential binding the hub's ArgoCD service account to it → grant that application a scoped AKS RBAC role on the spoke cluster → label the hub's ArgoCD service account for workload identity → apply the Secret → verify with &lt;code&gt;argocd cluster list&lt;/code&gt;. Auth failures here are almost always a &lt;code&gt;--subject&lt;/code&gt; mismatch in the federated credential or the workload identity label missing from the pod spec.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision Framework
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cluster count&lt;/th&gt;
&lt;th&gt;Compliance boundary&lt;/th&gt;
&lt;th&gt;Team topology&lt;/th&gt;
&lt;th&gt;Recommended pattern&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Single platform team&lt;/td&gt;
&lt;td&gt;Hub-and-spoke&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3-5&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Single or lightly federated&lt;/td&gt;
&lt;td&gt;Hub-and-spoke with AppProjects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5-10&lt;/td&gt;
&lt;td&gt;Some (staging vs. prod)&lt;/td&gt;
&lt;td&gt;Multiple product teams&lt;/td&gt;
&lt;td&gt;Hub-and-spoke, watch controller sharding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10-15&lt;/td&gt;
&lt;td&gt;Regulatory/contractual isolation on specific clusters&lt;/td&gt;
&lt;td&gt;Multiple teams, some regulated&lt;/td&gt;
&lt;td&gt;Hybrid — hub for general fleet, per-cluster for isolated outliers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any&lt;/td&gt;
&lt;td&gt;Air-gapped / no viable hub network path&lt;/td&gt;
&lt;td&gt;Any&lt;/td&gt;
&lt;td&gt;ArgoCD-per-cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The hybrid row in practice: a platform team running 13 clusters — 10 standard clusters registering against the hub via IRSA/workload identity, plus 3 PCI-scoped clusters each running their own ArgoCD with no network path back to the hub, syncing from a separate access-restricted Git repository. The platform team accepts fragmented visibility for those 3 clusters in exchange for not having to argue, in every audit cycle, that the hub's blast radius doesn't touch the PCI boundary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Read the Full Article
&lt;/h2&gt;

&lt;p&gt;The full article includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hub-and-spoke network reachability deep-dive for EKS (VPC peering, private API server endpoints, Transit Gateway) and AKS (VNet peering, Azure Private Link)&lt;/li&gt;
&lt;li&gt;Complete step-by-step EKS auth chain: IAM role creation, EKS access entry, ClusterRoleBinding, hub-side IRSA policy&lt;/li&gt;
&lt;li&gt;Complete step-by-step AKS auth chain: Azure AD app, federated credential, AKS RBAC role assignment, workload identity label&lt;/li&gt;
&lt;li&gt;Controller sharding mechanics and Redis HA requirements for fleets past 10-15 clusters&lt;/li&gt;
&lt;li&gt;The trust-boundary thinking connecting IRSA/Workload Identity to ArgoCD's cluster auth model&lt;/li&gt;
&lt;li&gt;What's coming in Article 2: repo structure, App-of-Apps, ApplicationSets, and AppProjects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://aloknecessary.in/blogs/multi-cluster-argocd-architecture/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_syndication&amp;amp;utm_content=multi-cluster-argocd-architecture" rel="noopener noreferrer"&gt;Multi-Cluster ArgoCD Architecture — Full Article&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>gitops</category>
      <category>devops</category>
      <category>argocd</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>GitHub Actions OIDC: Eliminating Long-Lived Credentials from Your CI/CD Pipeline</title>
      <dc:creator>Alok Ranjan Daftuar</dc:creator>
      <pubDate>Fri, 07 Aug 2026 04:49:30 +0000</pubDate>
      <link>https://dev.to/aloknecessary/github-actions-oidc-eliminating-long-lived-credentials-from-your-cicd-pipeline-gmg</link>
      <guid>https://dev.to/aloknecessary/github-actions-oidc-eliminating-long-lived-credentials-from-your-cicd-pipeline-gmg</guid>
      <description>&lt;p&gt;Every GitHub Actions workflow that deploys to AWS or Azure needs cloud credentials. The traditional answer — generate an IAM access key or Azure client secret, store it in GitHub secrets — works, but means you have a long-lived credential that's valid until you notice it leaked and manually revoke it.&lt;/p&gt;

&lt;p&gt;OpenID Connect eliminates this at the architectural level. Your workflow requests a short-lived token from GitHub's OIDC provider, exchanges it with AWS STS or Azure's token endpoint, and receives temporary credentials valid for the duration of the job. No secret to store. No credential to rotate. No static value to leak.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub Actions Job
    │  1. Requests OIDC token from GitHub
    ▼
GitHub OIDC Provider (token.actions.githubusercontent.com)
    │  2. Issues signed JWT (sub, repo, ref, environment, exp: 5min)
    ▼
AWS STS / Azure Token Endpoint
    │  3. Validates JWT, checks trust policy conditions
    │  4. Issues temporary credentials (15min–1hr)
    ▼
GitHub Actions Job (continues with scoped credentials)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sub&lt;/code&gt; claim encodes the repository, branch, and environment — every trust policy decision is a decision about which &lt;code&gt;sub&lt;/code&gt; values you trust.&lt;/p&gt;




&lt;h2&gt;
  
  
  Trust Policy Scoping — The Critical Detail
&lt;/h2&gt;

&lt;p&gt;Most tutorials show the broadest condition that works. Production requires precision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;repo:org/repo:environment:production&lt;/code&gt;&lt;/strong&gt; — strongest for production; coupled to GitHub Environment protection rules (required reviewers, deployment gates)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;repo:org/repo:ref:refs/heads/main&lt;/code&gt;&lt;/strong&gt; — good for staging; only main branch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;repo:org/repo:*&lt;/code&gt;&lt;/strong&gt; — acceptable for dev/sandbox only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;repo:org/*&lt;/code&gt;&lt;/strong&gt; — never use for anything with real permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The environment-scoped condition means an unapproved deployment cannot produce the token needed to assume the production role. The gate is enforced at the identity layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Per-Job Role Architecture
&lt;/h2&gt;

&lt;p&gt;Because each job gets its own fresh token, you can scope each job to exactly the permissions it needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plan job&lt;/strong&gt; → read-only role, branch-scoped trust&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build &amp;amp; push job&lt;/strong&gt; → ECR/ACR push permissions only, branch-scoped trust&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy job&lt;/strong&gt; → deployment permissions, environment-scoped trust with approval gate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One role per responsibility. Minimum permissions per role. The blast radius of any single compromised job is limited to its specific task.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Audit Trail Static Credentials Cannot Provide
&lt;/h2&gt;

&lt;p&gt;Every OIDC credential issuance produces a CloudTrail event with the full GitHub context — repository, branch, workflow run, commit. The &lt;code&gt;role-session-name&lt;/code&gt; encodes the GitHub run ID, so every subsequent API call is traceable to the exact workflow execution.&lt;/p&gt;

&lt;p&gt;With static access keys, you see the IAM user name — shared across all workflows, with no way to distinguish which run triggered each API call.&lt;/p&gt;




&lt;h2&gt;
  
  
  Migration Path
&lt;/h2&gt;

&lt;p&gt;The sequence that eliminates risk:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the IAM role / Azure federated credential with correct trust policy&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;id-token: write&lt;/code&gt; permission to the workflow job&lt;/li&gt;
&lt;li&gt;Add OIDC credential step — leave static credential commented but present&lt;/li&gt;
&lt;li&gt;Verify end-to-end on a branch&lt;/li&gt;
&lt;li&gt;Remove static credential step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delete the credential from the cloud provider&lt;/strong&gt; — not just from GitHub secrets&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 6 is what most teams defer indefinitely. The credential still exists and could be used by anyone with direct knowledge of the key.&lt;/p&gt;




&lt;h2&gt;
  
  
  Read the Full Article
&lt;/h2&gt;

&lt;p&gt;This is a condensed version. The full article includes complete, production-ready implementations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://aloknecessary.in/blogs/github-actions-oidc/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_syndication&amp;amp;utm_content=github-actions-oidc" rel="noopener noreferrer"&gt;GitHub Actions OIDC: Eliminating Long-Lived Credentials — Full Article&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The full article includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete AWS setup (OIDC provider + IAM role with Terraform)&lt;/li&gt;
&lt;li&gt;Complete Azure setup (Entra ID app registration + federated credentials)&lt;/li&gt;
&lt;li&gt;Full workflow files for AWS ECR+ECS and Azure ACR+AKS deployments&lt;/li&gt;
&lt;li&gt;Reusable workflow federation with &lt;code&gt;job_workflow_ref&lt;/code&gt; claim customisation&lt;/li&gt;
&lt;li&gt;Common failure modes and debug steps (token expiry, trust policy mismatches)&lt;/li&gt;
&lt;li&gt;Production checklist for retiring static credentials&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>github</category>
      <category>devops</category>
      <category>aws</category>
      <category>security</category>
    </item>
    <item>
      <title>Cloud Security Architecture: From Shared Responsibility to Zero Trust</title>
      <dc:creator>Alok Ranjan Daftuar</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:24:18 +0000</pubDate>
      <link>https://dev.to/aloknecessary/cloud-security-architecture-from-shared-responsibility-to-zero-trust-4jc7</link>
      <guid>https://dev.to/aloknecessary/cloud-security-architecture-from-shared-responsibility-to-zero-trust-4jc7</guid>
      <description>&lt;p&gt;Cloud security incidents that make news are rarely the result of a novel attack technique. They are the result of misconfiguration, over-permissioned identities, and static credentials left in places where attackers have learned to look automatically.&lt;/p&gt;

&lt;p&gt;This post covers the architectural decisions that prevent those incidents — made early, before the first breach, rather than in response to one.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Shared Responsibility Model — What You Actually Own
&lt;/h2&gt;

&lt;p&gt;The rows that never move to the provider regardless of service model: identity and access, application configuration, secrets management, and data encryption. Treating "we use managed Kubernetes" as implying "security is largely handled" is the category error that makes managed services into false comfort.&lt;/p&gt;

&lt;p&gt;Three items teams most commonly miscategorise as provider responsibility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Encryption at rest&lt;/strong&gt; — provider-managed keys mean the provider could theoretically decrypt your data. You own the key management decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network exposure&lt;/strong&gt; — a LoadBalancer service type provisions a public IP by default. Nothing prevents a developer from exposing an internal service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM overprivilege&lt;/strong&gt; — the provider supplies IAM. What role gets attached to which workload is entirely yours.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. IAM Least-Privilege at Scale
&lt;/h2&gt;

&lt;p&gt;IAM posture degrades monotonically — it gets worse over time, never better, unless you actively intervene. Three structural controls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ABAC over RBAC&lt;/strong&gt; — evaluate permissions dynamically based on attributes rather than maintaining named roles that accumulate permissions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permission boundaries&lt;/strong&gt; — set the maximum permissions a role can ever have, regardless of what policies are attached&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SCPs&lt;/strong&gt; — organisation-wide guardrails that cannot be overridden at the account level&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Secrets Management — No Static Credentials
&lt;/h2&gt;

&lt;p&gt;70% of leaked secrets remain active 2 years after exposure. The goal: eliminate long-lived static credentials entirely.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IRSA / Workload Identity&lt;/strong&gt; — pods assume cloud IAM roles without possessing any credential file, via OIDC federation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSI driver mounts&lt;/strong&gt; — secrets mounted as files, refreshed on rotation without pod restart&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The rule:&lt;/strong&gt; no secret should ever appear in a Docker image, Kubernetes manifest, GitHub repository, CI/CD log, or environment variable&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Network Segmentation
&lt;/h2&gt;

&lt;p&gt;Network controls reduce blast radius when an identity is compromised. A well-segmented VPC separates traffic by function and trust level:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public subnets (ALB/NLB only) → Private app subnets → Private data subnets&lt;/li&gt;
&lt;li&gt;VPC endpoints for all cloud services (S3, Secrets Manager, ECR, STS)&lt;/li&gt;
&lt;li&gt;No application or data tier resource has a public IP&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Zero Trust for Kubernetes
&lt;/h2&gt;

&lt;p&gt;Kubernetes ships open-by-default. Three gaps to close:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default-deny NetworkPolicy&lt;/strong&gt; in every namespace, with explicit allow policies for required communication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pod Security Admission&lt;/strong&gt; — &lt;code&gt;baseline&lt;/code&gt; enforced on application namespaces, &lt;code&gt;restricted&lt;/code&gt; warned&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One ServiceAccount per workload&lt;/strong&gt; — &lt;code&gt;automountServiceAccountToken: false&lt;/code&gt; at SA level, opt-in per pod&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. CI/CD Pipeline Security
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OIDC authentication&lt;/strong&gt; — no long-lived credentials stored in GitHub secrets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image signing at build, verification at admission&lt;/strong&gt; — unsigned images rejected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vulnerability scanning blocking&lt;/strong&gt; — high/critical CVEs fail the build&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret scanning on push&lt;/strong&gt; — any secret in source control treated as compromised&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Detection — What to Alert On
&lt;/h2&gt;

&lt;p&gt;High-signal events: root account login, IAM policy changes in production, security group rules allowing &lt;code&gt;0.0.0.0/0&lt;/code&gt;, CloudTrail disabled, &lt;code&gt;kubectl exec&lt;/code&gt; into production pods, secrets accessed from unexpected roles.&lt;/p&gt;

&lt;p&gt;Enable GuardDuty / Defender for Cloud before you need them — post-incident enablement loses the historical baseline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Read the Full Article
&lt;/h2&gt;

&lt;p&gt;This is a summary of the fifth and final post in the Cloud Architecture series. The full article includes detailed IAM policy examples, IRSA/Workload Identity configuration, NetworkPolicy manifests, Pod Security Admission labels, GitHub Actions OIDC setup, and a comprehensive production-readiness checklist:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://aloknecessary.in/blogs/cloud-security-architecture/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_syndication&amp;amp;utm_content=cloud-security-architecture" rel="noopener noreferrer"&gt;Cloud Security Architecture: From Shared Responsibility to Zero Trust — Full Article&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The full article includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shared responsibility matrix across IaaS/CaaS/PaaS&lt;/li&gt;
&lt;li&gt;Permission boundary and SCP JSON examples&lt;/li&gt;
&lt;li&gt;IRSA and Workload Identity ServiceAccount configuration&lt;/li&gt;
&lt;li&gt;Secrets Manager CSI driver SecretProviderClass manifest&lt;/li&gt;
&lt;li&gt;Default-deny NetworkPolicy with explicit allow rules&lt;/li&gt;
&lt;li&gt;Pod Security Admission namespace labels&lt;/li&gt;
&lt;li&gt;GitHub Actions OIDC workflow with scoped trust policy&lt;/li&gt;
&lt;li&gt;Complete cloud security architecture checklist (25+ items)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloud</category>
      <category>architecture</category>
      <category>devops</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Embedding Model Selection for Production: The Decision Nobody Documents</title>
      <dc:creator>Alok Ranjan Daftuar</dc:creator>
      <pubDate>Tue, 28 Jul 2026 08:16:17 +0000</pubDate>
      <link>https://dev.to/aloknecessary/embedding-model-selection-for-production-the-decision-nobody-documents-2o73</link>
      <guid>https://dev.to/aloknecessary/embedding-model-selection-for-production-the-decision-nobody-documents-2o73</guid>
      <description>&lt;p&gt;Every RAG architecture diagram has a box labeled "embed." Almost nobody documents how that box's contents got chosen, and almost everybody regrets the choice within eighteen months. Changing embedding models means re-embedding your entire corpus — vectors from different models are not compatible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Decision Matters
&lt;/h2&gt;

&lt;p&gt;Retrieval quality is bounded by embedding quality. A perfectly tuned chunking strategy, re-ranker, and hybrid pipeline cannot retrieve a document whose embedding never placed it near the query in vector space. And the cost of getting this wrong is not a quick fix — it's a migration project.&lt;/p&gt;




&lt;h2&gt;
  
  
  What MTEB Tells You (and Doesn't)
&lt;/h2&gt;

&lt;p&gt;MTEB is a reasonable first filter — it aggregates performance across retrieval, classification, and clustering tasks. What it does NOT tell you: how a model performs on &lt;em&gt;your&lt;/em&gt; documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practical rule:&lt;/strong&gt; Use MTEB to build a shortlist of 3–5 candidates. Never to make the final decision.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benchmark on Your Own Corpus
&lt;/h2&gt;

&lt;p&gt;The only benchmark that predicts production retrieval quality is one run against your own documents and representative queries. Build a golden set of 80–100+ query-to-relevant-document pairs, run each candidate through the same pipeline, measure Recall@K and MRR.&lt;/p&gt;

&lt;p&gt;The gap between MTEB rank and corpus-specific performance is frequently large enough to flip a recommendation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dimensionality and Matryoshka Trade-offs
&lt;/h2&gt;

&lt;p&gt;Matryoshka Representation Learning (supported by most 2026 providers) lets you truncate a 3072-dim vector to 512 or 256 dimensions post-hoc without re-running the model. This enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full dimensions for high-precision compliance search&lt;/li&gt;
&lt;li&gt;Truncated dimensions for low-latency autocomplete on the same corpus&lt;/li&gt;
&lt;li&gt;No separate embedding pass required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Benchmark the quality drop at each truncation point before committing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cost Modeling at Scale
&lt;/h2&gt;

&lt;p&gt;Two components that scale differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Indexing cost&lt;/strong&gt; — one-time, proportional to corpus size&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query cost&lt;/strong&gt; — ongoing, proportional to traffic volume&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A model that looks cheap per-token can be expensive at your actual corpus size. Model cost at your real numbers, not the pricing page.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Re-Embedding Migration Problem
&lt;/h2&gt;

&lt;p&gt;Vectors from different models are not interchangeable. Switching providers means re-embedding the entire corpus. The mitigation is architectural:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design the re-indexing pipeline before you need it&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;dual_write&lt;/code&gt; strategy: write both old and new vectors during transition&lt;/li&gt;
&lt;li&gt;Benchmark new collection against golden set before atomic cutover&lt;/li&gt;
&lt;li&gt;Prefer self-hosted models when deprecation risk is the primary concern&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When Fine-Tuned Embeddings Beat General-Purpose
&lt;/h2&gt;

&lt;p&gt;Fine-tuning reliably improves retrieval by 10–30% for genuinely specialized domains. But only move to fine-tuning if your corpus benchmark shows a real, sustained gap — not a hypothetical concern.&lt;/p&gt;

&lt;p&gt;Check whether a domain-specific model already exists (e.g., code-specific variants) before investing in custom fine-tuning.&lt;/p&gt;




&lt;h2&gt;
  
  
  Read the Full Article
&lt;/h2&gt;

&lt;p&gt;This is a summary of the seventh post in the RAG and AI Engineering series. The full article includes complete benchmarking code, cost modeling functions, the re-indexing migration architecture, decision matrix, and production checklist:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://aloknecessary.in/blogs/embedding-model-selection/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_syndication&amp;amp;utm_content=embedding-model-selection" rel="noopener noreferrer"&gt;Embedding Model Selection for Production — Full Article&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The full article includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python benchmarking code for corpus-specific Recall@K and MRR evaluation&lt;/li&gt;
&lt;li&gt;Matryoshka truncation implementation with re-normalization&lt;/li&gt;
&lt;li&gt;Cost modeling function at actual corpus and traffic scale&lt;/li&gt;
&lt;li&gt;Re-embedding migration architecture with dual-write cutover strategy&lt;/li&gt;
&lt;li&gt;Decision matrix by situation (multilingual, code, regulated, budget-constrained)&lt;/li&gt;
&lt;li&gt;Complete embedding selection checklist for production readiness&lt;/li&gt;
&lt;li&gt;When fine-tuned domain embeddings are justified vs premature&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>embeddings</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Cloud Cost Architecture: Engineering FinOps Into the System, Not Onto It</title>
      <dc:creator>Alok Ranjan Daftuar</dc:creator>
      <pubDate>Mon, 20 Jul 2026 09:30:40 +0000</pubDate>
      <link>https://dev.to/aloknecessary/cloud-cost-architecture-engineering-finops-into-the-system-not-onto-it-5362</link>
      <guid>https://dev.to/aloknecessary/cloud-cost-architecture-engineering-finops-into-the-system-not-onto-it-5362</guid>
      <description>&lt;p&gt;Cost is an architectural concern, not a finance concern. The decisions that determine your cloud bill are made in pull requests touching Terraform files and Kubernetes manifests — weeks before the invoice arrives. By the time finance highlights the line items, the spend has already happened.&lt;/p&gt;

&lt;p&gt;Cloud waste consumes 30–50% of cloud budgets. The bulk is not accidental extravagance — it is the accumulated result of architectural decisions made without cost visibility at the time they were made.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. FinOps Maturity — Where You Actually Are
&lt;/h2&gt;

&lt;p&gt;Most organisations overestimate their maturity by one stage. The diagnostic: can you tell, within five minutes, which team or service generated a specific line item on last month's bill? If not, you're in Crawl regardless of how sophisticated your dashboard looks.&lt;/p&gt;

&lt;p&gt;Most organisations see 15–20% waste reduction from showback alone — just making costs visible changes behaviour.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Commitment Tiers as Architecture Decisions
&lt;/h2&gt;

&lt;p&gt;The commitment model constrains the operational assumptions your workload can make:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On-Demand&lt;/strong&gt; — unpredictable burst, new workloads not yet baselined&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Savings Plans&lt;/strong&gt; — 20–66% discount, flexible across instance types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reserved Instances&lt;/strong&gt; — 40–72% discount, locked to specific instance family&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spot/Preemptible&lt;/strong&gt; — up to 90% discount, two-minute eviction notice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule: baseline on on-demand for 2–4 weeks before committing. Savings Plans before Reserved Instances for flexibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Cost Allocation Tagging
&lt;/h2&gt;

&lt;p&gt;Only 22% of companies have allocated 75%+ of their cloud costs. The gap is almost always a tagging gap.&lt;/p&gt;

&lt;p&gt;Four mandatory tags enforced at provisioning time: &lt;code&gt;team&lt;/code&gt;, &lt;code&gt;environment&lt;/code&gt;, &lt;code&gt;service&lt;/code&gt;, &lt;code&gt;cost-centre&lt;/code&gt;. Resources without them are rejected at creation — not documented for later.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Showback Before Chargeback
&lt;/h2&gt;

&lt;p&gt;Chargeback requires teams to trust the attribution model. That trust requires correct tags, understood allocation logic, and fair shared-cost treatment. None of that exists at the Crawl stage.&lt;/p&gt;

&lt;p&gt;Introduce showback first, run it for a full quarter, fix attribution disputes, then move to chargeback.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Kubernetes Cost Attribution
&lt;/h2&gt;

&lt;p&gt;When 50 services share a node pool, standard billing reports are useless. OpenCost (CNCF) and Kubecost provide per-pod and per-namespace cost breakdowns based on actual utilisation relative to node cost.&lt;/p&gt;

&lt;p&gt;ResourceQuotas are the Kubernetes-native cost governance primitive — apply one to every tenant namespace.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Pipeline Cost Gates
&lt;/h2&gt;

&lt;p&gt;The highest-leverage FinOps capability: a cost gate in CI/CD that shows projected cost impact before merge. Infracost analyses Terraform plans and returns a monthly dollar diff in the pull request.&lt;/p&gt;

&lt;p&gt;If the projected increase exceeds a threshold, the check fails and the PR cannot merge without explicit override.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Guardrails That Block, Not Just Alert
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instance type restrictions&lt;/strong&gt; — SCPs/Azure Policy restrict GPU and large families in non-production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idle resource cleanup&lt;/strong&gt; — unattached volumes, orphaned IPs detected and remediated by policy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dev environment cost caps&lt;/strong&gt; — CronJobs scale non-production to zero outside business hours&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. Structural Wastes to Eliminate First
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Egress cost from co-located-on-prem services now crossing AZs&lt;/li&gt;
&lt;li&gt;Overprovisioned node pools with untuned autoscaler scale-down&lt;/li&gt;
&lt;li&gt;Cross-region data transfer not modelled before architecture decisions&lt;/li&gt;
&lt;li&gt;Unused reserved capacity below 70% utilisation&lt;/li&gt;
&lt;li&gt;Storage in standard tiers that should be in lifecycle-managed cold storage&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Read the Full Article
&lt;/h2&gt;

&lt;p&gt;This is a summary of the fourth post in the Cloud Architecture series. The full article includes Infracost GitHub Actions workflow, Azure Policy JSON for tag enforcement, Kubernetes ResourceQuota and CronJob manifests, commitment tier decision matrix, and a comprehensive cost architecture checklist:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://aloknecessary.in/blogs/cloud-cost-architecture/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=blog_syndication&amp;amp;utm_content=cloud-cost-architecture" rel="noopener noreferrer"&gt;Cloud Cost Architecture: Engineering FinOps Into the System, Not Onto It — Full Article&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The full article includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FinOps Crawl/Walk/Run maturity assessment with next actions per stage&lt;/li&gt;
&lt;li&gt;Commitment tier decision matrix with discount ranges and risk profiles&lt;/li&gt;
&lt;li&gt;Azure Policy JSON for mandatory tag enforcement at provisioning&lt;/li&gt;
&lt;li&gt;Kubernetes ResourceQuota manifest for namespace cost governance&lt;/li&gt;
&lt;li&gt;Infracost GitHub Actions workflow with threshold-based cost gate&lt;/li&gt;
&lt;li&gt;CronJob manifest for non-production scale-to-zero outside business hours&lt;/li&gt;
&lt;li&gt;Structural waste audit across egress, node pools, data transfer, reservations, and storage&lt;/li&gt;
&lt;li&gt;Complete cloud cost architecture checklist (15 items)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloud</category>
      <category>architecture</category>
      <category>finops</category>
      <category>devops</category>
    </item>
    <item>
      <title>I Almost Lost an Entire Blog with git reset --hard (And Git Saved Me)</title>
      <dc:creator>Alok Ranjan Daftuar</dc:creator>
      <pubDate>Wed, 15 Jul 2026 11:24:26 +0000</pubDate>
      <link>https://dev.to/aloknecessary/i-almost-lost-an-entire-blog-with-git-reset-hard-and-git-saved-me-do6</link>
      <guid>https://dev.to/aloknecessary/i-almost-lost-an-entire-blog-with-git-reset-hard-and-git-saved-me-do6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;What started as a routine cleanup became a lesson in Git's resilience — and a reminder that understanding the model matters more than memorizing commands.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Moment Everything Disappeared
&lt;/h2&gt;

&lt;p&gt;I had a feature branch with a freshly committed blog post. I was tidying up my local repository — something I'd done a hundred times before. Then, almost on autopilot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout blogs/microservices-by-def
git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; 65515962bc35fe08514f0b1dcad58cb89773bd2d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The terminal didn't flinch. No warning. No confirmation prompt.&lt;/p&gt;

&lt;p&gt;My article was gone. Hours of writing — vanished in under a second.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Happened
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:   A ── B ── C ── D  ← my blog commit
After:    A ── B ── C  ← pointer moved here (D still exists, orphaned)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git didn't &lt;em&gt;delete&lt;/em&gt; my commit. It just moved the branch pointer. The commit was still there — floating, unreachable, but alive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mental Model That Changes Everything
&lt;/h2&gt;

&lt;p&gt;Git doesn't store files. It stores &lt;strong&gt;snapshots&lt;/strong&gt;. A branch is just a pointer. &lt;code&gt;git reset&lt;/code&gt; relocates that pointer — it doesn't destroy history.&lt;/p&gt;

&lt;p&gt;The commit persists until garbage collection prunes unreachable objects (which doesn't happen immediately).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Recovery: &lt;code&gt;git reflog&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reflog show blogs/microservices-by-def
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6551596 reset: moving to 65515962bc35...
0434e98 commit: added blog on Microservices by Default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recovery took one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; 0434e98
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything came back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Reset Modes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Working Tree&lt;/th&gt;
&lt;th&gt;Index&lt;/th&gt;
&lt;th&gt;HEAD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git reset --soft&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git reset --mixed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git reset --hard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;--hard&lt;/code&gt; rewrites all three areas. That's why my files disappeared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Decision Matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;I want to...&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Undo a local commit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git reset&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Undo a pushed commit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git revert&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recover lost work&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git reflog&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Save work temporarily&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git stash&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restore a single file&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git restore&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clean up commit history&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git rebase -i&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Commit early, commit often.&lt;/strong&gt; Small commits are cheap insurance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Push important milestones.&lt;/strong&gt; Remote refs survive local disasters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learn &lt;code&gt;reflog&lt;/code&gt; before you need it.&lt;/strong&gt; In a panic, you won't have time to read docs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never panic after a bad Git command.&lt;/strong&gt; Most mistakes are recoverable.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;The full post covers merge vs rebase, interactive rebase, reword, cherry-pick, stash, &lt;code&gt;git fsck&lt;/code&gt;, and why linear history matters for CI/CD.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://aloknecessary.in/blogs/i-almost-lost-an-entire-blog-with-git-reset-hard/?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=git-reset-hard" rel="noopener noreferrer"&gt;Read the complete guide on my blog&lt;/a&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>programming</category>
      <category>productivity</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
