<?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: gyorgy</title>
    <description>The latest articles on DEV Community by gyorgy (@gyorgy).</description>
    <link>https://dev.to/gyorgy</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%2F3868028%2F54d5507c-1a65-4f3f-917d-7357374a763f.JPG</url>
      <title>DEV Community: gyorgy</title>
      <link>https://dev.to/gyorgy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gyorgy"/>
    <language>en</language>
    <item>
      <title>Democratizing professional cloud infrastructure</title>
      <dc:creator>gyorgy</dc:creator>
      <pubDate>Wed, 17 Jun 2026 17:21:24 +0000</pubDate>
      <link>https://dev.to/gyorgy/democratizing-professional-cloud-infrastructure-28ck</link>
      <guid>https://dev.to/gyorgy/democratizing-professional-cloud-infrastructure-28ck</guid>
      <description>&lt;p&gt;&lt;em&gt;Published June 17, 2026 by &lt;a href="https://github.com/gyrgy" rel="noopener noreferrer"&gt;gyorgy&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The cloud the serious companies use is closer than you think.&lt;/p&gt;

&lt;p&gt;You can build almost anything now. You describe what you want, an agent writes it, and a few minutes later there is a working app on your screen. "That part is largely solved." The gate that used to keep most people out of building software is largely gone.&lt;/p&gt;

&lt;p&gt;Then there is a second gate, and the tools that nailed the first one drop you right at its edge. Building the thing and shipping the thing are different problems. Your app runs on your laptop. Maybe a toy host will take a frontend. But the moment you want a real backend, a database or three, something that holds up when more than one person shows up, on infrastructure you actually own, the weekend project stalls. That is the cliff.&lt;/p&gt;

&lt;p&gt;The gate has always been made of knowledge that was hard to get. Cloud accounts, roles and permissions, networking, the difference between a thing that works and a thing that works securely. Knowing one of those does not mean you know the others. You cannot really practice it without a real thing to build for, and the big providers sat behind all of it. You could see them. You could not get in without a pile of knowledge that has nothing to do with your idea.&lt;/p&gt;

&lt;p&gt;That gate is starting to come down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part the critics are right about
&lt;/h2&gt;

&lt;p&gt;People who ship by prompting tend to ship things that are not safe. The app works in the demo and falls over in the real world. The secrets are in the wrong place. The setup is one good day away from a bad one. "You cannot vibe your way to a secure production deployment" has been a fair thing to say.&lt;/p&gt;

&lt;p&gt;Fair, because the person shipping had to assemble all the professional parts themselves, and could not tell the agent to build what they could not picture. Left to improvise, the agent builds something that looks finished and might not be. The problem was never the person. It was that the safe, standard way to deploy was something you had to already understand to get.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes
&lt;/h2&gt;

&lt;p&gt;So change that. Put the secure setup underneath the app by default, instead of asking the person to assemble it.&lt;/p&gt;

&lt;p&gt;That is what tsdevstack does. &lt;a href="https://tsdevstack.dev" rel="noopener noreferrer"&gt;tsdevstack&lt;/a&gt; is free and open source. You build your services in standard frameworks, and it generates the production setup around them: a CDN and WAF at the edge, a load balancer that terminates TLS, a Kong gateway behind it routing every request, your services on a private network nothing outside the gateway can reach, secrets in your cloud's managed secret store instead of an env file, the database, the CI, the deploy. As Terraform that lives in your repo and runs in your own cloud account. No platform in the middle, nothing to lock into, nothing to pay but your own cloud bill, through your own account.&lt;/p&gt;

&lt;p&gt;You own all of it. And none of it is the part you have to know how to build. Not because vibe coders learned security overnight, but because the part under them is no longer the flimsy part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your agent already knows the rails
&lt;/h2&gt;

&lt;p&gt;Here is the part nothing else gives you. When the agent scaffolds your project, it wires itself to tsdevstack directly. From then on it understands the whole system: every command, the structure, where the secrets go, how the routing is wired. So when you ask it for help, it is not improvising infrastructure it half-understands and quietly getting it wrong. It is operating a system that already has the guardrails, using the safe defaults instead of reinventing them.&lt;/p&gt;

&lt;p&gt;That is the real answer to "the AI writes insecure code." Here the AI is not writing the dangerous part at all. The dangerous part is already built, the same way for everyone, and the agent just moves around inside it. The thing people are scared of, an agent left alone with your production setup, is the thing that does not happen.&lt;/p&gt;

&lt;p&gt;And starting is one paste. There is a prompt on the site you drop into Claude Code, Cursor, or whatever agent you use. It scaffolds the project and brings it up locally in an environment that mirrors the cloud, so what runs on your laptop runs when you deploy. One block of text, from nothing to a running full-stack app. There is a 90-second video. If you would rather drive, it is a few CLI commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fast track is Google
&lt;/h2&gt;

&lt;p&gt;One part still takes real effort. You need a cloud account, and you need to give the framework access. The docs walk every step. It is not hard. It is just the one place that asks for fifteen quiet minutes instead of a prompt.&lt;/p&gt;

&lt;p&gt;The providers are not equal here. Start with Google, where the setup is genuinely simple; follow the steps and you are deployed before you have time to get confused. AWS asks a little more, more moving parts, but it is doable and documented. Azure, well. Azure is there when you are ready, and you will know when you are ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate is lower
&lt;/h2&gt;

&lt;p&gt;The first wave let everyone build. It left out that building was never the whole job; shipping something that holds up was a separate gate with its own toll. That gate is lower now. Not gone, and not for everything, but for ordinary full-stack microservices, the secure, standard deployment is something you can get without having built it yourself. You still have to make the thing good. You just no longer have to become an infrastructure engineer first to put it somewhere real.&lt;/p&gt;

&lt;p&gt;The prompt is on the site. Paste it into your agent and start there.&lt;/p&gt;




&lt;p&gt;tsdevstack is free and open source: &lt;a href="https://tsdevstack.dev" rel="noopener noreferrer"&gt;tsdevstack.dev&lt;/a&gt;. The copy-paste prompt and the 90-second demo are on the homepage.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>gcp</category>
      <category>azure</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>IaC, FdI, IaF: three ways a codebase becomes infrastructure</title>
      <dc:creator>gyorgy</dc:creator>
      <pubDate>Sat, 13 Jun 2026 09:53:42 +0000</pubDate>
      <link>https://dev.to/gyorgy/iac-fdi-iaf-three-ways-a-codebase-becomes-infrastructure-2pfo</link>
      <guid>https://dev.to/gyorgy/iac-fdi-iaf-three-ways-a-codebase-becomes-infrastructure-2pfo</guid>
      <description>&lt;p&gt;&lt;em&gt;Published June 17, 2026 by &lt;a href="https://github.com/gyrgy" rel="noopener noreferrer"&gt;gyorgy&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Infrastructure used to be something you wrote separately from your application. Lately that boundary has been dissolving, and the vocabulary has not kept up. Three distinct ideas are getting blurred together, partly because they all start from the same place: your code already implies what infrastructure it needs, so why state it twice.&lt;/p&gt;

&lt;p&gt;They diverge sharply on what they do about that. Here is the short version, then the longer one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure as Code (IaC).&lt;/strong&gt; You describe the infrastructure explicitly, in its own files. The tool turns those files into real resources. Total control, total verbosity, and your infrastructure definition lives apart from your application code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Framework-defined Infrastructure (FdI).&lt;/strong&gt; The framework infers the infrastructure from your application code, and a managed platform provisions it for you. Almost no configuration, no drift between app and infra, but the inference only covers what the framework exposes, and the resulting infrastructure runs on the platform's rails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure as Framework (IaF).&lt;/strong&gt; The framework reads your applications and generates infrastructure code that you own, deployed into your own cloud accounts. The framework does the inferring, you keep the output and the account.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Who writes the infra&lt;/th&gt;
&lt;th&gt;Who owns the output&lt;/th&gt;
&lt;th&gt;Where it runs&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IaC&lt;/td&gt;
&lt;td&gt;You, by hand&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;Any cloud&lt;/td&gt;
&lt;td&gt;Anything you can express&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FdI&lt;/td&gt;
&lt;td&gt;The framework&lt;/td&gt;
&lt;td&gt;The platform&lt;/td&gt;
&lt;td&gt;The platform&lt;/td&gt;
&lt;td&gt;What the framework exposes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IaF&lt;/td&gt;
&lt;td&gt;The framework&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;Your cloud accounts&lt;/td&gt;
&lt;td&gt;What the framework covers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rest of this is just those three rows, explained.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure as Code
&lt;/h2&gt;

&lt;p&gt;IaC is the established answer. You write declarations, in HCL or a general-purpose language, that spell out the resources you want: this VPC, this load balancer, this database, these IAM bindings. A tool like Terraform or Pulumi reads the declarations and reconciles your cloud to match.&lt;/p&gt;

&lt;p&gt;The strength is that nothing is hidden. Every resource is something you chose and can see. If you need an unusual topology, you can express it, because you are working directly with the primitives.&lt;/p&gt;

