- Book: Ship It — The Complete Tech Stack Guide for Startup Founders
- My project: Hermes IDE | GitHub
- Me: gabrielanhaia
You've been comparing hosting providers for two weeks. You've opened so many pricing pages your browser looks like a stock ticker. You've read four "Best Hosting in 2026" articles that were all affiliate link farms ranking GoDaddy in the top 3. You still haven't deployed.
I'm going to end that today. Four complete stacks at four budget tiers, with real prices, real specs, and the exact commands to set them up. Pick the tier that matches your wallet, follow the steps, and ship your thing this weekend.
Every recommendation is evaluated on three things: what it costs at your scale (not enterprise pricing), how hard it is to migrate away (vendor lock-in kills), and how much of your time it eats in ops work. No affiliate links. I don't make money from any of this.
$0/month — The Prototype Stack
For pre-revenue projects, weekend experiments, and things you're not sure will survive the month. Zero dollars while you figure out if anyone cares.
| Layer | Pick | Free tier limit |
|---|---|---|
| Frontend hosting | Vercel or Cloudflare Pages | Generous — you won't hit limits |
| Backend/API | Vercel Functions or Cloudflare Workers | Vercel: 100GB-hrs/mo. Workers: 100k requests/day |
| Database | Supabase (Postgres) | 500MB storage, 2 projects |
| Auth | Clerk or Supabase Auth | Clerk: 10k MAU. Supabase: unlimited |
| CDN/DNS | Cloudflare | Free plan covers everything |
| File storage | Cloudflare R2 | 10GB, 10M reads/mo, zero egress |
What you actually get: A real, deployed application with a Postgres database, authentication, file storage, and a CDN. Not a toy. Supabase gives you actual Postgres with Row Level Security and a REST API auto-generated from your schema. Cloudflare's free tier is absurdly generous for what it is.
Deploy a Next.js app to Vercel from the CLI:
# From your project directory
npx vercel
# Follow the prompts. First deploy takes ~2 minutes.
# Vercel auto-detects Next.js, sets up builds, gives you a URL.
# Every push to main auto-deploys after this.
Connect Supabase for your database:
// lib/supabase.ts
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
That's a database-connected, deployed app for $0. The catch: free tiers have limits. Supabase pauses inactive projects after a week of no activity. Vercel Functions have cold starts that can add 1-2 seconds to the first request after idle. None of this matters when you're validating an idea with 10 users.
When to upgrade: The moment someone pays you. Once you have a paying customer, their experience matters. Spend $10/month. You owe it to them.
$10/month — Real Infrastructure
Your first paying users showed up, or you're serious enough to invest a coffee per week. This tier gets you off free-tier limits and onto infrastructure you fully control.
| Layer | Pick | Cost |
|---|---|---|
| Server | Hetzner CX22 (2 vCPU, 4GB RAM, 40GB NVMe) | ~€4/mo (~$4.50) |
| Database | PostgreSQL on the same VPS | $0 (runs on your server) |
| Reverse proxy | Caddy (auto HTTPS) | $0 |
| CDN/DNS | Cloudflare (free) | $0 |
| Backups | Hetzner automated snapshots | ~€1/mo |
| File storage | Cloudflare R2 (free tier) | $0 |
| Monitoring | UptimeRobot (free tier) | $0 |
Total: ~$5-6/month. Room left in your $10 budget for a domain (~$12/year from Cloudflare Registrar).
Setting it up from scratch
Provision a Hetzner CX22 through their Cloud console (takes 60 seconds), pick Ubuntu 24.04, and SSH in:
# First login: update everything
ssh root@your-server-ip
apt update && apt upgrade -y
# Create a non-root user (don't run your app as root)
adduser deploy
usermod -aG sudo deploy
# IMPORTANT: copy your SSH key to the deploy user BEFORE disabling root login
# otherwise you'll lock yourself out
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
# Now it's safe to disable root SSH login
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl restart sshd
# Basic firewall: allow SSH, HTTP, HTTPS, deny everything else
ufw allow OpenSSH
ufw allow 80
ufw allow 443
ufw enable
Install Caddy and PostgreSQL:
# Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
# Install PostgreSQL
sudo apt install -y postgresql postgresql-contrib
# Create your database and user
sudo -u postgres createuser --pwprompt myapp
sudo -u postgres createdb -O myapp myapp_db
Your Caddyfile (the entire web server config):
# /etc/caddy/Caddyfile
myapp.com {
reverse_proxy localhost:3000
header {
X-Frame-Options "DENY"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
Strict-Transport-Security "max-age=31536000; includeSubDomains"
-Server
}
}
sudo systemctl reload caddy
Caddy handles HTTPS certificates automatically. No certbot, no cron jobs, no renewal headaches. Point your domain's DNS to the server IP through Cloudflare (proxy mode enabled for free CDN + DDoS protection) and it just works.
Set up your daily database backup:
#!/bin/bash
# /usr/local/bin/backup-db.sh
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="/tmp/db_backup_${TIMESTAMP}.sql.gz"
pg_dump -U myapp myapp_db | gzip > "$BACKUP_FILE"
rclone copy "$BACKUP_FILE" "r2:myapp-backups/daily/"
find /tmp -name "db_backup_*.sql.gz" -mtime +30 -delete
chmod +x /usr/local/bin/backup-db.sh
# crontab -e
# 0 3 * * * /usr/local/bin/backup-db.sh >> /var/log/db-backup.log 2>&1
Why Hetzner
Best price-to-performance ratio in the industry and it's not close. Their CX22 at €4/month gives you 2 vCPU and 4GB RAM on ARM-based Ampere processors with NVMe storage. That same spec on DigitalOcean costs $24/month. On AWS, you'd pay more and also spend an hour configuring security groups.
Data centers in Germany, Finland, and the US (Ashburn, Hillsboro). If your users are in Europe, this is the obvious choice. US coverage is solid too.
App + database on the same box?
Yes. At this scale, it's not just fine, it's optimal. Database connections over localhost have zero network latency. You're not Netflix. You're not even close to Netflix. A single Hetzner CX22 running a Node.js app and Postgres can handle thousands of requests per second.
When you need to separate them, you'll know. Your monitoring will show database queries competing with app CPU. For most apps, that happens somewhere between 1,000 and 10,000 daily active users. Way further out than you think.
When to upgrade: When you're spending more than an hour per week on server maintenance (updates, debugging, firefighting), or when your monitoring shows the single server is actually the bottleneck. Not when a blog post tells you to.
$50/month — Managed Services
Revenue is real. Users depend on your app. Your time is worth more than the $40/month difference between self-managed Postgres and someone else handling it. This tier is about buying back your weekends.
| Layer | Pick | Cost |
|---|---|---|
| Server | Hetzner CX32 (4 vCPU, 8GB RAM) | ~€8/mo (~$9) |
| Database | Neon Pro or Supabase Pro (managed Postgres) | ~$25/mo |
| CDN/DNS | Cloudflare Pro | $20/mo |
| Backups | Managed by database provider | Included |
| File storage | Cloudflare R2 | Likely still free tier |
| Monitoring | BetterStack or Sentry (starter tier) | $0-7/mo |
| CI/CD | GitHub Actions | Free for public repos, generous for private |
Total: ~$50-55/month
What changes at this tier
The big shift: you stop managing the database yourself. Neon or Supabase Pro handles backups, connection pooling, point-in-time recovery, and patches. When Postgres crashes at 2 AM (it won't crash often, but when it does), it's their pager that goes off. Not yours.
Neon's serverless Postgres is worth a specific callout. It scales to zero when nobody's using it and spins up in milliseconds when they do. The connection pooling through their proxy handles the "too many connections" problem that hits every growing app. The branching feature is the real win: instant copies of your production database for testing, something that'd take an afternoon to set up yourself.
// Connecting to Neon — same pg library, just a different connection string
import { Pool } from "@neondatabase/serverless";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
// That's it. Same Postgres. Same queries. They handle the rest.
Cloudflare Pro at $20/month adds real WAF rules (blocks common attack patterns automatically), better analytics, and image optimization. Is it necessary? No. Is it worth $20/month when you have paying users? Yes. It blocks attacks you don't even know are happening.
CI/CD at this stage
You should have automated deploys by now. If you're still SSHing into production and running git pull, fix that today.
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- run: npm ci
- run: npm test
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: deploy
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/myapp
git pull origin main
npm ci --production
pm2 restart myapp
Tests run first. Deploy only happens if they pass. Push to main and walk away. That's the entire pipeline.
When to upgrade: When you're onboarding a cofounder or first hire who needs to deploy safely. When you need a staging environment to test without risking production. When your single server can't keep up (you'll know because BetterStack will tell you).
$200/month — Production-Hardened
Real users. Real revenue. Maybe investors poking around asking about your infrastructure. This is the setup that doesn't embarrass you in technical due diligence and doesn't page you at 3 AM for preventable problems.
| Layer | Pick | Cost |
|---|---|---|
| Production server | Hetzner CX42 (8 vCPU, 16GB RAM) | ~€16/mo (~$18) |
| Staging server | Hetzner CX22 | ~€4/mo (~$4.50) |
| Database | Neon Scale or Supabase Team | ~$50-70/mo |
| CDN/DNS | Cloudflare Pro | $20/mo |
| Error tracking | Sentry Team | ~$26/mo |
| Uptime monitoring | BetterStack | ~$25/mo |
| File storage | Cloudflare R2 | ~$5-15/mo |
| CI/CD | GitHub Actions | ~$4/mo |
| Secrets management | Doppler or Infisical | Free tier or ~$10/mo |
Total: ~$170-200/month
What this buys you
Separate staging and production. Every change hits staging first. You and your team test there. When it's solid, promote to production. This sounds obvious but a surprising number of startups push directly to prod and hope for the best.
Proper error tracking. Sentry doesn't just catch errors. It shows you the exact request, the user session, the stack trace, and what happened in the 30 seconds before the crash. When a user reports "the app broke," you don't ask them to reproduce it. You look at Sentry and you already know.
// Sentry v8+ setup for Express
import * as Sentry from "@sentry/node";
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0.1,
});
// After all routes and middleware:
Sentry.setupExpressErrorHandler(app);
// Captures unhandled exceptions with full request context.
// User info, headers, body, the whole thing.
Secrets management. At the $10 tier, your secrets are in .env files on the server. That's fine for one person. At the $200 tier, with multiple people and environments, you need something better. Doppler or Infisical give you encrypted secrets, environment-specific configs, and audit logs showing who changed what and when.
# Instead of managing .env files manually:
doppler run -- node server.js
# Doppler injects all secrets as environment variables.
# Different secrets for staging vs production.
# Rotate a key in the dashboard, it's live immediately.
What you DON'T need yet: Kubernetes. Multi-region deployments. Auto-scaling groups. Load balancers. A dedicated DevOps hire. Your Hetzner CX42 with 8 vCPU and 16GB RAM handles more traffic than you think. When you outgrow it, you'll know — because your monitoring and error tracking will show you exactly where the bottleneck is.
The trigger points: when to move up
Don't upgrade because you feel like you should. Upgrade when a specific signal tells you to.
| Signal | What it means | Action |
|---|---|---|
| Free tier limits hit consistently | You've outgrown prototyping | Move to $10/month |
| First paying customer | You owe them reliability | At minimum: backups + monitoring |
| >1 hour/week on server ops | Your time costs more than managed services | Move to $50/month |
| Onboarding a teammate | They need safe deploy access | Move to $200/month |
| P95 response time >500ms at normal load | Server is the bottleneck | Bigger server or separate the DB |
| Database >80% storage | You'll run out faster than you think | Upgrade database plan |
The most important row is the paying customer one. Before revenue, you're experimenting. After revenue, you're responsible. That means backups, monitoring, and a plan for when things break.
This is one chapter
This post covers hosting decisions. One piece of the full picture. The rest (payments, authentication, security, GDPR compliance, tax across 30+ countries, CI/CD) each has the same depth of tradeoffs and "it depends on what you're building."
I wrote all of it in Ship It. 306 pages, 32 chapters. Same structure: what to pick, why, when to upgrade, and what changes depending on whether you're building SaaS, e-commerce, a marketplace, a mobile app, or an API product.
If you found this useful, the book is 31 more chapters of the same. The hardcover is available now — ebook and paperback are coming in a few days.
What tier are you running on? Over-engineered for your stage or under-investing? Drop your setup in the comments — always curious what stacks people are actually running.

Top comments (0)