Serverless vs Containers vs Bare Metal: A Cost and Flexibility Map
Compute models for web applications describe how application code is executed and billed. Serverless functions (AWS Lambda, Vercel Functions, Cloudflare Workers) execute code on-demand and bill per invocation and execution time, with no always-running infrastructure to manage. Containers (Docker on ECS, Kubernetes, Railway, Render) package application code with its runtime and run as always-on or auto-scaled processes. Bare metal or VPS hosting runs application code on dedicated or virtual machines where the team manages the operating system and runtime environment.
Written by Yashveer Singh, founder of Yashveer Labs.
What you need to know
- Serverless is cost-effective for spiky or low-baseline traffic. It becomes expensive at consistently high request rates with meaningful execution duration.
- Containers provide predictable cost at consistent traffic levels and are appropriate for stateful connections, continuous workloads, and AI inference.
- Bare metal or VPS is the most cost-efficient for CPU/memory-intensive continuous workloads when the team has DevOps capacity to manage the infrastructure.
- Cold start latency is a real trade-off for serverless. Use provisioned concurrency or containers for latency-sensitive user-facing operations.
- The operational overhead increases from serverless to containers to bare metal. Match the compute model to the team's operational capacity.
The core argument
The compute model decision is inseparable from the team's operational capacity. Serverless provides the lowest operational overhead at the cost of flexibility and potentially higher cost at scale. Bare metal provides the highest flexibility and lowest cost-per-compute-unit at the cost of significant operational overhead. Containers sit in the middle: more flexibility than serverless, less operational overhead than bare metal.
The mistake is choosing a compute model based on performance or cost in isolation without accounting for the operational capacity required to run it. A team of two engineers without a DevOps background running their application on bare metal spends a disproportionate fraction of their time on infrastructure management rather than product work. A team of 15 engineers with a dedicated DevOps function running everything on Lambda at high traffic volumes pays three times the container cost without clear benefit.
The decision framework: start with what the team can operate reliably today, and migrate toward more cost-efficient models as operational capacity grows. For most early-stage SaaS products, a managed container platform (Railway, Render, or Fly.io) provides the right balance: more control than pure serverless, less operational overhead than self-managed Kubernetes or bare metal. Migrate to AWS ECS or self-managed infrastructure when the cost savings justify the operational investment.
Common mistakes
- Choosing serverless for a product that requires persistent connections. WebSocket-based real-time features, long-polling, and streaming responses are difficult or impossible to implement correctly in serverless functions, which have execution time limits and no persistent process state. If the product requires real-time bidirectional communication, containers with a persistent process are the appropriate compute model.
- Not modeling cold start impact on the user experience. Teams choosing serverless for user-facing APIs without analyzing cold start frequency and impact may discover the problem only in production, when users report intermittent slowness. Use AWS X-Ray or Datadog Lambda tracing to measure cold start frequency and duration. If cold starts occur regularly in the user-facing critical path, configure provisioned concurrency or migrate the affected functions to containers.
- Running databases on serverless-scaled containers without connection pooling. A serverless function that opens a new database connection for every invocation can exhaust the database's connection limit at high concurrency. Each Lambda invocation creates a new connection; 100 concurrent invocations create 100 connections. Use PgBouncer, RDS Proxy, or a database with a serverless-compatible connection model (PlanetScale, Neon) when running database-connected code in serverless functions.
- Over-provisioning bare metal for variable workloads. Bare metal servers are always-on; they do not scale down when traffic is low. Provisioning bare metal for peak traffic means paying for peak capacity continuously. Variable workloads (products with daily or weekly traffic cycles) should use auto-scaling containers or serverless, not bare metal. Bare metal is efficient for workloads with consistently high and predictable resource utilization.
- Not accounting for egress costs in the compute model comparison. Cloud provider egress (data transferred out of the cloud) can be a significant cost line, especially for products that return large payloads or serve media. Egress costs are roughly similar across serverless, containers, and VMs on the same cloud provider. Switching to Hetzner reduces compute cost dramatically but does not reduce egress cost for European regions to the same extent.
Where to start
- Categorize workloads by traffic pattern and execution characteristics. List the main workload types in the product: user-facing API requests (traffic pattern, average execution time, memory needs), background jobs (continuous vs triggered, execution duration), and heavy processing tasks (ML inference, document processing, report generation). Each category may warrant a different compute model.
- Compare the monthly cost of the current compute model against alternatives for the primary API workload. Pull the current monthly compute cost per request for the main API. Calculate the equivalent cost on Lambda (invocation cost + execution time cost), on a container platform (hourly or monthly rate divided by expected request capacity), and on a Hetzner VPS (monthly instance cost divided by expected capacity). The comparison reveals whether the current model is cost-optimal at current scale.
- Migrate one low-risk workload to test operational requirements of the target model. Before migrating the main application, migrate one background job or side service. This pilot reveals the operational challenges of the new compute model in a low-risk context and builds the team's experience before the main migration.
Related reading
Frequently asked
Closing note from the author
I keep these closing notes short on purpose. Most engineers writing about this topic are not the engineer you want to hire. I might be. Yashveer Singh, founder of Yashveer Labs. The contact channel is Instagram. The proof is the portfolio. The standard is in the work. If we are aligned, you will know within five minutes of the first message.
Posts that line up with this one.
- Backend, APIs, and System Design
Saga Patterns: Distributed Transactions Without Distributed Pain
Distributed transactions are hard. Sagas make them manageable by breaking long-running operations into compensatable steps. Here is how choreography and orchestration sagas work, when each fits, and what the implementation actually looks like.
- Backend, APIs, and System Design
Idempotency Keys: A Pattern Every Senior Engineer Should Master
Idempotency keys are a small implementation with an outsized impact on system reliability. Here is the pattern, the edge cases, and the production pitfalls that most introductions skip.
- Backend, APIs, and System Design
JSON Columns in Postgres: When They Make Sense
JSON columns in Postgres are genuinely useful for flexible, semi-structured data. They are also frequently misused as a shortcut to avoid schema design. Here is when to use them and when to use normalized tables instead.
- Backend, APIs, and System Design
Kafka in 2026: When You Need It and When You Do Not
Kafka is powerful, but most startups reach for it before they need it. Here is how to decide.