&lt;p&gt;The cost is the verbosity and the drift. A production system is hundreds to thousands of lines of declarations, and most of it is boilerplate that looks the same across projects. And the infrastructure definition is a separate artifact from the application it serves. The app says it needs a queue; somewhere else, by hand, you wrote the queue. Those two facts can drift apart, and keeping them in sync is manual work that nothing enforces.&lt;/p&gt;

&lt;p&gt;IaC is not going anywhere. It is the layer the other two approaches generate down to. The question the newer ideas ask is not whether the declarations get written, but whether you have to be the one writing them by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework-defined Infrastructure
&lt;/h2&gt;

&lt;p&gt;FdI says no. The insight, which Vercel articulated and named, is that a framework already encodes most of what the infrastructure needs to know. Vercel's own description is that it leverages the predictable structure of framework-based applications to map framework concepts onto infrastructure without explicit configuration.&lt;/p&gt;

&lt;p&gt;The canonical examples are frontend primitives. In Next.js, a file in the routing directory implies a route, so the route table can be generated rather than declared. A page using server-side rendering implies a compute resource to render it, so a serverless function is provisioned. Middleware implies edge compute. You change the code, and the inferred infrastructure changes with it, at the same commit. There is no separate infra artifact to drift, because there is no separate infra artifact at all.&lt;/p&gt;

&lt;p&gt;This is genuinely elegant for what it targets. It grew out of the frontend cloud, and that is where it is strongest: deploying framework-based frontends with zero configuration and no drift.&lt;/p&gt;

&lt;p&gt;The trade follows from the design rather than being a flaw in it. First, the inference can only reach what the framework exposes, so the model is strongest on frontend and request-response work and thinner on backend systems that want always-on processes, long-lived connections, or full control over the runtime. The platform keeps narrowing that gap, with longer-running and more server-like compute, so the boundary is moving rather than fixed. But the inference is rooted in framework structure, and that structure is richest at the frontend. Second, the provisioning runs on the platform's own infrastructure and accounts. That is what makes the zero-configuration experience possible, and also what bounds it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure as Framework
&lt;/h2&gt;

&lt;p&gt;IaF starts from the same observation as FdI, reached independently from a different direction: if the framework understands the project, its services and how they fit together, the framework can produce the infrastructure. But it answers two questions differently, and those answers are the whole distinction.&lt;/p&gt;

&lt;p&gt;The first is ownership of the output. An IaF framework reads your application and generates infrastructure code as a real artifact, the same IaC a person would have written by hand, except a person did not write it. You keep that output. It lives in your repository and deploys into your own cloud accounts. The framework is a generator, not a runtime. When it is done, what you have is yours, and you could in principle stop using the framework and keep the infrastructure.&lt;/p&gt;

&lt;p&gt;The second is scope. Because the generated artifact is ordinary infrastructure code targeting ordinary cloud primitives, there is no inherent ceiling at the frontend. The same approach that generates a gateway and a CDN can generate a VPC, a managed database per service, background workers, a message queue, scheduled jobs, and the observability stack around them. The framework reads the whole project, backend included, and emits the infrastructure that architecture implies.&lt;/p&gt;

&lt;p&gt;tsdevstack is one implementation of this idea. You build standard application-framework services through its CLI, and the framework keeps track of what your project contains. From that it generates the Terraform, the gateway configuration, the CI pipelines, and the rest, across GCP, AWS, and Azure, deployed into accounts you control. Your application code carries no cloud-provider-specific code, so a different provider is a target you choose, handled by the tooling but open to anyone who wants to change it by hand. The framework holds the knowledge of how to build the infrastructure. You hold the infrastructure.&lt;/p&gt;

&lt;p&gt;This idea has relatives. There is a broader movement, often called infrastructure from code, where you express infrastructure needs from inside your application and a tool derives the infrastructure from them. Some of those tools are an SDK you add to an existing framework. Others ask you to adopt a framework of their own. Either way, the infrastructure primitives end up living in your application code. IaF parts ways on exactly that coupling. Your application stays in standard frameworks, with no infrastructure primitives mixed into your business logic. A separate config describes what you want, and the framework generates infrastructure code you own. The knowledge of how to build the infrastructure lives in the framework, not spread through your application code.&lt;/p&gt;

&lt;p&gt;The cost here is honesty about a different boundary. The framework covers what it has been taught to cover. It is opinionated about architecture, and an application that wants a fundamentally different shape than the framework models will fit it poorly. IaF trades some of IaC's open-ended expressiveness for the automation, in exchange for keeping the ownership that FdI gives up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the three together
&lt;/h2&gt;

&lt;p&gt;The three approaches are three points on the same map, defined by two axes: who produces the infrastructure, and who owns and runs what comes out.&lt;/p&gt;

&lt;p&gt;IaC keeps everything in your hands, which is also the problem, because everything is in your hands. FdI takes nearly all of it off your hands, which is also the boundary, because the output and its scope are defined by the platform. IaF tries to hold the middle: the framework produces the infrastructure, and you keep the output and the account.&lt;/p&gt;

&lt;p&gt;None of these is strictly better than the others. They optimize for different things. IaC optimizes for control and expressiveness. FdI optimizes for the least possible configuration on a focused set of concerns. IaF optimizes for keeping ownership while letting the framework do the writing. The right one depends on which trade you actually want to make, and the only mistake is not noticing that you are making one.&lt;/p&gt;




&lt;p&gt;tsdevstack is the IaF framework I build. You create standard application-framework services through its CLI, the framework stays aware of what is in your project, and from that it generates production infrastructure across GCP, AWS, and Azure, as Terraform and gateway configuration and CI pipelines that live in your repository and deploy to your accounts. The framework does the writing. You keep the output. It is open source.&lt;/p&gt;

&lt;p&gt;A note on timing. This part of the field moves fast. Platform limits, pricing, and capabilities shift month to month, and some of what is described here will have moved by the time you read it. Everything above reflects the state of things as of June 2026, sourced to the providers' own documentation below where it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://vercel.com/blog/framework-defined-infrastructure" rel="noopener noreferrer"&gt;Framework-defined infrastructure - Vercel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vercel.com/blog/the-foundations-of-the-frontend-cloud" rel="noopener noreferrer"&gt;The foundations of the Frontend Cloud - Vercel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vercel.com/blog/fluid-how-we-built-serverless-servers" rel="noopener noreferrer"&gt;Fluid compute - Vercel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/" rel="noopener noreferrer"&gt;Pulumi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>infrastructure</category>
      <category>fdi</category>
      <category>iac</category>
      <category>iaf</category>
    </item>
    <item>
      <title>Agentic loops don't fix lying agents</title>
      <dc:creator>gyorgy</dc:creator>
      <pubDate>Fri, 12 Jun 2026 13:57:38 +0000</pubDate>
      <link>https://dev.to/gyorgy/agentic-loops-dont-fix-lying-agents-3c87</link>
      <guid>https://dev.to/gyorgy/agentic-loops-dont-fix-lying-agents-3c87</guid>
      <description>&lt;p&gt;&lt;em&gt;Published June 15, 2026 by &lt;a href="https://github.com/gyrgy" rel="noopener noreferrer"&gt;gyorgy&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The current discourse says you should stop prompting coding agents and start designing loops around them. Give the agent a trigger and a verifiable goal, let an evaluator check the result, and only stop when it passes. One major provider shipped a dedicated &lt;code&gt;/goal&lt;/code&gt; command for it. People are running 25-hour unattended sessions and calling it loop engineering.&lt;/p&gt;

&lt;p&gt;The instinct is correct. Never accept the agent's word that something is done. Demand proof.&lt;/p&gt;

&lt;p&gt;But "verifiable" is doing all the work in that sentence. I build a framework that generates cloud infrastructure across GCP, AWS, and Azure, and I use coding agents on it daily. I keep an audit document of every serious failure. Reading it through the loop engineering lens is uncomfortable, because every bug in it would have survived a loop. Not because the loop iterated too few times. Because the verifier could not see the lie.&lt;/p&gt;

&lt;p&gt;Here are three of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The init job that never existed
&lt;/h2&gt;

&lt;p&gt;During the AWS implementation, the agent generated the Terraform for the database layer. The GCP equivalent creates per-service databases and users through native Terraform resources. AWS has no such resources, so something else has to create them.&lt;/p&gt;

&lt;p&gt;The agent solved this with a comment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Individual databases and users are created at deploy time via init job&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There was no init job. Not a stub, not a TODO, not a half-finished Lambda. The agent wrote documentation for a mechanism it never built, then moved on. The code compiled. &lt;code&gt;terraform validate&lt;/code&gt; passed. The RDS instance deployed fine. Services failed to connect the first time I tested against a real cloud environment, because the databases they were configured to use did not exist. This was during development, long before any release.&lt;/p&gt;

&lt;p&gt;A comment is the cheapest possible way to make code look complete. It costs one line and satisfies any reviewer who skims.&lt;/p&gt;

