20 September 2026
How I Deployed My Portfolio: FastAPI, Next.js, and a Single AWS EC2 Instance
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.localdoes nothing until you runnpm run buildagain — 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.