AWS Architecture Patterns for Small Teams
A practical decision framework for choosing the right AWS architecture when your team is small and your budget isn't infinite.
Every time someone asks me the same question: “What AWS setup should we use for our project?” And every time, my answer starts with: “How big is your team?”
Architecture decisions that work for a 50-person engineering org with a dedicated platform team are wildly different from what works for 3 people shipping a product. I’ve seen small teams burn months setting up Kubernetes clusters they didn’t need, and I’ve seen others struggle with Lambda cold starts because someone told them “serverless is the future.”
After building 40+ production systems on AWS, here’s the framework I actually use when advising teams.
Context: What “Small Team” Means Here
Let me be specific about the context. When I say small team, I mean:
- 1-5 backend engineers
- Single cloud provider (AWS)
- 3-8 services (not 50)
- Moderate traffic: hundreds to low thousands of requests per second
- Budget-conscious, typically startup or SMB
If you’re running 30 microservices with a dedicated SRE team, this article isn’t for you. Go read the AWS Well-Architected Framework instead. But if you’re a small team trying to ship product without drowning in infrastructure, keep reading.
Pattern 1: Serverless-First
The architecture: Lambda + API Gateway + DynamoDB (or Aurora Serverless).
This is my default recommendation for greenfield projects where the workload is request-response or event-driven.
When this works great:
- REST/GraphQL APIs with variable traffic
- Webhook receivers
- CRON jobs and scheduled tasks
- Event processing pipelines
- MVPs where you want to validate before investing in infra
When this falls apart:
- WebSocket connections (Lambda has a 15-minute timeout)
- Workloads needing consistent sub-50ms latency (cold starts add 100-500ms)
- High-throughput gRPC services
- Applications with heavy in-memory state
Real cost example: One of my SaaS clients handles 40K API requests/day on this stack. Monthly bill: $73. That includes Lambda, API Gateway, DynamoDB, and CloudWatch. Try getting that price with always-on servers.
The trade-off nobody mentions: DynamoDB’s pricing model is straightforward for simple access patterns, but the moment you need complex queries or joins, you either redesign your data model or add a second database. I’ve seen teams spend more engineering time on DynamoDB single-table design than they would have spent just running PostgreSQL.
Pattern 2: Container-Based (ECS Fargate)
The architecture: ECS Fargate + ALB + RDS PostgreSQL.
This is what I recommend when you need a traditional API server with persistent connections, consistent performance, or you’re running existing containerized code.
When this works great:
- Long-running HTTP/gRPC APIs
- WebSocket servers
- Applications needing consistent response times
- Teams already comfortable with Docker
- Services that need more than 15 minutes of compute per request (batch processing)
When it’s overkill:
- Simple CRUD APIs with low traffic
- Pure event-driven architectures
- Projects in the “validate the idea” phase
Real cost example: A fintech client running 3 Fargate services (0.5 vCPU, 1GB each) with RDS db.t3.medium and a Redis node. Monthly bill: $280. Higher than serverless, but they get sub-20ms response times and WebSocket support.
The setup I always use:
# Minimum production-ready Fargate service
resource "aws_ecs_service" "api" {
name = "api"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.api.arn
desired_count = 2 # Always at least 2 for availability
launch_type = "FARGATE"
network_configuration {
subnets = var.private_subnets
security_groups = [aws_security_group.ecs.id]
assign_public_ip = false
}
load_balancer {
target_group_arn = aws_lb_target_group.api.arn
container_name = "api"
container_port = 8080
}
}
Two tasks minimum gives you availability during deployments. Private subnets with no public IP because your containers don’t need direct internet access (NAT Gateway handles outbound). Everything behind an ALB for health checking and TLS termination.
Pattern 3: Hybrid
The architecture: Fargate for core APIs + Lambda for everything else.
This is what most of my projects end up looking like after 6 months. You start with one pattern, then realize some workloads fit better elsewhere.
The principle: Use containers for your hot path (the thing users wait on) and serverless for everything else (background jobs, event processing, integrations).
Why this works: Your core API gets consistent, fast response times. But you’re not paying for idle containers to handle a webhook that fires 3 times a day or a report that generates weekly.
Real example: An e-commerce client runs their product API on Fargate (fast reads from Redis cache, consistent 15ms responses) but processes order confirmations, sends emails, generates invoices, and syncs inventory all via Lambda functions triggered by SQS. The Lambda functions don’t need to be fast, they need to be reliable and cheap.
The Decision Framework
Here’s how I actually decide:
| Question | Serverless | Container | Hybrid |
|---|---|---|---|
| Traffic pattern? | Bursty, variable | Steady, predictable | Mixed |
| Need WebSockets/gRPC? | No | Yes | Core: yes, rest: no |
| Latency requirement? | >100ms OK | <50ms needed | Varies by endpoint |
| Team Docker experience? | Low | Medium-High | Medium |
| Budget priority? | Minimize fixed cost | Minimize ops time | Balance both |
| Stage? | MVP/early | Growth/stable | Scaling |
If you answered mostly left column: start serverless. If you answered mostly middle: go with containers. If you’re mixed: you want the hybrid approach.
One more factor: If your entire team has never written a Dockerfile and you’re shipping in 2 weeks, don’t introduce containers just because an architecture diagram looks nicer. Lambda with API Gateway has a 5-minute setup time. Ship first, optimize later.
Cost Comparison
Real numbers from projects I’ve deployed (50K requests/day, ~1KB average payload):
| Component | Serverless | Container | Hybrid |
|---|---|---|---|
| Compute | $35 (Lambda) | $120 (Fargate 2x 0.25vCPU) | $90 (Fargate 1 + Lambda) |
| Database | $25 (DynamoDB) | $60 (RDS t3.small) | $60 (RDS) |
| Load Balancer | $0 (API GW included) | $22 (ALB) | $22 (ALB) |
| Cache | $0 (DAX if needed: $27) | $15 (ElastiCache t3.micro) | $15 |
| Networking | $10 | $35 (NAT Gateway) | $35 |
| Total | ~$70/mo | ~$252/mo | ~$222/mo |
Note: NAT Gateway is the sneaky cost in container architectures. $32/month base + data processing charges. If your containers only make outbound calls occasionally, consider VPC endpoints for AWS services and a cheaper NAT instance.
Mistakes I Keep Seeing
“Let’s use Kubernetes because Netflix uses it.” Netflix also has 200+ platform engineers. You have 3 people and a deadline. ECS Fargate gives you 80% of the benefit at 10% of the complexity.
“We’ll need microservices for scale.” You won’t. A well-structured monolith on a single Fargate service handles more traffic than 95% of startups will ever see. Split when you have a real reason, not a theoretical one.
“Serverless is always cheaper.” It is for low-traffic workloads. But at high consistent load, Lambda’s per-invocation pricing gets expensive fast. The crossover point is typically around 1M requests/day where Fargate becomes cheaper per-request.
“We need multi-region from day 1.” You probably don’t. Single region with multi-AZ gives you 99.99% availability. Multi-region adds enormous complexity. Wait until you actually have users in other continents.
When to Migrate Between Patterns
You’ll know it’s time to switch when:
Serverless → Container:
- Cold starts are causing user-visible latency issues
- You’re hitting Lambda concurrency limits regularly
- Monthly bill exceeds what containers would cost for equivalent compute
- You need persistent connections (WebSocket, gRPC streaming)
Container → Hybrid:
- You’re deploying code that runs on schedules or events alongside your API
- Background jobs are competing with API requests for container resources
- You want to scale event processing independently from API traffic
Any → Kubernetes:
- Team exceeds 10 engineers
- You have 15+ services that need sophisticated networking
- Multi-cloud is a real requirement (not just theoretical)
- You have dedicated platform/SRE capacity to maintain it
My Recommendation
If I’m starting a project today with a small team, fully on AWS:
- Week 1-2: Ship on Lambda + API Gateway. Get something live. Validate the idea.
- Month 1-3: If the product has traction, evaluate if serverless constraints are hurting you.
- Month 3-6: Migrate hot paths to Fargate if needed. Keep background work on Lambda.
- Month 6+: You now have data. Optimize based on actual usage patterns, not assumptions.
The worst thing you can do is spend 3 months building the “perfect” architecture for a product that nobody uses. Ship fast, measure, then optimize.
Need help choosing the right architecture for your specific project? I offer technical consultation sessions where we can map out your requirements and design something that fits. Not something copied from a blog post (including this one).