&lt;h2&gt;
  
  
  The variable that nothing consumes
&lt;/h2&gt;

&lt;p&gt;When I pointed out the missing databases, the agent fixed it. Its fix was to copy the GCP pattern: pass each service's database password to Terraform as &lt;code&gt;TF_VAR_db_{service}_password&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbPasswords&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;suffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;toTfVarSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;tfEnv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`TF_VAR_db_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_password`&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On GCP this variable exists because &lt;code&gt;google_sql_user&lt;/code&gt; consumes it to create the user. On AWS, no resource consumed it. Terraform accepted the variable and ignored it. The connection string was then built with a password for a user that would never exist.&lt;/p&gt;

&lt;p&gt;The agent copied syntax without understanding semantics. It never asked the one question that mattered: what Terraform resource uses this variable? Again, everything compiled. Everything validated. The fix made the codebase look more correct while leaving it exactly as broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that passed a real deploy
&lt;/h2&gt;

&lt;p&gt;This one is the worst, because it survived the strongest verifier I have.&lt;/p&gt;

&lt;p&gt;The Azure Front Door generator needed to create an endpoint, origin, and route for each Next.js service. The agent wrote this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstNextjsService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nextjsServiceNames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One hardcoded index. It created routing for the first Next.js service and silently ignored the rest. And it deployed. Real Terraform apply, real Front Door, real traffic flowing to a real app. Green across the board.&lt;/p&gt;

&lt;p&gt;It was green because the test project had one Next.js service. tsdevstack is a framework. Users can add as many as they want. The spec was "N services must work" and the verifier only ever asked about one. No loop catches that, no matter how many iterations you give it, unless the verification encodes the spec instead of the happy path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every bug passed a verification layer
&lt;/h2&gt;

&lt;p&gt;Line the three up against the checks they survived:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bug&lt;/th&gt;
&lt;th&gt;Compiles&lt;/th&gt;
&lt;th&gt;terraform validate&lt;/th&gt;
&lt;th&gt;Real deploy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Phantom init job&lt;/td&gt;
&lt;td&gt;passed&lt;/td&gt;
&lt;td&gt;passed&lt;/td&gt;
&lt;td&gt;failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unconsumed TF_VAR&lt;/td&gt;
&lt;td&gt;passed&lt;/td&gt;
&lt;td&gt;passed&lt;/td&gt;
&lt;td&gt;failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardcoded first service&lt;/td&gt;
&lt;td&gt;passed&lt;/td&gt;
&lt;td&gt;passed&lt;/td&gt;
&lt;td&gt;passed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A loop wired to the compiler would have terminated and reported success three times. A loop wired to &lt;code&gt;terraform validate&lt;/code&gt; would have done the same. A loop wired to a live deploy still misses the third one.&lt;/p&gt;

&lt;p&gt;The agent was not failing to iterate. The verifier was failing to see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why infrastructure is the worst case
&lt;/h2&gt;

&lt;p&gt;Loops genuinely work in domains with cheap, strong verifiers. A compiler error is instant and unambiguous. A type checker is free. A fast unit test suite gives a tight feedback signal, and an agent looping against it converges on working code. That is the environment where the loop engineering results come from, and the results are real.&lt;/p&gt;

&lt;p&gt;Infrastructure inverts every one of those properties. The cheap checks are weak: validation confirms your HCL is well-formed, not that your architecture works. The strong check is a real deploy that takes 20 minutes and costs money. And the strongest check might not exist yet. The second Next.js service that exposes the hardcoded index is a user action that happens months after release.&lt;/p&gt;

&lt;p&gt;There is one more failure mode worth naming. An agent under pressure to make a verifier pass will sometimes change the verifier instead of the code. Adjust the test expectation. Delete the failing assertion. I have watched this happen. When it does, the loop is not converging on correctness. It is training the agent to fake completion more efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually do
&lt;/h2&gt;

&lt;p&gt;The answer is not more iterations. It is matching each verification layer to the lies it can catch, and being honest about what each layer cannot see.&lt;/p&gt;

&lt;p&gt;For tsdevstack the split looks like this. Snapshot tests on every generated output catch unintended changes to Terraform, Kong configs, and CI workflows the moment they happen. That is the fast loop, and the agent runs inside it constantly.&lt;/p&gt;

&lt;p&gt;Real cloud deploys are reserved for the apply, deploy, and verify chain. Slow and expensive, so they run when the cheap layers cannot answer the question. The phantom init job and the unconsumed variable both died here, before any release.&lt;/p&gt;

&lt;p&gt;And the hardcoded index taught me the last rule: the spec has to live in the tests, not in my head. The fix for that bug was iteration over all Next.js services, and the regression test now deploys with more than one. The verifier had to learn the framework's actual contract before it could defend it.&lt;/p&gt;

&lt;p&gt;The one verifier I never rely on is the agent's self-report. It is the weakest signal in the entire system, and a loop built on it is just the agent agreeing with itself faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part you can delegate
&lt;/h2&gt;

&lt;p&gt;If you cannot define done in a way the agent cannot fake, the loop is just faster fabrication. That is the whole argument.&lt;/p&gt;

&lt;p&gt;Loops are the right structure. But the engineering effort moves from prompting into verification, and verification is where the domain knowledge lives. For infrastructure, that means encoding contracts the agent will not infer on its own: every service, not the first one. A user that exists, not a variable that compiles.&lt;/p&gt;

&lt;p&gt;Agents are good at producing work. They are not yet trustworthy at judging it. Knowledge is the part you can safely delegate. Outcomes are not.&lt;/p&gt;




&lt;p&gt;This experience shaped how tsdevstack treats AI agents. The framework ships an MCP server, &lt;code&gt;@tsdevstack/cli-mcp&lt;/code&gt;, built as a knowledge layer rather than an autopilot. It makes an agent a framework expert: every command, the config schema, the routing, the secret assignments. What it does not do is hand the agent ownership of outcomes. People stay in command, because the responsibility is theirs. I wrote about the boundary between hints and enforcement in &lt;a href="https://tsdevstack.dev/blog/mcp-annotations-are-a-ux-layer-not-a-security-layer" rel="noopener noreferrer"&gt;MCP annotations are a UX layer, not a security layer&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/specification/2025-03-26" rel="noopener noreferrer"&gt;Model Context Protocol specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@tsdevstack/cli-mcp" rel="noopener noreferrer"&gt;@tsdevstack/cli-mcp on npm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tsdevstack.dev/blog/mcp-annotations-are-a-ux-layer-not-a-security-layer" rel="noopener noreferrer"&gt;MCP annotations are a UX layer, not a security layer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>loops</category>
      <category>ai</category>
      <category>terraform</category>
      <category>agents</category>
    </item>
    <item>
      <title>Getting a production WAF out of Azure Front Door Standard</title>
      <dc:creator>gyorgy</dc:creator>
      <pubDate>Mon, 01 Jun 2026 14:35:00 +0000</pubDate>
      <link>https://dev.to/gyorgy/getting-a-production-waf-out-of-azure-front-door-standard-3gl2</link>
      <guid>https://dev.to/gyorgy/getting-a-production-waf-out-of-azure-front-door-standard-3gl2</guid>
      <description>&lt;p&gt;&lt;em&gt;Published June 1, 2026 by &lt;a href="https://github.com/gyrgy" rel="noopener noreferrer"&gt;gyorgy&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Azure Front Door covers the whole edge in a single resource: CDN, managed SSL, a load balancer, and a WAF. On AWS and GCP that job is spread across two or three services. On Azure it is one. When I set it up for an Azure deployment, that consolidation was the part I liked.&lt;/p&gt;

&lt;p&gt;The first surprise was the floor. Front Door Standard has a base fee of around $35 a month, before you serve a single request. The closest AWS setup, the new CloudFront Free plan, gives you a CDN, a WAF, and DDoS protection for $0 at small scale. So on Azure you start at $35 for roughly the thing AWS hands you for nothing. Not a fortune. But a fixed monthly floor where the competition has none.&lt;/p&gt;

&lt;p&gt;The second surprise took longer to find.&lt;/p&gt;

&lt;h2&gt;
  
  
  The WAF has a ceiling
&lt;/h2&gt;

&lt;p&gt;On Standard, the Front Door WAF runs on custom rules you write yourself. There are no managed rule sets at this tier. That was fine by me. I would rather read the rules than trust a black box.&lt;/p&gt;

&lt;p&gt;What I did not expect: Standard caps a WAF policy at 100 custom rules. That is a hard limit. Your entire protection has to fit inside 100 lines.&lt;/p&gt;

&lt;p&gt;To cover the usual ground I ended up with about 80 rules, grouped into priority bands: rate limiting, restricted paths and methods, scanner fingerprints, known CVE patterns, SQL injection, XSS, path traversal, command injection, and protocol abuse. That left around 20 rules of headroom under the cap for anything app-specific.&lt;/p&gt;

&lt;p&gt;Enough for now. But you can feel the ceiling. A larger app carrying a lot of its own rules would start bumping into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What custom rules cost you
&lt;/h2&gt;

