I've walked engineering teams through this choice dozens of times in support escalations and architecture reviews. The serverless question isn't about hype or resume-driven development—it's about matching workload economics to runtime constraints. Most teams pick wrong because they evaluate features instead of actual usage patterns.
Here's the framework I use when a team asks whether to move a service to Lambda, keep it in Docker, or just run it on a VPS.
Decision Point 1: Traffic Shape and Predictability
Serverless shines when requests arrive in bursts separated by silence. An image resize API hit five hundred times after a content publish, then nothing for twenty minutes. A webhook receiver that processes GitHub events or Stripe callbacks. Batch jobs triggered by S3 uploads.
The math changes fast. If your service handles fewer than ten requests per minute on average but occasionally spikes to hundreds, serverless saves you from paying for idle capacity. A $5 VPS sits there burning money whether it's serving zero requests or five hundred.
Containers on ECS or Kubernetes make sense for steady, predictable load. When you're serving four thousand requests per hour around the clock, a long-running process beats the invocation overhead. The break-even usually lands somewhere between continuous baseline traffic and spiky workloads—run the numbers for your own request volume.
Traditional hosting (a VPS or dedicated box) works when you've got baseline load that would keep a single server above 40% utilization most of the day. You're paying for the hardware anyway. Why add orchestration complexity?
Decision Point 2: Execution Time and Startup Tolerance
Serverless functions have hard timeout limits. AWS Lambda caps at fifteen minutes. Google Cloud Functions stops you at nine. If your workload naturally completes in seconds or low single-digit minutes, you're fine. Video transcoding, PDF generation, API aggregation—these fit.
Cold starts hurt when you need sub-100ms response times. The first request to a Lambda function that hasn't run recently pays a startup penalty: runtime initialization, dependency loading, connection pool setup. For a Node.js function with a few dependencies, expect 200-800ms. Python with heavy libraries can hit multiple seconds. Go and Rust start faster but still aren't instant.
I've seen teams burn weeks trying to optimize cold starts when they should have just used a container. If your SLA promises p95 latency under 150ms and you serve user-facing requests, a warm container pool gives you better guarantees. Reserve a few instances with minimum capacity and you've solved the problem.
Long-running jobs need different infrastructure. A data pipeline that runs for forty minutes doesn't belong in Lambda. Use ECS Fargate tasks, Kubernetes Jobs, or a worker server. Batch processing frameworks exist for a reason.
Decision Point 3: State and Connection Management
Serverless functions are stateless by design. Anything you want to persist between invocations goes in external storage: DynamoDB, RDS, S3, ElastiCache. Each function invocation starts fresh. No in-memory cache carries over. No local disk survives past the execution.
This forces good architecture in some cases. You can't hide state in global variables or rely on sticky sessions. But it also makes certain patterns expensive or impossible. If your application depends on a 50MB in-memory cache warmed from a database at startup, Lambda will rebuild that cache on every cold start.
Database connection pooling gets weird. Traditional apps open five or ten connections and reuse them for thousands of requests. Serverless invocations can't share connections across execution contexts. At high concurrency, you might open three hundred simultaneous database connections. RDS has a max_connections limit, and you'll hit it.
The usual fix is RDS Proxy or a connection pooler like PgBouncer. That's another service to run and pay for. Containers let you keep a stable connection pool in memory. A single app server handles hundreds of requests through ten database connections.
WebSocket connections and long-polling don't map to the serverless model. API Gateway supports WebSockets with Lambda, but the architecture is awkward. You're better off with a persistent container or a specialized service.
Decision Point 4: Development and Deployment Velocity
Serverless deployments are fast when your function is a single file or a small package. Zip it, upload it, done. No server to SSH into, no systemd units to reload, no container registry. For small teams moving quickly on microservices, this cuts friction.
The story reverses when you've got a monolith or a complex dependency tree. A 200MB deployment package takes time to upload and unpack. Lambda has a 50MB zipped / 250MB unzipped limit for direct uploads. Larger packages require S3 and container images, which brings you back to container-land anyway.
Local development with serverless requires emulation or mocking. Tools like LocalStack and SAM CLI help, but they're never perfect replicas of the actual runtime. Containers give you identical environments from laptop to production. Build the image once, run it everywhere.
If your team already knows Docker and has CI/CD pipelines for containers, adding serverless is a new deployment path to learn and maintain. If you're starting fresh and building small services, serverless has less operational overhead.
Decision Point 5: Cost Structure and Budget Control
Serverless pricing is pure usage: invocations times execution duration times memory allocation. You pay for exactly what you use, down to the 100ms. This is perfect for low-traffic side projects and bursty workloads. An API that handles two hundred requests a day might cost $0.30 per month on Lambda.
The curve flips at high sustained volume. Once you're executing functions constantly, you're paying per-millisecond instead of per-server. A function running 24/7 at 512MB memory costs more than a reserved instance of equivalent capacity. The break-even depends on your specific usage, but it's usually somewhere between 20-40% sustained utilization.
Hidden costs matter. Data transfer out of Lambda to the internet is billed. NAT Gateway charges hit hard if your functions need internet access from a VPC. CloudWatch Logs ingestion adds up when you're logging verbosely. I've seen teams shocked by a $400 logging bill from Lambda functions that wrote 10GB of debug output per day.
Containers on Fargate or GKE Autopilot give you middle ground: pay for what you use with per-second billing, but at a lower per-unit price than serverless. Traditional VPS hosting offers the lowest per-request cost if you can keep utilization high. A $40/month VPS can serve millions of requests if you're not wasting cycles.
So which one should you pick?
Match the infrastructure to the load pattern. Serverless for unpredictable, spiky, or low-volume workloads where paying for idle time is wasteful. Containers for steady traffic, stateful apps, or teams already invested in Docker. VPS or dedicated servers for high-utilization baseline workloads where you'll use the capacity.
Hybrid is valid. Run your public API on containers with autoscaling, move background jobs to Lambda, keep your database on a dedicated instance. Infrastructure doesn't have to be uniform. I see this pattern often—core services on ECS, event processors on Lambda, scheduled tasks on Fargate.
Start with the simplest thing that works. A $6 VPS running a Flask app can handle a surprising load before you need to optimize. Don't over-engineer on day one.
Quick checks before committing
Can you tolerate cold starts? If your p99 latency budget is tight, serverless will hurt.
How long does a typical request take? Under five minutes favors serverless. Over that, use something else.
Is your traffic predictable? Steady load is cheaper on long-running servers. Spikes favor serverless.
Do you need persistent connections? Databases, caches, and WebSockets fight the serverless model.
What's your team's existing skill set? Adding a new deployment paradigm has learning cost.
When does serverless make the most sense?
Event-driven workloads where an external trigger kicks off processing. S3 uploads, queue messages, HTTP webhooks, scheduled tasks. If the code runs in response to something and then stops, serverless fits naturally.
What about vendor lock-in?
It's real. Lambda functions use AWS-specific APIs for triggers, permissions, and integrations. Moving to Google Cloud Functions or Azure Functions requires rewriting glue code. Containers are more portable—Docker images run anywhere. If multi-cloud is a requirement, serverless creates friction.
Can I run existing apps on serverless without changes?
Sometimes. Web frameworks like Express, Flask, and FastAPI can run on Lambda with adapter layers (AWS Lambda Web Adapter, Mangum, Serverless Express). But you'll hit limitations around timeouts, file system access, and background tasks. It's easier to extract the logic into purpose-built functions than to shoehorn a monolith.
How do I estimate serverless costs before migrating?
Profile your current request volume, average execution time, and memory usage. Plug those into the Lambda pricing calculator. Add 30% for overhead and cold starts. Compare against your current server costs including operational time. If the serverless number is higher and your load is steady, you'll probably lose money.
What's the biggest mistake teams make with serverless?
Treating it like a general-purpose compute platform. Serverless is a specialized tool for specific workload shapes. Teams that migrate everything to Lambda without evaluating fit end up with slow, expensive systems that would have been better on containers.
What to evaluate first
Look at your actual traffic graphs over the past month. Is the pattern spiky or flat? Measure your p95 and p99 latencies under load. Check your current compute utilization—if you're running servers at 15% average CPU, you're wasting money on idle capacity. If you're consistently above 60%, long-running processes will be cheaper.
Run a cost projection with real numbers before you commit. And remember that operational simplicity has value too—if serverless eliminates a class of infrastructure problems your team currently handles, that's worth something even if the AWS bill is slightly higher.
