AWS Builder Center

Serverless Wasn't Always the Answer

Why I Moved from ECS Fargate Back to EC2 + Nginx + Docker Compose

When I first designed the infrastructure, I picked the "newest, most-recommended" architecture I could find. Then, a few days before demo day, I tore it down myself. This is the story of that decision.

What I Was Building (Identity)

I'm a Computer Science student at Dongduk Women's University, and I owned the backend and infrastructure for Proovy, a personal AI tutor for STEM students. I learned something this project taught me the hard way: writing the code is far easier than shipping something you can actually operate.
That's also how I fell into cloud computing. At first I thought, "Don't you just upload it to a server?" But once I actually ran a service in production, I realized that something that runs and something that's sustainable are two completely different problems.

1. The First Architecture: Serverless Containers

The initial setup looked like this:
1
CloudFront → ALB → ECS Fargate
My reasons for choosing a serverless container architecture on ECS Fargate were clear:
  • Less server management — no need to patch and maintain EC2 instances myself
  • Clean per-service scaling — scale at the container level
On paper, it was about as close to the "model answer" as you can get. So I went with it without a second thought.

2. Looking at It Through an Operations Lens, the Cracks Showed (Community & Learning)

Once I analyzed our demo environment and the conditions of an early-stage service, two problems surfaced.

Problem #1: For an always-on API server, serverless is actually expensive

Fargate doesn't bill per request — it bills by task runtime × vCPU/memory. Which means:
  • Even with zero traffic, you keep paying as long as the API server is alive.
  • Running a 24/7 API creates a fixed cost that has nothing to do with traffic.
The whole serverless promise — "pay only for what you use" — didn't fit the nature of our service, which is an API server that has to stay on all the time.
💡 I'd learned this lesson more expensively on another project (SomShare, a study-archive sharing service). Back then I treated infra setup as an afterthought, got billed $60 in a single day, and learned — through the invoice — that infrastructure being sustainable matters more than infrastructure merely running. On Proovy, I didn't want to repeat that mistake.

Problem #2: On demo day, "the ability to intervene" is stability

Two things scared me most about demo day:
  • Sudden traffic spikes
  • The unstable network conditions of a live venue
Fargate's high level of abstraction made it hard to see inside and intervene when something went wrong. Normally that abstraction is a feature — but in a "the demo has to work, on stage, right now" moment, I decided that an architecture I could put my hands directly into was the safer bet.

3. The Architecture I Switched To (Implementation)

So I moved to this:
1
CloudFront → ALB → EC2 (Auto Scaling Group) → Nginx → Docker Compose
LayerRoleWhy I changed it
CloudFrontStatic caching / edgeKept as-is
ALBLoad balancingKept as-is
EC2 (ASG)ComputePredictable fixed cost for an always-on API, and I can intervene directly
NginxReverse proxyI control routing, timeouts, etc. myself
Docker ComposeContainer orchestrationSimple multi-container management inside a single instance
The key point: I didn't abandon serverless — I re-chose the level of abstraction that fit this service's operating conditions. Fargate wasn't wrong. It just wasn't the right fit for a demo-stage service that has to be always on, needs hands-on intervention, and demands predictable cost.

4. What I Learned (Lessons Learned)

  1. "Newest architecture" ≠ "right answer for my service." Match the billing model (per-request vs. per-runtime) against your traffic pattern (always-on vs. bursty) before you commit.
  2. Abstraction is a double-edged sword. Higher abstraction is more convenient, but it shrinks your room to intervene when things break. Choose based on "when will I need to get in there myself?"
  3. Cost is decided at design time. Don't wait for the invoice to regret it. (This one was genuinely a $60 lesson.)

What's Next (Passion & Purpose)

I'm now an AWS Cloud Club Captain, and I run sessions where I draw out network and infrastructure architectures with newer members. The one thing this project burned into me: a "good architecture" isn't the prettiest diagram — it's what you get when you honestly confront your service's operating conditions.
If I were advising a peer designing their first infrastructure, I'd say:
Don't start by picking the coolest structure. First answer three questions: "When does this server need to be on? Who sends traffic, and how? When will I need to step in?" The architecture follows from those answers.
In my next article, I'll break down — in the same way — how I solved the cost and load problems of large-file downloads on SomShare using S3 + Presigned URLs and public/private subnet isolation.

Thanks for reading. If you're weighing a similar migration, drop your service's conditions in the comments — let's reason through it together.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article