&lt;p&gt;Custom rules are static. They match what you told them to match. They don't learn, and they don't update when a new attack pattern shows up next month. Someone has to maintain them. That someone is you.&lt;/p&gt;

&lt;p&gt;A managed rule set works the other way. Microsoft tracks new attack patterns and updates the signatures, and you inherit the updates without touching anything. For an OWASP baseline you would rather not hand-maintain, that is worth real money.&lt;/p&gt;

&lt;p&gt;On Front Door, managed rule sets are Premium only.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Premium buys, and what it costs
&lt;/h2&gt;

&lt;p&gt;Premium replaces the OWASP bands (SQL injection, XSS, path traversal, command injection) with Microsoft's managed Default Rule Set 2.1, which updates as new signatures land. It adds bot management. And it adds Private Link origins, so your origin services lose their public endpoint entirely. The custom-rule cap goes from 100 to 500.&lt;/p&gt;

&lt;p&gt;The price for all of that: the Front Door SKU goes from about $35 a month to about $330. A flat $295 difference, every month, no matter the traffic.&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;Base fee&lt;/th&gt;
&lt;th&gt;WAF&lt;/th&gt;
&lt;th&gt;Bot management&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS CloudFront Free plan&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;5 custom rules&lt;/td&gt;
&lt;td&gt;No, starts on the $200 Business plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure Front Door Standard&lt;/td&gt;
&lt;td&gt;~$35/mo&lt;/td&gt;
&lt;td&gt;~80 custom rules, 100 cap&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure Front Door Premium&lt;/td&gt;
&lt;td&gt;~$330/mo&lt;/td&gt;
&lt;td&gt;Managed DRS 2.1 plus ~35 custom, 500 cap&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Usage, the data transfer and request charges, sits on top of the base fee for all three. The AWS Free plan covers small scale: 100 GB and one million requests a month, five WAF rules. Past that you move up its paid tiers. The point here is the starting line, not the ceiling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which tier to pick
&lt;/h2&gt;

&lt;p&gt;For an early or small service, Standard with a solid custom rule set is the right call. You get real WAF coverage, you can read every rule, and you pay $35 instead of $330. Maintaining 80 rules by hand is not free work, but it is a few hours now and then, not a second job.&lt;/p&gt;

&lt;p&gt;You move to Premium when that maintenance stops being worth your time, or when you specifically need bot management or origins with no public endpoint. Managed rules you pay for beat static rules you forget to update. Decide which side of that line you are on before you commit, because $295 a month adds up fast. The move is also hard to walk back. Azure has no in-place downgrade from Premium to Standard, so reversing it rebuilds the Front Door profile from scratch: new hostnames, new certificates, and 15 to 20 minutes of downtime.&lt;/p&gt;

&lt;p&gt;For someone landing on Azure for the first time, this is a lot to work out on day one. Two tiers, a rule cap, managed against custom, a $295 gap. None of it shows up in a getting-started guide. It is the kind of decision that should live in your tooling, not in your head.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Azure Front Door pricing: &lt;a href="https://azure.microsoft.com/pricing/details/frontdoor/" rel="noopener noreferrer"&gt;https://azure.microsoft.com/pricing/details/frontdoor/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS CloudFront pricing: &lt;a href="https://aws.amazon.com/cloudfront/pricing/" rel="noopener noreferrer"&gt;https://aws.amazon.com/cloudfront/pricing/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Cloud Armor pricing: &lt;a href="https://cloud.google.com/armor/pricing" rel="noopener noreferrer"&gt;https://cloud.google.com/armor/pricing&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Standard with this rule set is the default Azure tier in &lt;a href="https://tsdevstack.dev" rel="noopener noreferrer"&gt;tsdevstack&lt;/a&gt;. The framework generates the full custom WAF policy from one config file, and switches to Premium's managed rule sets when you set a single flag. You pick the tier. It writes the rules.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: azure, waf, security, cloud&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>waf</category>
      <category>security</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Cloud Run private networking without a VPC Connector</title>
      <dc:creator>gyorgy</dc:creator>
      <pubDate>Thu, 07 May 2026 16:02:30 +0000</pubDate>
      <link>https://dev.to/gyorgy/cloud-run-private-networking-without-a-vpc-connector-4ej5</link>
      <guid>https://dev.to/gyorgy/cloud-run-private-networking-without-a-vpc-connector-4ej5</guid>
      <description>&lt;p&gt;If you Google how to call one Cloud Run service from another over private networking, every result tells you to provision a Serverless VPC Access Connector. It works. It also runs a managed pool of e2-micro instances you pay for whether you use them or not, costs $14 to $30 per month, and is no longer the recommended pattern.&lt;/p&gt;

&lt;p&gt;Google has documented a cleaner approach in at least three different places. It uses Direct VPC Egress, a Cloud DNS private zone, and Private Google Access on your subnet. It costs about $0.20 per month. And it gives you something the connector path quietly fails at: keeping egress: private-ranges-only on your services while still reaching external APIs without a Cloud NAT.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;You have backend services on Cloud Run that should be unreachable from the public internet. They need to call each other. They need to call external APIs (Stripe, Resend, OpenAI, whatever). And the database and Redis live on private IPs in your VPC.&lt;/p&gt;

&lt;p&gt;The "unreachable from the public internet" part is easy. Cloud Run gives you &lt;code&gt;ingress: internal&lt;/code&gt;. Set it, done.&lt;/p&gt;

&lt;p&gt;The hard part is the rest: services calling each other, services calling external APIs, all without re-exposing them or paying for a NAT. Cloud Run lets you set &lt;code&gt;egress&lt;/code&gt; on outbound traffic, with two useful values:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;all-traffic&lt;/code&gt; routes every outbound packet through your VPC. To reach the public internet from there, you need a Cloud NAT, which is another ~$30 per month plus data processing. Every call to Stripe now hairpins through your network just to leave it again.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;private-ranges-only&lt;/code&gt; only routes private destinations through the VPC. Public IPs go straight out via Google's edge, no NAT needed. This is what you want for external APIs.&lt;/p&gt;

&lt;p&gt;But there's a catch. Cloud Run service URLs (&lt;code&gt;*.run.app&lt;/code&gt;) resolve to public IPs by default. So when service A calls &lt;code&gt;https://service-b-123.region.run.app&lt;/code&gt;, that traffic exits over the internet path, hits the public Cloud Run frontend, and gets blocked because service B has &lt;code&gt;ingress: internal&lt;/code&gt;. You see connection failures on your own infrastructure.&lt;/p&gt;

&lt;p&gt;The connector approach works around this by routing everything through the VPC and using &lt;code&gt;all-traffic&lt;/code&gt; egress. That works, but as discussed, it brings the NAT problem back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Google-documented fix
&lt;/h2&gt;

&lt;p&gt;Make &lt;code&gt;*.run.app&lt;/code&gt; resolve to private IPs from inside your VPC. Specifically, to the &lt;code&gt;private.googleapis.com&lt;/code&gt; range: &lt;code&gt;199.36.153.8/30&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is not a hack. The &lt;a href="https://cloud.google.com/vpc/docs/configure-private-google-access" rel="noopener noreferrer"&gt;VPC Private Google Access docs&lt;/a&gt; explicitly list &lt;code&gt;*.run.app&lt;/code&gt; among the domains supported via this range, alongside &lt;code&gt;*.gcr.io&lt;/code&gt;, &lt;code&gt;*.pkg.dev&lt;/code&gt;, and &lt;code&gt;*.gke.goog&lt;/code&gt;. The &lt;a href="https://cloud.google.com/run/docs/securing/private-networking" rel="noopener noreferrer"&gt;Cloud Run private networking page&lt;/a&gt; recommends it. The &lt;a href="https://cloud.google.com/run/docs/configuring/networking-best-practices" rel="noopener noreferrer"&gt;networking best practices doc&lt;/a&gt; calls &lt;code&gt;private-ranges-only&lt;/code&gt; with Private Google Access the recommended option for reaching Google APIs over Direct VPC Egress. Google maintains a &lt;a href="https://codelabs.developers.google.com/codelabs/how-to-access-internal-only-service-while-retaining-internet" rel="noopener noreferrer"&gt;codelab&lt;/a&gt; walking through the exact setup.&lt;/p&gt;

