How I Built and Deployed a Full-Stack Portfolio for $0

Ever checked your cloud bill after a "quick deployment" and found a surprise $47 charge staring back at you?
When I decided to build my developer portfolio, I set one rule: it has to cost exactly $0 to run — excluding the domain name. Not $5/month. Not "free for 30 days." Zero.
But I also refused to compromise. I wanted a full CMS, dynamic content, a blog, an AI chatbot, and multi-language support. The kind of portfolio that actually represents what I can build as an engineer.
Spoiler: I pulled it off. Here's exactly how.
The Stack
Here's what's actually running behind my portfolio:
- Frontend: Next.js (App Router) + Tailwind CSS + shadcn/ui
- CMS: Strapi v5 — headless, self-hosted
- Database: PostgreSQL
- Image CDN: Cloudinary
- Cache: Redis (for the AI chatbot)
- AI: Groq API for LLM inference
- Keep-alive: cron-job.org — the unsung hero
Why Next.js + Strapi? I wanted every section to be CMS-driven — not hardcoded JSX with my name baked in. Strapi's dynamic zones let me define component schemas, compose pages visually, and the frontend renders whatever the CMS sends. 16 sections — hero, about, skills, projects, timeline, certifications, blog, contact, and more — all powered by a single `DynamicZoneManager` that lazy-loads each section on demand.
Combined with ISR (Incremental Static Regeneration), pages are statically generated but revalidate every hour. Millisecond loads, but I can update content without redeploying.
The $0 Hosting Breakdown
This is the core of the whole setup — 7 free-tier services, each doing what it does best:
- Vercel — Next.js frontend + CDN (100 GB bandwidth free)
- Koyeb — Strapi CMS backend (1 nano instance, 512 MB RAM)
- Neon — PostgreSQL database (0.5 GB storage, 191 compute hrs/month)
- Cloudinary — Image hosting + CDN (25 GB storage, 25 GB bandwidth/month)
- Upstash — Redis for AI chatbot (10,000 commands/day, 256 MB)
- Groq — AI inference (free API, rate-limited)
- cron-job.org — Strapi keep-alive (unlimited free crons)
Total: $0.00/month. No credit card traps. No trial periods. Genuine free tiers running in production. The only real cost? A domain name — and even that's optional since Vercel gives you a `.vercel.app` subdomain for free.
The Cold Start Problem — And the Hero That Saved Me
Here's the part nobody warns you about. Koyeb's free tier scales to zero after 5 minutes of inactivity. No traffic for 5 minutes? Your Strapi instance goes to sleep. The next visitor triggers a cold start that takes 10-30 seconds.
Imagine a recruiter clicking your portfolio link and staring at a spinner for 20 seconds. That's a closed tab and a missed opportunity.
My first fix? UptimeRobot with 5-minute pings. Didn't work. If it pings at minute 4:59 and Koyeb sleeps at 5:00, you've got a race condition. The interval was too close to the threshold.
The real fix: cron-job.org
I set up a cron job that pings my Strapi health endpoint every single minute. That's 1,440 pings per day, all free. With a 1-minute interval, the instance never gets close to Koyeb's 5-minute sleep threshold. There's always a heartbeat within the last 60 seconds.
My setup:
- URL: Strapi's `/api/health` endpoint on Koyeb
- Schedule: Every 1 minute, 24/7
- Notifications: Email if 3 consecutive pings fail
As a safety net, the Next.js side also has retry logic with exponential backoff — 3 retries at 2s → 4s → 8s delays. The cron prevents cold starts; the retry handles the edge cases. Together, they've been rock-solid.
The AI Chatbot — For Free
This is the part I'm most proud of. My portfolio has an AI chatbot that answers questions about my experience, projects, and skills. Free.
The architecture is what I call "Push & Stream":
Push — When I update content in Strapi, a webhook pushes the data to Upstash Redis. This keeps a pre-built knowledge base of my portfolio content ready to go.
Stream — When a visitor asks a question, the API route pulls context from Redis, stuffs it into the prompt, and streams the response from Groq via the Vercel AI SDK.
Why context stuffing instead of RAG with vector embeddings? Because my portfolio corpus is small enough to fit directly into the prompt. No need for embedding models, vector databases, or similarity search. Sometimes the simplest solution is the right one.
The sync is layered for reliability:
- Strapi webhook → updates Redis on content change
- Vercel deploy hook → re-syncs on every deployment
The Architecture
Here's how the 7 services connect, from content creation to what visitors see:
1. I write content in Strapi CMS (hosted on Koyeb)
2. Strapi stores data in Neon PostgreSQL and images in Cloudinary
3. When content changes, a webhook syncs it to Upstash Redis for the AI chatbot
4. cron-job.org pings Strapi every minute to prevent Koyeb cold starts
5. Next.js (hosted on Vercel) fetches content from Strapi via REST API
6. The AI chatbot streams responses through Groq API
7. Visitors get a fast, static page served from Vercel's global CDN
What I Learned
The best infrastructure is the one you understand completely. I could have thrown everything on a $20/month VPS and called it a day. Instead, I learned how 7 different services work, how they connect, and how to make them resilient.
Every constraint forced a better decision. No budget for hosting? I learned Cloudinary's optimization pipeline. Backend keeps sleeping? I discovered cron-job.org and built retry logic. Need two languages? I architected i18n from the CMS layer instead of bolting on translation files.
If you're building your portfolio, my advice: don't start with the design — start with the architecture. Figure out where your content lives, how it reaches the browser, and how you'll update it in 6 months when you've got new projects to showcase.
The tools are out there. The free tiers are generous. The only thing you need to invest is time — and as engineers, that's the one resource we enjoy spending.
Questions about this stack? Found a better free-tier combo? Reach out through the contact form or connect with me on LinkedIn.