One container costs $19 a month whether anyone’s using it. A fleet of Lambda functions costs nothing when nobody is. Picking which job belongs in which is the difference between a $125 bill and a $1,250 one.
Which jobs should I pay-per-use for, and which should I pay rent on?
Both products in this series run on a hybrid of two AWS runtimes. The customer-facing web app lives in an ECS Fargate container — always on, fixed cost, ready to serve a page in under 200 milliseconds. The backup engine lives in a fleet of Lambda functions — billed only when work actually happens.
That mix isn’t an accident, and it isn’t a hedge. It’s the answer to one business question: which jobs should I pay-per-use for, and which should I pay rent on?
The two shapes of compute
Containers are rent. You pay 24 hours a day for reserved capacity, whether or not anyone is using it. The Fargate container running the web app costs $19 a month even at three in the morning when nobody’s logged in. In return, every request hits a process that’s already warm, already connected to the database. Time to first byte is in the tens of milliseconds.
Serverless is pay-per-use. Lambda costs zero dollars when nothing is happening. When a backup fires, Lambda spins up, runs, and bills me for the milliseconds it ran. The trade is a cold start — sometimes hundreds of milliseconds before the function is ready. Doesn’t matter if a backup takes 8 seconds or 8.2. Matters a lot if a customer is waiting.
Neither is “better.” They suit different workloads, and the business answer is usually both.
How I decide
Three questions per workload:
- Predictable or bursty? Steady traffic → container. Spiky, irregular work → serverless.
- Latency-sensitive? If a user is waiting, cold starts ruin the product. Synchronous = container. Background = serverless.
- At expected volume, which is cheaper? Serverless wins when usage is low. Containers win once usage is consistent. There is a crossover, and it matters.
If a workload reads “steady, latency-sensitive, busy enough to keep a container full” — it’s a container job. If it reads “spiky, asynchronous, mostly idle” — it’s a serverless job.
Most products have both.
What this looks like in practice
On containers (always-on, rent):
- The customer-facing web app and the pg-boss workers inside it.
- The admin dashboard and staging environment.
On Lambda (pay-per-use):
- The whole backup engine — 27 functions covering data exports, file backups, schema scans, log archives.
- Scheduled jobs that fire hourly or daily.
- Webhook callbacks from third-party services.
The join point is Supabase. Neither tier knows what runtime the other lives on. That’s the architectural payoff: picking the runtime per workload doesn’t fragment the data model.
What I deliberately don’t do
- No Lambda for the web app. Putting a whole Next.js app on Lambda or edge functions sounds modern, but at sustained traffic you pay more and your customers hit cold starts. Wrong shape for the workload.
- No always-on containers for backup jobs. Most customers run a backup once a day. A container reserving capacity for the moment a backup fires is paying rent on idle 23 hours out of 24.
- No hybrid for the sake of hybrid. If you only have one shape of workload, just use the right one. I run both because I have a steady-traffic product and a bursty back-end.
The 73% bill cut, in business terms
Part 3 mentioned that the Lambda fleet went from $284 a month to $76. That’s only possible because Lambda’s pricing is granular — billed per function, per millisecond, per MB. Measurable at that granularity, you can optimise at it: switch to arm64, right-size memory, stream instead of buffer. The same exercise on a fleet of always-on containers would have saved nothing on idle hours.
The hidden benefit of pay-per-use is that it doesn’t just price the work — it makes the work measurable. And measurable work is improvable work.
Back to the goals
- Coffee-budget infra: I pay for capacity I actually use, and rent only what I genuinely need to keep warm.
- No ops team: both runtimes are managed by AWS. No cluster, no instances to patch.
- Velocity: the Lambda fleet ships independently of the web app. A backup change doesn’t require a web deploy.
The closing principle
Pick the runtime that fits the workload’s shape, not its team’s tribal identity. Serverless-everywhere people will tell you containers are dead. Container-everywhere people will tell you serverless is too magical. Both are wrong about the other half of the problem.
A real product usually has both. Most cloud bills are too high because someone picked one shape for both jobs.
That closes the series
Six parts, four business questions, two products, one consistent posture: decide the goals first, refuse the services that don’t earn their line, and use boring runtimes to host what’s left.
It’s not clever. It’s just basic business sense applied to a software stack.