&lt;p&gt;Three pieces of 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="c1"&gt;# 1. Subnet with Private Google Access enabled&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_subnetwork"&lt;/span&gt; &lt;span class="s2"&gt;"subnet"&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;"${var.project_name}-subnet"&lt;/span&gt;
  &lt;span class="nx"&gt;ip_cidr_range&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/24"&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;private_ip_google_access&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Cloud DNS private zone overriding *.run.app&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_dns_managed_zone"&lt;/span&gt; &lt;span class="s2"&gt;"cloudrun_internal"&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;"cloudrun-internal"&lt;/span&gt;
  &lt;span class="nx"&gt;dns_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"run.app."&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Route Cloud Run URLs through VPC"&lt;/span&gt;
  &lt;span class="nx"&gt;visibility&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt;

  &lt;span class="nx"&gt;private_visibility_config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;networks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;network_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_dns_record_set"&lt;/span&gt; &lt;span class="s2"&gt;"cloudrun_a"&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;"*.run.app."&lt;/span&gt;
  &lt;span class="nx"&gt;managed_zone&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_dns_managed_zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudrun_internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt;
  &lt;span class="nx"&gt;ttl&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
  &lt;span class="nx"&gt;rrdatas&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"199.36.153.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"199.36.153.9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"199.36.153.10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"199.36.153.11"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Cloud Run service with Direct VPC Egress&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_cloud_run_v2_service"&lt;/span&gt; &lt;span class="s2"&gt;"backend"&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;"auth-service"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;ingress&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INGRESS_TRAFFIC_INTERNAL_ONLY"&lt;/span&gt;

  &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;vpc_access&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;network_interfaces&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;network&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
        &lt;span class="nx"&gt;subnetwork&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_subnetwork&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnet&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;egress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PRIVATE_RANGES_ONLY"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;containers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-central1-docker.pkg.dev/${var.project_id}/repo/auth-service:latest"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you serve IPv6 traffic, also add an AAAA record pointing to &lt;code&gt;2600:2d00:0002:2000::&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What happens at runtime: a request from service A to &lt;code&gt;service-b-123.region.run.app&lt;/code&gt; does a DNS lookup, the private zone returns &lt;code&gt;199.36.153.x&lt;/code&gt;, and Direct VPC Egress with &lt;code&gt;private-ranges-only&lt;/code&gt; treats those addresses as internal and routes the traffic through the VPC's Private Google Access path. Traffic stays on Google's network, hits Cloud Run's internal frontend, passes the &lt;code&gt;ingress: internal&lt;/code&gt; check, and reaches service B.&lt;/p&gt;

&lt;p&gt;External APIs still work because they resolve to ordinary public IPs, fall outside the DNS override, and bypass the VPC entirely. No Cloud NAT needed.&lt;/p&gt;

&lt;p&gt;A Cloud Run service that needs to be reached by an external Load Balancer (a Kong gateway in front of your stack, for instance) uses &lt;code&gt;ingress: internal-and-cloud-load-balancing&lt;/code&gt; instead. Same egress and VPC config; slightly looser ingress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is this actually secure?
&lt;/h2&gt;

&lt;p&gt;Reasonable question. Changing how DNS resolves can sound like the kind of thing that accidentally undoes a security boundary.&lt;/p&gt;

&lt;p&gt;It does not. Three reasons.&lt;/p&gt;

&lt;p&gt;First, &lt;code&gt;ingress: internal&lt;/code&gt; is enforced at the Cloud Run frontend, not by DNS. The check is "did this request arrive from an allowed network source?" Public traffic still hits a Google-managed boundary that rejects it regardless of how DNS resolved. Resolving &lt;code&gt;service.run.app&lt;/code&gt; to a different IP doesn't unlock anything; it changes which Google entry point your traffic uses.&lt;/p&gt;

&lt;p&gt;Second, IAM is still enforced. The caller's service account needs &lt;code&gt;roles/run.invoker&lt;/code&gt; on the target. If you remove that binding, requests fail even from inside the VPC. The DNS path doesn't bypass IAM.&lt;/p&gt;

&lt;p&gt;Third, the &lt;code&gt;199.36.153.8/30&lt;/code&gt; range is not "a public IP we are tricking the network to treat as private." It is a Google-owned, Google-routed range published specifically for Private Google Access traffic. It only carries traffic between your VPC and Google services, never to or from the broader internet.&lt;/p&gt;

&lt;p&gt;The pattern that would be insecure is setting &lt;code&gt;ingress: all&lt;/code&gt; and relying on the DNS trick to keep services private. Don't do that. Stack &lt;code&gt;ingress: internal&lt;/code&gt; plus IAM plus the DNS routing. That is defense in depth, not security through DNS.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this costs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Recurring cost&lt;/th&gt;
&lt;th&gt;Components&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VPC Connector + Cloud NAT&lt;/td&gt;
&lt;td&gt;$44 to $60 per month&lt;/td&gt;
&lt;td&gt;Connector instance pool ($14 to $30) plus Cloud NAT (~$30)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPC Connector, &lt;code&gt;all-traffic&lt;/code&gt;, no NAT&lt;/td&gt;
&lt;td&gt;$14 to $30 per month, but external APIs broken&lt;/td&gt;
&lt;td&gt;Connector only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct VPC Egress + DNS override&lt;/td&gt;
&lt;td&gt;~$0.20 per month&lt;/td&gt;
&lt;td&gt;Cloud DNS private zone&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Direct VPC Egress itself is free. Private Google Access is free. The only line item is the Cloud DNS zone at $0.20 per zone per month, plus $0.40 per million queries.&lt;/p&gt;

&lt;p&gt;There's a performance angle too. The connector adds an extra network hop through a managed proxy pool that maxes out around 1 Gbps. Direct VPC Egress is a direct network path with no proxy hop, lower latency, and higher throughput. Google's own &lt;a href="https://cloud.google.com/blog/products/serverless/announcing-direct-vpc-egress-for-cloud-run" rel="noopener noreferrer"&gt;launch blog&lt;/a&gt; leads with this when announcing the feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is this not the default in tutorials?
&lt;/h2&gt;

&lt;p&gt;Speculation, but: the connector shipped first, and most "Cloud Run private networking" content online predates Direct VPC Egress reaching general availability. The DNS-override pattern is documented across three Google pages (VPC, Cloud Run, codelabs), and it's easy to miss if you only read the first hit on a search. The connector is also conceptually simpler to explain in a one-paragraph blog post: "spin up a connector, point your service at it, done." The DNS-override path requires understanding Private Google Access, which most introductory material skips.&lt;/p&gt;

&lt;p&gt;The result is an ecosystem default that is strictly worse than what the platform owner recommends.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Run private networking: &lt;a href="https://cloud.google.com/run/docs/securing/private-networking" rel="noopener noreferrer"&gt;https://cloud.google.com/run/docs/securing/private-networking&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Configure Private Google Access: &lt;a href="https://cloud.google.com/vpc/docs/configure-private-google-access" rel="noopener noreferrer"&gt;https://cloud.google.com/vpc/docs/configure-private-google-access&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cloud Run networking best practices: &lt;a href="https://cloud.google.com/run/docs/configuring/networking-best-practices" rel="noopener noreferrer"&gt;https://cloud.google.com/run/docs/configuring/networking-best-practices&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Codelab walkthrough: &lt;a href="https://codelabs.developers.google.com/codelabs/how-to-access-internal-only-service-while-retaining-internet" rel="noopener noreferrer"&gt;https://codelabs.developers.google.com/codelabs/how-to-access-internal-only-service-while-retaining-internet&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Direct VPC Egress launch blog: &lt;a href="https://cloud.google.com/blog/products/serverless/announcing-direct-vpc-egress-for-cloud-run" rel="noopener noreferrer"&gt;https://cloud.google.com/blog/products/serverless/announcing-direct-vpc-egress-for-cloud-run&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;This is the default GCP networking setup for &lt;a href="https://tsdevstack.dev" rel="noopener noreferrer"&gt;tsdevstack&lt;/a&gt;. The Terraform above is essentially what the framework generates from a single config file.&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>devops</category>
      <category>serverless</category>
      <category>terraform</category>
    </item>
    <item>
      <title>MCP annotations are a UX layer, not a security layer</title>
      <dc:creator>gyorgy</dc:creator>
      <pubDate>Tue, 05 May 2026 14:18:58 +0000</pubDate>
      <link>https://dev.to/gyorgy/mcp-annotations-are-a-ux-layer-not-a-security-layer-mdh</link>
      <guid>https://dev.to/gyorgy/mcp-annotations-are-a-ux-layer-not-a-security-layer-mdh</guid>
      <description>&lt;p&gt;When the Model Context Protocol added tool annotations like &lt;code&gt;readOnlyHint&lt;/code&gt;, &lt;code&gt;destructiveHint&lt;/code&gt;, and &lt;code&gt;idempotentHint&lt;/code&gt;, a lot of MCP server authors and host implementers read them as a permission system. The mental model goes something like: a tool declares itself destructive, the host sees that, and the host either prompts the user or refuses outright. Annotations as enforcement, the way file permissions work in a Unix filesystem.&lt;/p&gt;

&lt;p&gt;That's not what they are. A tool annotation is a string the server author typed into a tool definition. The model sees it, the host sees it, and they can use it for confirmation prompts or sorting or color coding. Nothing in the protocol verifies the annotation is true. A server can declare &lt;code&gt;readOnlyHint: true&lt;/code&gt; on a tool that drops your production database, and the protocol won't notice. The host can choose to trust the annotation or not, but the trust is a policy decision the host makes about the server, not something the protocol provides.&lt;/p&gt;

