All Posts

20 September 2026

How I Deployed My Portfolio: FastAPI, Next.js, and a Single AWS EC2 Instance

AWSDevOpsDeploymentNginx

This site — the one you're reading right now — runs on a single AWS EC2 instance alongside several of my other projects. Here's exactly how it's wired together, since "just deploy it" tutorials rarely show the full setup.

The Stack

  • Compute: one Ubuntu EC2 instance running multiple projects side by side
  • Process manager: PM2, running both the Next.js frontend and the FastAPI backend (via uvicorn) as managed processes
  • Reverse proxy: Nginx, routing each domain/subdomain to the right internal port
  • SSL: Let's Encrypt via Certbot, auto-renewing
  • Database: Neon (serverless Postgres) — not self-hosted, so the EC2 box itself stays stateless for data

Why One EC2 Instance for Multiple Projects

Running separate servers per side-project gets expensive fast for portfolio-scale traffic. One instance with Nginx doing name-based routing handles several low-traffic projects comfortably:

server {
    listen 80;
    server_name codewithmunnax.com www.codewithmunnax.com;
    location / { proxy_pass http://127.0.0.1:3004; }
}

server {
    listen 80;
    server_name api.codewithmunnax.com;
    location / { proxy_pass http://127.0.0.1:8004; }
}

Each project gets its own port and its own PM2 process name, so restarting one never touches the others.

Process Management with PM2

pm2 start "venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8004" --name backend
pm2 start "npm run start -- -p 3004" --name frontend
pm2 save
pm2 startup   # survives reboots

pm2 status gives an at-a-glance view of every project's memory/CPU and whether it's actually running — genuinely useful once you're juggling five or six processes on one box.

SSL for Multiple Domains

sudo certbot --nginx -d codewithmunnax.com -d www.codewithmunnax.com -d api.codewithmunnax.com

Certbot edits the Nginx config directly, adding the listen 443 ssl blocks and certificate paths, plus a cron job that auto-renews before expiry.

Lessons Learned the Hard Way

  • NEXT_PUBLIC_* environment variables are baked in at build time. Changing .env.local does nothing until you run npm run build again — restarting the process alone won't pick up the new value.
  • DNS "parking" records silently coexist with your real A record. If a domain resolves to multiple IPs unexpectedly, check for a leftover parked-domain record before assuming it's a code issue.
  • Never run a database seed script against production without reading it first. A script that "upserts" can just as easily be a script that deletes everything not on its list. Read before you run, every time — especially on a database with real data in it.

Why Not Just Use Vercel + Railway?

For a pure Next.js app, Vercel is genuinely less operational overhead. I run this on raw EC2 because several of my projects need a persistent Python backend (yt-dlp, OpenCV, long-running scripts) that don't fit neatly into serverless function timeouts — and because understanding the full stack, Nginx included, is part of the point.

FAQ

Common Questions

Vercel is a great fit for pure Next.js apps, but several of my projects need a persistent Python backend for tasks like video processing and long-running scripts, which don't fit well into serverless function time limits.