Introduction
Serverless platforms like AWS Lambda promised a developer's dream: pay only for what you use, never manage infrastructure, and scale automatically. For many startups, this worked beautifully—until it didn't. As workloads grow and usage patterns stabilize, the cost-per-request economics that made serverless attractive can flip dramatically. Simultaneously, the operational constraints—cold starts, execution timeouts, and limited debugging tools—start to feel less like trade-offs and more like genuine limitations.
This is where VPS-based containerized deployments enter the picture. Running Docker containers on a virtual private server isn't as trendy as serverless, but for many production applications, it offers better economics, more predictable performance, and significantly less operational complexity than either serverless or traditional bare-metal management.
This article walks through why thousands of bootstrapped teams and growing startups are making this shift, and how to do it without losing the benefits that attracted you to serverless in the first place.
The Hidden Costs of Serverless at Scale
Serverless pricing looks simple on paper: AWS Lambda charges $0.20 per million requests plus $0.0000166667 per GB-second of execution. For a small SaaS product handling 100K requests per month, that's roughly $20 in compute costs. Sounds great.
But serverless costs don't stop at compute:
Data egress fees often blindside teams. If your Lambda function pulls data from S3 or writes logs, AWS charges $0.09 per GB of outbound traffic. A video processing function or data pipeline can easily rack up $200–400 monthly in egress alone, invisible until your bill arrives.
Cold starts eat into your budget silently. Every new container initialization takes 0.5–2 seconds in Node.js or Python, during which you're still charged compute time. For an API handling traffic spikes, this can add 10–15% to your total bill. Keeping containers "warm" with ping functions adds perpetual cost.
Storage and database costs escalate with serverless. DynamoDB's on-demand pricing ($1.25 per million writes, $0.25 per million reads) works for tiny projects but becomes expensive at scale. By comparison, a $15/month PostgreSQL instance on a VPS can handle millions of queries with better tooling and no surprises.
Real example: A team running a moderately-trafficked SaaS API (5M requests/month) reported their actual serverless bill:
- Lambda compute: $100
- CloudWatch Logs: $45
- Data egress: $120
- DynamoDB: $210
- API Gateway: $35
- Total: $510/month
They migrated to a containerized setup on a $60/month VPS (Docker + PostgreSQL) plus a $30/month managed database for redundancy. Same workload, 12x cheaper.
Why Containerized VPS Beats Serverless for Production Apps
Predictable, Linear Pricing
A $40/month VPS handles the same traffic whether you get 100K requests or 10M requests per month. You pay the same whether your functions execute 50 milliseconds or 5 seconds. This isn't just cheaper—it changes how you think about optimization. You're no longer nickeled-and-dimed for every database query; you're incentivized to build efficient systems, not workaround serverless's limitations.
Full Debugging and Observability
Lambda's "logs are magic" approach breaks down fast. You can't SSH into a Lambda, inspect a running process, or attach a debugger. VPS-based containers give you the full Unix toolbox: you can run strace to debug system calls, inspect memory with standard tools, and reproduce production issues locally because your container environment is identical.
Modern Docker containers run the same software stack locally and in production. This eliminates the "works on my machine" class of bugs entirely.
No Architectural Compromises
Serverless functions assume statelessness. Want to keep an in-memory cache across requests? Can't do it reliably—functions are recycled arbitrarily. Need to run a background job that takes 15 minutes? Lambda's 15-minute timeout is a hard limit. Your VPS runs whatever code you write for as long as you need, with standard process management (systemd, supervisor, or a simple Docker Entrypoint).
Complexity That Scales With Your Team, Not Your Bill
Serverless introduces operational complexity masquerading as simplicity. You still need to manage IAM roles, environment variables, deployment pipelines, and the cognitive overhead of serverless-specific patterns. Add a database, a queue, and a cache, and you're managing five different AWS services with five different pricing models and operational quirks.
A containerized VPS keeps everything in one place: your code runs in a container, your database is a standard Postgres instance, your logs go to stdout. This is simpler operationally, easier to onboard new developers into, and easier to debug when things break.
Comparison: Serverless vs. Containerized VPS
| Factor | Serverless (Lambda) | Containerized VPS |
|---|---|---|
| Base cost (small app) | $10–50/month | $20–60/month |
| Cost at 10M req/month | $400–800 | $40–80 |
| Cold starts | 0.5–2s (variable) | None |
| Deployment speed | 30–60 seconds | 5–15 seconds |
| Local debugging | Limited (SAM, local invoke) | Full (standard Docker) |
| Max execution time | 15 minutes | Unlimited |
| State between requests | None (stateless) | Full (stateful possible) |
| Observability tooling | CloudWatch (proprietary) | Standard (ELK, Prometheus, etc.) |
| Scaling complexity | Automatic (hard to predict) | Manual/orchestrated (predictable) |
| Learning curve | Medium (new abstractions) | Low (standard containerization) |
Making the Migration Easier
If your application is already running on serverless, moving to containers doesn't require a rewrite.
Step 1: Containerize your existing code. Write a Dockerfile that runs your existing handler. If you're on Lambda, you likely have a single entrypoint function—wrap it in a simple Flask or Express server:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "-m", "uvicorn", "handler:app", "--host", "0.0.0.0", "--port", "8000"]
Your Lambda handler becomes a standard HTTP service. No architectural changes needed.
Step 2: Choose a VPS provider. For evaluating options and comparing providers, tools like ServerToolPick help cut through the marketing. Look for providers offering:
- Docker pre-installed or easy setup
- Managed backups
- API access for automation
- Simple scaling (adding a second instance should be trivial)
Most DigitalOcean, Linode, and Hetzner droplets run $10–40/month and handle millions of requests.
Step 3: Add a reverse proxy. Use Nginx or Caddy as a reverse proxy in front of your container. This handles SSL/TLS termination, request routing, and lets you run multiple app instances without code changes:
upstream app {
server localhost:8000;
server localhost:8001;
}
server {
listen 80;
location / {
proxy_pass http://app;
}
}
Step 4: Orchestrate with Docker Compose. For teams not ready for Kubernetes, Docker Compose handles the essentials:
version: '3'
services:
app:
build: .
ports:
- "8000:8000"
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
This gives you multi-container coordination with a single docker-compose up. Deploy it once to your VPS; it stays running.
When Serverless Still Makes Sense
This isn't a "serverless is dead" manifesto. Serverless still excels in specific scenarios:
- Infrequent, bursty workloads (batch processing, scheduled jobs). You pay nothing when idle.
- Tiny prototype projects with traffic under 100K requests/month. The VPS base cost isn't worth it.
- Event-driven pipelines (S3 uploads → processing → notification). Serverless shines here because the infrastructure complexity is AWS's problem.
- Strict zero-ops requirements. If your team has no infrastructure experience and can't tolerate any server management, serverless's hands-off nature has value.
Most teams making the shift aren't in these categories. They have predictable traffic, growing complexity, and infrastructure-aware developers.
Conclusion
The serverless-to-VPS migration is increasingly common because the economic and operational case is compelling. When your application stabilizes into predictable traffic patterns, serverless's pay-per-request model becomes a liability, not an asset. Containerization has matured to the point where Docker is the lingua franca of deployment—moving to a VPS doesn't mean abandoning modern practices; it means applying them more directly.
The complexity cost is lower than you'd expect. A Dockerfile, a basic Docker Compose setup, and a standard VPS handle production-grade applications reliably. Your team gains operational visibility (standard Unix tools instead of proprietary dashboards), cost predictability (one bill, not five), and architectural freedom (no timeouts, no cold starts, no stateless shackles).
If you're evaluating hosting options or considering this migration, take time to compare providers and understand the economics. Tools designed to cut through marketing—like those available at comparison sites in your ecosystem—can save weeks of research. Then containerize your app, rent a VPS, and watch your infrastructure bill drop while your operational simplicity climbs.
Top comments (0)