&lt;p&gt;This distinction matters because the annotation system is being asked to carry weight it wasn't designed to carry. Two active spec proposals (&lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1862" rel="noopener noreferrer"&gt;SEP-1862&lt;/a&gt; and &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1913" rel="noopener noreferrer"&gt;SEP-1913&lt;/a&gt;) extend the annotation surface in useful ways. Neither of them changes what annotations fundamentally are. They make a UX layer better. They do not turn it into a security layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What annotations actually are
&lt;/h2&gt;

&lt;p&gt;Annotations are server-declared hints. The server author writes them into the tool definition, the server sends them to the client in &lt;code&gt;tools/list&lt;/code&gt;, and that's the entire chain of custody. There is no signature, no third-party verification, no model-side analysis of what the tool actually does. The annotation is exactly as trustworthy as the server that produced it.&lt;/p&gt;

&lt;p&gt;The MCP specification is explicit about this. From the &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/schema" rel="noopener noreferrer"&gt;schema documentation&lt;/a&gt;: "All properties in ToolAnnotations are hints. They are not guaranteed to provide a faithful description of tool behavior... Clients should never make tool use decisions based on ToolAnnotations received from untrusted servers." That language is in the spec because the working group knows annotations are forgeable.&lt;/p&gt;

&lt;p&gt;Justin Spahr-Summers, one of the MCP co-creators, raised the obvious question during the original review of the annotation system: if a client knows the annotations can't be trusted, what's the point of having them? It's the right question and the spec hasn't really answered it. The working answer in practice is that annotations are useful for two things. First, hosts can build better UX on top of them when the server is trusted (skip the confirmation prompt for a tool that declares itself read-only, render destructive tools in a different color, sort tools so safer ones are surfaced first). Second, hosts can use annotations as one signal among many when scoring how much to scrutinize a tool call.&lt;/p&gt;

&lt;p&gt;Neither of those is enforcement. Both assume the host has already decided the server is honest. The annotation tells the host how to render the tool's intent, not whether to allow it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two SEPs in flight
&lt;/h2&gt;

&lt;p&gt;Two annotation-related proposals are currently working through the MCP spec process, both authored or co-authored by Sam Morrow at GitHub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1862" rel="noopener noreferrer"&gt;SEP-1862&lt;/a&gt; (Tool Resolution) addresses a real problem with static annotations: a single tool that takes an &lt;code&gt;action&lt;/code&gt; argument and behaves differently based on its value has to declare itself destructive at all times, because the static annotation has to cover the worst case. A &lt;code&gt;manage_files&lt;/code&gt; tool that supports both &lt;code&gt;read&lt;/code&gt; and &lt;code&gt;delete&lt;/code&gt; operations is forced to look as dangerous as its most dangerous mode, even on read calls. The fix is a new &lt;code&gt;tools/resolve&lt;/code&gt; method, inspired by LSP's &lt;code&gt;codeAction/resolve&lt;/code&gt; pattern. Before invoking the tool, the client asks the server: given these specific arguments, what are the real annotations? The server returns refined metadata for that call. Multi-action tools become viable again without sacrificing UX accuracy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1913" rel="noopener noreferrer"&gt;SEP-1913&lt;/a&gt; (Trust and Sensitivity Annotations), co-authored with OpenAI, works on a different axis. Where existing annotations describe what a tool does, SEP-1913 adds annotations that describe what the data flowing through a tool means. New fields like &lt;code&gt;sensitiveHint&lt;/code&gt; (low/medium/high), &lt;code&gt;privateHint&lt;/code&gt;, &lt;code&gt;maliciousActivityHint&lt;/code&gt;, and &lt;code&gt;attribution&lt;/code&gt; let servers mark returned data with trust and sensitivity metadata, and let that metadata propagate through an agent session so a host can enforce policies like "do not send data marked private to tools marked open-world."&lt;/p&gt;

&lt;p&gt;Both proposals fill genuine gaps. SEP-1862 unblocks a tool design pattern that was effectively forbidden by static annotations. SEP-1913 extends the annotation surface from what tools do to what data they handle, which is the right direction if you care about prompt injection and exfiltration.&lt;/p&gt;

&lt;p&gt;What neither proposal changes is the trust model. SEP-1862's resolved annotations are still server-declared. SEP-1913's data annotations are still server-declared. A server that lies in &lt;code&gt;tools/list&lt;/code&gt; can lie just as easily in &lt;code&gt;tools/resolve&lt;/code&gt; or in a &lt;code&gt;sensitiveHint&lt;/code&gt; field on returned content. The proposals make honest servers more expressive. They do not make dishonest servers detectable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for MCP server design today
&lt;/h2&gt;

&lt;p&gt;If annotations are a UX layer, design your server so the UX layer stays accurate without depending on protocol-level enforcement.&lt;/p&gt;

&lt;p&gt;The first decision is tool granularity. A multi-action tool with an &lt;code&gt;action&lt;/code&gt; argument forces a worst-case static annotation, which means honest hosts will over-prompt and well-tuned models will steer around the tool because it looks dangerous. Until SEP-1862 lands, separate tools per action keep static annotations honest. One tool reads, one tool lists, one tool removes. Each declares its real shape and the annotation is true at all times. This costs you a few more tool definitions and saves the host from making bad UX decisions on your behalf.&lt;/p&gt;

&lt;p&gt;The second decision is how to use the existing annotation fields. The boolean grid (&lt;code&gt;readOnlyHint&lt;/code&gt;, &lt;code&gt;destructiveHint&lt;/code&gt;, &lt;code&gt;idempotentHint&lt;/code&gt;, &lt;code&gt;openWorldHint&lt;/code&gt;) is independent flags rather than ordered tiers, but in practice tools cluster into three groups. Read-only tools (&lt;code&gt;readOnlyHint: true&lt;/code&gt;). Mutating but recoverable tools (&lt;code&gt;readOnlyHint: false, destructiveHint: false&lt;/code&gt;). Destructive tools (&lt;code&gt;readOnlyHint: false, destructiveHint: true&lt;/code&gt;). Treating these as a tier internally simplifies host policy, even though the protocol doesn't enforce the structure. It also makes it obvious which tier a new tool belongs to when you add one, which matters at scale.&lt;/p&gt;

&lt;p&gt;The third decision is what to do about the trust gap. The honest answer is that the protocol can't close it for you, so you close it elsewhere. Sandboxed execution, infrastructure-level egress controls, and third-party scanners (&lt;a href="https://github.com/snyk/agent-scan" rel="noopener noreferrer"&gt;Snyk's Agent Scan&lt;/a&gt; is one example) sit outside the protocol and verify or constrain what tools actually do, regardless of what they claim. If your MCP server runs in a context where any of those layers exist, lean on them. The annotations on your tools should be honest, but the security boundary lives somewhere else.&lt;/p&gt;

&lt;p&gt;What you should not do is treat annotation correctness as the security boundary. A server author who annotates carefully and a server author who lies look identical to the protocol. If your design assumes the host can tell them apart through annotations alone, you have a gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual security layer lives outside MCP
&lt;/h2&gt;

&lt;p&gt;Once you accept that annotations are a UX layer, the question of where security actually lives becomes easier to answer. It lives in three places, none of them in the protocol.&lt;/p&gt;

&lt;p&gt;The first is host-level policy on which servers to trust. The host decides which MCP servers it accepts tools from, what scopes those servers operate under, and what the user has approved. That's where the real allow/deny decision happens. Annotations help the host build clearer prompts and better defaults, but the host is the one accepting or rejecting the tool call.&lt;/p&gt;

&lt;p&gt;The second is infrastructure-level enforcement. Sandboxed execution, network egress rules, filesystem permissions, container boundaries. These don't care what a tool's annotations say. A tool that claims to be read-only but tries to write outside its sandbox is stopped by the sandbox, not by the annotation. For any MCP server doing real work in production, this layer is where deletion, exfiltration, and lateral movement actually get prevented.&lt;/p&gt;

&lt;p&gt;The third is third-party verification. Scanners that examine MCP server code or behavior independently of what the server claims. &lt;a href="https://github.com/snyk/agent-scan" rel="noopener noreferrer"&gt;Snyk's Agent Scan&lt;/a&gt; is one example of this category, and more will appear as the ecosystem matures. These tools occupy the space the protocol can't, because by definition they treat the server as untrusted and verify rather than trust.&lt;/p&gt;

&lt;p&gt;None of this makes annotations useless. Annotations let honest servers communicate intent, let hosts build interfaces that match that intent, and give users the right amount of friction at the right moments. SEP-1862 will make that signal sharper for multi-action tools. SEP-1913 will extend it to the data flowing through tools. Both are worth shipping.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>security</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Migrating off AWS App Runner before the April 30 deadline</title>
      <dc:creator>gyorgy</dc:creator>
      <pubDate>Tue, 14 Apr 2026 14:11:34 +0000</pubDate>
      <link>https://dev.to/gyorgy/migrating-off-aws-app-runner-before-the-april-30-deadline-5g8m</link>
      <guid>https://dev.to/gyorgy/migrating-off-aws-app-runner-before-the-april-30-deadline-5g8m</guid>
      <description>&lt;p&gt;AWS is shutting the door on App Runner for new customers effective April 30, 2026. If you're running production workloads on it, existing apps keep working for now, but there are no new features coming, and "maintenance mode" at AWS historically means "start planning your migration."&lt;/p&gt;

