<?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.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>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>opensource</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
