All posts

Deploying Next.js + FastAPI + PostgreSQL on AWS: what you actually need

A practical, right-sized AWS setup for a Next.js front end, FastAPI back end, and Postgres database, from your first 100 users to your first 10,000.

4 min read#aws#architecture#nextjs#fastapi

Next.js on the front, FastAPI on the back, Postgres underneath. It's one of the most common stacks we see from AI-built apps, and AWS offers roughly fifteen ways to deploy it.

Most of those ways are wrong for a small team. Here's what you actually need, sized by traffic.

The shape of it

Browser → CloudFront → Next.js
                     → ALB → FastAPI (ECS Fargate) → RDS PostgreSQL

Four moving parts. Everything else, including queues, caches, and multiple regions, is something you add when a metric tells you to, not before.

Starter: about 100 active users

At this size, simplicity beats elegance.

  • Compute: one small EC2 instance, or a single Fargate task, running both apps in containers.
  • Database: RDS PostgreSQL on a db.t4g.micro or small, with automated backups turned on.
  • Front end: Next.js can run on the same box, or on Vercel if you're already happy there.
  • Estimated cost: roughly $25 to $50 a month.

The key decision here is managed Postgres from day one. Moving off SQLite or a self-hosted database later, with real user data, is far more painful than paying for RDS now.

Growth: about 1,000 active users

Now you want deploys that don't cause downtime and a server that can be replaced without drama.

  • Compute: FastAPI on ECS Fargate behind an Application Load Balancer, with at least two tasks.
  • CDN: CloudFront in front of Next.js static assets and images.
  • Database: a larger RDS instance, plus a connection pooler such as RDS Proxy if you run many tasks.
  • Alarms: CPU, memory, 5xx rate, p95 latency, and database connections.
  • Estimated cost: roughly $120 to $250 a month.

Point the load balancer's health check at a real endpoint that touches the database:

@app.get("/health")
async def health():
    await db.execute("SELECT 1")
    return {"status": "ok"}

ECS will only shift traffic to new tasks that pass it, and it'll roll back a deploy that doesn't.

Scale: 10,000+ active users

  • Compute: Fargate autoscaling across two availability zones.
  • Database: RDS Multi-AZ, so a zone failure means a short failover instead of an outage.
  • Cache and queue: ElastiCache for hot reads, SQS for anything slow such as emails, webhooks, or AI calls.
  • Estimated cost: $500 a month and up, driven mostly by database size and traffic.

The parts people skip

These matter more than instance sizes.

Networking

Put the database in private subnets with no public IP. Only the API's security group can reach port 5432. Watch out for NAT gateways: each one costs around $30 a month plus data charges, and agents tend to add one per availability zone by default.

Secrets

Store database credentials and API keys in AWS Secrets Manager or SSM Parameter Store, and inject them into tasks as environment variables. Never bake them into the image.

Migrations

Run database migrations (Alembic for FastAPI) as a one-off task before the new version takes traffic. Never run them from a developer laptop against production.

Infrastructure as code

Write all of this in OpenTofu or Terraform, and commit it. When something breaks at 2am, "what changed?" should be a git log, not a guess.

Budgets

Create an AWS Budget with an alert at 80% of what you expect to spend. It takes two minutes and catches most expensive surprises.

Should you even be on AWS?

If your app is happy on Vercel and Render, and you have no credits, compliance, or cost reason to move, stay there. The case for AWS gets strong when you hold startup credits, need data in a specific region for DPDP or GDPR, or when platform pricing starts to outgrow the raw infrastructure cost.

If you're weighing that move, book a call. We'll look at your repo and traffic and tell you which tier fits, including "not yet."