Every major cloud provider offers a free tier. AWS Free Tier, Google Clouds Always Free, Azures 12-month credits, Vercels hobby plan, Supabases starter tier. They are genuinely useful for prototyping and learning. They are also, by design, traps that create lock-in and explode in cost the moment your project gets traction.
I have helped three startups navigate the transition from free-tier architectures to production infrastructure. Each one faced costs they did not anticipate, not because the pricing was hidden, but because the architecture decisions made during the free tier phase created expensive dependencies. This is what I learned.
The Architectural Lock-In Problem
Free tiers do not just give you compute and storage. They give you a specific way of building software that is deeply coupled to the providers ecosystem. Consider a typical “free tier stack” on AWS:
- Lambda (1M free requests/month)
- DynamoDB (25GB free storage)
- API Gateway (1M API calls free for 12 months)
- S3 (5GB free for 12 months)
- CloudWatch (basic monitoring)
This stack is genuinely free for small projects. But every component is an AWS-specific service. Your Lambda functions use AWS SDK calls. Your data model is designed around DynamoDBs key-value patterns. Your API routing is defined in API Gateway configuration. When you need to scale or migrate, you are not moving “an app” — you are rewriting an entire AWS-native application.
The DynamoDB Example
DynamoDBs free tier is generous: 25GB of storage and enough read/write capacity for most prototypes. The problem is that DynamoDB requires you to design your data model around access patterns, not relationships. A schema that works beautifully in DynamoDBs single-table design looks like this:
// DynamoDB single-table design for a SaaS app
{
"PK": "ORG#org_123",
"SK": "USER#user_456",
"GSI1PK": "USER#user_456",
"GSI1SK": "ORG#org_123",
"email": "dev@example.com",
"role": "admin",
"entityType": "OrgMembership"
}
This is not portable. When you need to move to PostgreSQL (because you need joins, transactions, or full-text search), you are not migrating data — you are redesigning your entire data layer. One startup I worked with spent 6 weeks and $45,000 in engineering time migrating from DynamoDB to PostgreSQL. The free tier saved them roughly $200 over the prototyping period.
The Real Cost Breakdown
Here is what actually costs money when you outgrow free tiers, based on three real projects (anonymized):
Project A: SaaS Dashboard (B2B)
| Phase | Monthly Cost | What Happened |
|---|---|---|
| Free tier (months 1-6) | $0 | Lambda + DynamoDB + S3 |
| First paying customers (months 7-9) | $47 | Exceeded free tier limits slightly |
| Growth phase (months 10-14) | $380 | API Gateway costs exploded |
| Migration month | $2,100 | Running both old and new infrastructure |
| Post-migration (VPS + managed DB) | $95 | Hetzner + Supabase Pro |
The API Gateway surprise is common. At $3.50 per million requests, it sounds cheap. But a dashboard with 500 active users making 50 API calls per session, 20 sessions per month, generates 500,000 requests per month — just from normal usage. Add WebSocket connections for real-time updates, and the costs compound fast.
Project B: Content Platform (B2C)
This project started on Vercels free tier with Next.js. The free tier includes generous serverless function invocations and bandwidth. The problem surfaced when they needed:
- Image optimization: Vercel charges per image transformation after the free tier. A content platform with user-uploaded images hit $180/month in image optimization alone.
- Edge functions: Free tier limits on execution time meant moving to Pro ($20/month per team member).
- Analytics: Vercel Analytics is paid. They needed it for business decisions, adding another $50-150/month.
Total monthly cost went from $0 to $420 within three months of launch, for an app serving 12,000 monthly users.
The Egress Tax
Data egress — the cost of moving data out of a cloud provider — is the most insidious hidden cost. AWS charges $0.09/GB for data transfer out to the internet (after the first 100GB/month). This seems negligible until you consider:
- A 2MB API response served 100,000 times = 200GB = $18/month for one endpoint
- Backups to an external service: moving 500GB of database backups out of AWS costs $45/month
- Multi-cloud architectures: data moving between AWS and GCP is charged on both ends
# Calculate your actual egress costs
# This script estimates monthly egress based on your CloudWatch metrics
aws cloudwatch get-metric-statistics --namespace AWS/EC2 --metric-name NetworkOut --start-time $(date -u -d "30 days ago" +%Y-%m-%dT%H:%M:%S) --end-time $(date -u +%Y-%m-%dT%H:%M:%S) --period 2592000 --statistics Sum --output text | awk '{
bytes = $2;
gb = bytes / (1024^3);
cost = (gb > 100) ? (gb - 100) * 0.09 : 0;
printf "Total egress: %.2f GB\nEstimated cost: $%.2f/month\n", gb, cost
}'
Cloudflares zero-egress policy on R2 storage is a direct response to this problem, and it is one reason R2 has gained significant market share against S3 in 2025-2026.
Monitoring and Observability: The Invisible Expense
Free tiers give you basic logging. Production applications need observability. Here is what that actually costs:
- Datadog: $15/host/month for infrastructure, $1.70/million log events. A modest setup with 3 hosts and reasonable logging: $125/month.
- New Relic: Free tier is generous (100GB/month of data ingest), but APM features that you actually need cost $0.30/GB beyond that.
- CloudWatch: Custom metrics are $0.30/metric/month. 50 custom metrics across your services: $15/month just for metrics, plus log storage costs.
The alternative — self-hosted observability with Grafana, Prometheus, and Loki — requires a dedicated machine ($20-40/month) and maintenance time. There is no free lunch in monitoring.
How to Build Free-Tier-Proof Architecture
The goal is not to avoid free tiers. They are excellent for prototyping. The goal is to avoid architectural decisions that create expensive migrations later.
1. Use Portable Data Layers
// Instead of DynamoDB-specific code:
const result = await dynamoDB.query({
TableName: "users",
KeyConditionExpression: "PK = :pk",
ExpressionAttributeValues: { ":pk": "USER#123" }
}).promise();
// Use a portable abstraction (Drizzle ORM example):
const users = await db
.select()
.from(usersTable)
.where(eq(usersTable.id, "123"));
// This works with PostgreSQL, SQLite, MySQL
// Migration cost: change the connection string
2. Containerize from Day One
Even if you deploy to a serverless platform, keep a working Dockerfile. This is your escape hatch.
# Dockerfile that works on any container platform
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]
This container runs on AWS ECS, Google Cloud Run, Fly.io, Railway, a $5 VPS, or your local machine. Zero lock-in.
3. Abstract Provider-Specific Services
// storage.ts - Provider-agnostic storage interface
interface StorageProvider {
upload(key: string, data: Buffer, contentType: string): Promise<string>;
download(key: string): Promise<Buffer>;
delete(key: string): Promise<void>;
getSignedUrl(key: string, expiresIn: number): Promise<string>;
}
// Implementations for S3, R2, local filesystem, MinIO
// Swap providers by changing one environment variable
class S3Storage implements StorageProvider { /* ... */ }
class R2Storage implements StorageProvider { /* ... */ }
class LocalStorage implements StorageProvider { /* ... */ }
4. Calculate the 10x Cost Before Choosing
Before committing to a platform, estimate costs at 10x your current usage. If your prototype handles 1,000 users, price out the infrastructure for 10,000 users. Most pricing calculators make this straightforward:
- AWS Pricing Calculator:
calculator.aws - Google Cloud Pricing Calculator:
cloud.google.com/products/calculator - Infracost (for Terraform users): estimates costs from your IaC definitions
The Providers That Get It Right
Not all free tiers are traps. Some providers have pricing models that scale predictably:
- Hetzner: No free tier, but their entry-level dedicated servers ($4.50/month) outperform many cloud instances at 10x the price. Transparent pricing that does not spike.
- Cloudflare R2: Zero egress fees. You pay for storage and operations, period. No surprises at scale.
- Fly.io: Free tier is small but the pricing curve is smooth. Going from free to $5/month to $50/month is predictable.
- Supabase: PostgreSQL-based, so your data layer is inherently portable. The Pro plan at $25/month covers most small SaaS applications.
The Bottom Line
Free tiers are marketing tools, not architecture recommendations. Use them for what they are — a way to experiment without financial risk. But design your application as if you will need to move it, because you probably will.
The cheapest infrastructure decision is not the one with the lowest sticker price today. It is the one that gives you options tomorrow. A $5/month VPS running a Docker container is often cheaper in total cost of ownership than a “free” serverless architecture that costs $45,000 to migrate away from.
Build portable. Stay flexible. Read the pricing page before the documentation.