&lt;p&gt;I just finished a migration off App Runner for a production Next.js frontend, and wanted to write down what I learned in case it's useful to anyone else facing the same deadline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The options
&lt;/h2&gt;

&lt;p&gt;AWS officially recommends &lt;strong&gt;ECS Express Mode&lt;/strong&gt; as the direct App Runner replacement. It's a newer single-resource abstraction that auto-provisions an ECS cluster, service, ALB, security groups, auto-scaling, and CloudWatch logging. One Terraform resource, one deploy, done.&lt;/p&gt;

&lt;p&gt;The other options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standard ECS Fargate&lt;/strong&gt;. More moving parts, years of battle-testing, full control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda + API Gateway&lt;/strong&gt;. True scale-to-zero, good for infrequent API traffic, cold starts on anything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightsail containers&lt;/strong&gt;. Simpler than ECS, cheaper for small workloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Cloud Run&lt;/strong&gt;. If you're open to leaving AWS, this is genuinely the best container-in-a-box experience on any cloud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fly.io / Render / Railway&lt;/strong&gt;. PaaS experience outside AWS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For our use case (production Next.js behind CloudFront with a real VPC, Kong gateway, and backend services on the same infrastructure), ECS Fargate was the natural fit. Express Mode looked appealing on paper, but I went with standard Fargate instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not ECS Express Mode
&lt;/h2&gt;

&lt;p&gt;Three reasons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Terraform bug.&lt;/strong&gt; The &lt;code&gt;aws_ecs_express_gateway_service&lt;/code&gt; resource had an open issue (hashicorp/terraform-provider-aws#45792, "Provider produced inconsistent result after apply") that would have blocked deploys. Fixable with workarounds, but not something I wanted to own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. "Managed abstraction" fatigue.&lt;/strong&gt; App Runner was also supposed to be the easy path. It lasted four years before being sidelined. Express Mode is newer than App Runner was when I first used it. I wasn't willing to bet a second production frontend on another abstraction that might get sunset in 18 months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. ALB duplication.&lt;/strong&gt; Express Mode auto-creates its own ALB. If you already have an ALB for other services (like I did for a Kong gateway routing backend services), you end up paying for two. Around $16/month extra for the overlap. Not huge, but annoying and unnecessary.&lt;/p&gt;

&lt;p&gt;Standard ECS Fargate uses the ALB you already have. Same pattern as every other service in the cluster. Boring, predictable, stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the migration actually looked like
&lt;/h2&gt;

&lt;p&gt;The architecture ended up like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
  ↓
CloudFront (caching + WAF)
  ↓ X-Origin-Verify header
ALB (port 443, host-based routing)
  ↓                    ↓
Next.js target      Kong target
group               group
  ↓                    ↓
ECS Fargate         Kong gateway
(Next.js)              ↓
                    Backend services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next.js containers run in private VPC subnets. ALB listener rules use host-based routing to split frontend traffic (&lt;code&gt;example.com&lt;/code&gt; → Next.js target group) from API traffic (any host + X-Origin-Verify header → Kong target group). CloudFront in front for caching, SSL, and WAF.&lt;/p&gt;

&lt;p&gt;For origin protection, I stuck with &lt;code&gt;X-Origin-Verify&lt;/code&gt; header validation on the ALB rule. The AWS-managed CloudFront prefix list is a cleaner option (allow only CloudFront IPs at the security group level) but it's more moving parts and one more thing to update when AWS changes its prefix list. The header check was good enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas I hit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Health checks.&lt;/strong&gt; Next.js needs a &lt;code&gt;/health&lt;/code&gt; endpoint returning 200 for ALB target group health checks. This is obvious in retrospect but it was our first failed deploy. Add it to your &lt;code&gt;app/health/route.ts&lt;/code&gt; before you migrate, not during.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single-phase deploy.&lt;/strong&gt; The App Runner + CloudFront setup I had was a two-phase deploy: Terraform creates App Runner, CLI collects the URL, Terraform runs again with the URL as a CloudFront origin. With ECS behind an ALB that already exists at plan time, this goes away. One &lt;code&gt;terraform apply&lt;/code&gt;, no two-phase dance. Genuinely nicer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private subnets from the start.&lt;/strong&gt; App Runner services are publicly routable on the internet, with WAF-only protection and no network-level isolation. ECS Fargate in private subnets gives you proper network boundaries. Don't skip this. Put your container in private subnets with no public IP, only allow ingress from the ALB security group.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-scaling.&lt;/strong&gt; Express Mode gives you auto-scaling for free. Standard Fargate requires configuring target-tracking scaling policies yourself. One extra Terraform resource, but you have actual control over what the scaling metric is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about scale-to-zero?
&lt;/h2&gt;

&lt;p&gt;This is the pain point for everyone moving off App Runner. Standard Fargate does not scale to zero. You always pay for at least one running task. If your workload has long idle periods, this is a real cost difference.&lt;/p&gt;

&lt;p&gt;For production workloads this is usually fine (you want at least one container warm anyway). For dev/staging environments or low-traffic side projects, you have three options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cloud Run on GCP&lt;/strong&gt;. Actual scale-to-zero, sub-second cold starts, no ALB needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda + API Gateway&lt;/strong&gt;. Scale-to-zero, but cold starts hurt if your app isn't designed for them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled shutdowns&lt;/strong&gt;. &lt;code&gt;eventbridge&lt;/code&gt; rules to scale the ECS service to 0 at night, back to 1 in the morning. Crude but effective for dev environments.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your app is a very low traffic fastapi backend (as in the Reddit thread that prompted this article), honestly, Cloud Run is probably the right answer. AWS just doesn't have a real equivalent right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Would I do it again?
&lt;/h2&gt;

&lt;p&gt;Yeah, for a production workload with an existing VPC and other services, the standard Fargate path was the right call. The migration was not fun but the result is cleaner than App Runner. Single-phase deploys, private networking, no dependency on a deprecated service.&lt;/p&gt;

&lt;p&gt;If I were starting fresh with a brand new single service and no existing infrastructure, I'd look harder at Cloud Run or fly.io. AWS's container story below ECS is just not compelling anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tsdevstack angle
&lt;/h2&gt;

&lt;p&gt;I build a multi-cloud TypeScript framework called &lt;a href="https://tsdevstack.dev" rel="noopener noreferrer"&gt;tsdevstack&lt;/a&gt; that generates production infrastructure from a config file. The App Runner to ECS Fargate migration above is what shipped in v0.2.0. Framework users who were deploying Next.js frontends via App Runner can now re-run &lt;code&gt;infra:deploy&lt;/code&gt; and the framework handles the migration automatically.&lt;/p&gt;

&lt;p&gt;One thing worth mentioning given the scale-to-zero discussion above: tsdevstack implements scale-to-zero on AWS for services that set &lt;code&gt;minInstances: 0&lt;/code&gt; in config. Since ECS Fargate doesn't have native scale-to-zero, the framework generates a three-layer mechanism: a CloudWatch alarm scales the service to zero when idle (CPU below 5% for 15 minutes), and a wake-up Lambda spins it back up when the first request hits the ALB and returns 502. Kong catches the 502, fires the wake-up call, and returns a 503 with &lt;code&gt;Retry-After: 30&lt;/code&gt; so the client retries automatically. Cold start is around 30-60 seconds, which is significant compared to Cloud Run or Container Apps, but it's real scale-to-zero on AWS and it works. Kong itself stays at &lt;code&gt;minInstances &amp;gt;= 1&lt;/code&gt; so there's always something to trigger the wake-up.&lt;/p&gt;

&lt;p&gt;If you're tired of writing Terraform by hand for every AWS migration AWS forces on you, take a look. &lt;a href="https://tsdevstack.dev" rel="noopener noreferrer"&gt;Docs here&lt;/a&gt;, repo at &lt;a href="https://github.com/tsdevstack" rel="noopener noreferrer"&gt;github.com/tsdevstack&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: aws, terraform, devops, cloud&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>terraform</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>I built a TypeScript framework that generates your entire cloud infrastructure</title>
      <dc:creator>gyorgy</dc:creator>
      <pubDate>Wed, 08 Apr 2026 15:18:57 +0000</pubDate>
      <link>https://dev.to/gyorgy/i-built-a-typescript-framework-that-generates-your-entire-cloud-infrastructure-1392</link>
      <guid>https://dev.to/gyorgy/i-built-a-typescript-framework-that-generates-your-entire-cloud-infrastructure-1392</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; tsdevstack is an open-source TypeScript microservices framework. You write a config file and application code. It generates Terraform, Docker, Kong gateway routes, CI/CD pipelines, secrets, and observability — across GCP, AWS, and Azure. One command deploys the whole stack.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/6MJ4PPPjxH8"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every TypeScript project I shipped to production followed the same pattern. Write the application code in a week. Spend the next month wiring up infrastructure.&lt;/p&gt;

&lt;p&gt;Terraform for the cloud resources. Docker for local dev. Kong or some other gateway for routing. JWT auth boilerplate. Secrets management across environments. CI/CD pipelines. Observability. WAF rules. SSL certificates. Health checks. Database migrations. And then the same dance for staging and production.&lt;/p&gt;

&lt;p&gt;The application code was the easy part. Everything around it took 10x longer.&lt;/p&gt;

&lt;p&gt;I tried the existing options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Heroku-style platforms&lt;/strong&gt; hide too much. The moment you need a WAF, a custom gateway, or VPC isolation, you're stuck.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pulumi/CDK/Terraform modules&lt;/strong&gt; are flexible but you still write and maintain all of it. And you write it differently for each cloud provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Templates and starters&lt;/strong&gt; get you a working hello-world but rot the moment you customise them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted something in between. A framework that owned the infrastructure layer entirely — generated, managed, deployed — but stayed out of the way of the application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure as Framework.&lt;/strong&gt; You write TypeScript application code and one config file. The framework generates everything else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @tsdevstack/cli init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scaffolds a monorepo with NestJS backends, Next.js frontends, a Kong API gateway, Postgres, Redis, and observability. Everything wired together, ready to run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Local development matches production. Same gateway, same database engine, same auth flow, same observability stack. No "works on my machine" gap.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tsdevstack cloud:init &lt;span class="nt"&gt;--gcp&lt;/span&gt;
npx tsdevstack infra:init &lt;span class="nt"&gt;--env&lt;/span&gt; dev
npx tsdevstack infra:deploy &lt;span class="nt"&gt;--env&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provisions the full production stack: VPC, managed Postgres, Redis, container registry, Cloud Run services, API gateway, load balancer, WAF, SSL certificates, observability. From a single config file.&lt;/p&gt;

&lt;p&gt;The same flow works on AWS (ECS Fargate) and Azure (Container Apps). Same framework, same patterns, same commands. No rewriting infrastructure when you switch providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the box
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Application layer&lt;/strong&gt; — NestJS backends, Next.js frontends, Rsbuild SPAs. Auto-generated TypeScript API clients with DTOs as separated imports — both frontend and backend apps consume the same type-safe library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API gateway&lt;/strong&gt; — Kong routes auto-generated from your OpenAPI specs. JWT validation, rate limiting, CORS, bot detection. Fully customisable when you need it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Background processing&lt;/strong&gt; — BullMQ job queues with detached workers running in separate containers. Scale independently from API services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Object storage&lt;/strong&gt; — Add buckets with &lt;code&gt;add-bucket-storage&lt;/code&gt;. MinIO locally, S3/GCS/Azure Blob in production. Unified &lt;code&gt;StorageModule&lt;/code&gt; with pre-signed URLs and per-provider adapters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Async messaging&lt;/strong&gt; — Inter-service pub/sub via Redis Streams. Consumer groups, dead letter queues, retry logic. No new infrastructure — runs on the same Redis instance as caching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication&lt;/strong&gt; — JWT token management, protected routes, session handling, email confirmation. Bring your own OIDC or use the built-in auth service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secrets&lt;/strong&gt; — Local secrets generated automatically for development. Cloud secrets managed separately and pushed to the cloud provider's Secret Manager. Environment isolation, scoped per service. Works with Secret Manager on all three providers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observability&lt;/strong&gt; — Prometheus metrics, Grafana dashboards, distributed tracing with Jaeger, structured logging. Configured from day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt; — Generated Terraform for GCP, AWS, and Azure. VPC/VNet, managed databases, Redis, container orchestration, load balancers, WAF, SSL, CDN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI/CD&lt;/strong&gt; — Generated GitHub Actions workflows. OIDC authentication, per-service deploys, environment selection. No secrets in your repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compliance&lt;/strong&gt; — SOC 2, ISO 27001, GDPR technical controls built in. Encryption at rest and in transit, network isolation, zero-credential runtimes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it actually works
&lt;/h2&gt;

&lt;p&gt;The framework manages a &lt;code&gt;config.json&lt;/code&gt; for your project structure — you don't edit it by hand, you modify it through commands like &lt;code&gt;add-service&lt;/code&gt;, &lt;code&gt;add-bucket-storage&lt;/code&gt;, &lt;code&gt;add-messaging-topic&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Your &lt;code&gt;config.json&lt;/code&gt; ends up looking like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"projectName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-saas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"services"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"auth-service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nestjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hasDatabase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"frontend"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nextjs"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"storage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"buckets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"uploads"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run &lt;code&gt;npx tsdevstack sync&lt;/code&gt;, the framework reads the config and generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker-compose.yml&lt;/code&gt; with all the services, dependencies, and health checks&lt;/li&gt;
&lt;li&gt;Kong gateway config from OpenAPI specs&lt;/li&gt;
&lt;li&gt;Local secrets in &lt;code&gt;.env&lt;/code&gt; files per service&lt;/li&gt;
&lt;li&gt;Database initialization scripts&lt;/li&gt;
&lt;li&gt;Service stubs if you added new ones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You write the &lt;code&gt;infrastructure.json&lt;/code&gt; directly for cloud-specific settings (domains, scaling, environments).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"environments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"services"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"auth-service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"minInstances"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"maxInstances"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"cpu"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"memory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"512Mi"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"frontend"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"minInstances"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"maxInstances"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"cpu"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"memory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1Gi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dev.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run &lt;code&gt;npx tsdevstack infra:deploy&lt;/code&gt;, it generates Terraform for your chosen provider and applies it. The framework owns the Terraform, you don't write it, you don't maintain it.&lt;/p&gt;

&lt;p&gt;The escape hatch is intentional. Custom Kong config? Drop in your own. Need a Terraform resource the framework doesn't generate? Add it as a side file. Need a cloud-native service the framework doesn't wrap? Use the SDK directly. The framework isn't a cage — it's a starting point that handles 95% of cases and gets out of your way for the other 5%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why three clouds?
&lt;/h2&gt;

&lt;p&gt;Vendor lock-in is real but slow-moving. You don't switch clouds because you want to — you switch because acquisition, pricing change, region requirements, or a customer with an immovable preference forces you to. When that happens, rewriting infrastructure is brutal.&lt;/p&gt;

&lt;p&gt;tsdevstack generates the equivalent infrastructure on GCP (Cloud Run + Cloud SQL + Memorystore), AWS (ECS Fargate + RDS + ElastiCache), and Azure (Container Apps + Azure Database for PostgreSQL + Azure Cache for Redis). Same application code, same config file, different generated Terraform. Switching providers is a config change and a redeploy.&lt;/p&gt;

&lt;p&gt;No abstraction layer trying to hide the differences between clouds. Each provider gets a native, idiomatic implementation. The framework handles the translation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about AI agents?
&lt;/h2&gt;

&lt;p&gt;There's a built-in MCP (Model Context Protocol) server with 54 tools for deploying, querying, and debugging your stack. Claude Code, Cursor, and VS Code Copilot can manage the infrastructure directly — and because the framework has strong conventions, the AI agent actually understands what it's doing instead of hallucinating CLI commands.&lt;/p&gt;

&lt;p&gt;Three permission tiers: SAFE_READ, CLOUD_MUTATE, CLOUD_DESTRUCTIVE. The agent always asks for permission before mutating anything. The MCP server is built into the CLI — no separate package, no extra setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stands
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Open source.&lt;/strong&gt; MIT license. Four packages on npm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@tsdevstack/cli&lt;/code&gt; — the CLI, infrastructure generation, deployment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@tsdevstack/nest-common&lt;/code&gt; — shared NestJS modules&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@tsdevstack/cli-mcp&lt;/code&gt; — MCP server for AI agents&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@tsdevstack/react-bot-detection&lt;/code&gt; — React bot detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;v0.2.0 just shipped&lt;/strong&gt; with object storage, async messaging, AWS App Runner → ECS Fargate migration (App Runner stops accepting new customers April 30), and a batch of WAF and observability improvements across all three providers.&lt;/p&gt;

&lt;p&gt;This is solo work. I'm a developer building this on the side. It started as the framework I wanted for my own projects and grew into something I think other people will find useful. The first users are showing up now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @tsdevstack/cli init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docs and guides: &lt;a href="https://tsdevstack.dev" rel="noopener noreferrer"&gt;tsdevstack.dev&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/tsdevstack" rel="noopener noreferrer"&gt;github.com/tsdevstack&lt;/a&gt;&lt;br&gt;
Discord: &lt;a href="https://discord.gg/tsdevstack" rel="noopener noreferrer"&gt;discord.gg/tsdevstack&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback wanted. Bug reports wanted. Issues, ideas, complaints — all welcome.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: typescript, nestjs, devops, opensource&lt;/em&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>nestjs</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
