The default advice for AI infrastructure is “use managed cloud services.” Vercel for hosting, Railway for databases, Zapier for automation, managed Postgres for state. Each service adds $10–30/month. The convenience is real; so is the bill.
We run a different stack: a single KVM VPS, Docker Compose, Caddy, n8n, and Postgres. Total cost: under €15/month. This post is the honest account — what works, what doesn’t, and when the managed-cloud path makes more sense.
The Stack
VPS (KVM, Ubuntu 24.04)
└── Docker Compose
├── caddy — TLS termination + reverse proxy + static file serving
├── n8n — visual workflow automation (self-hosted)
├── postgres — persistent state for n8n + custom workflows
└── mcp-fs — Model Context Protocol file server
No Cloudflare. No external CDN. No managed database. Everything on one box.
Why Caddy
We evaluated Nginx and Traefik before settling on Caddy. The decision came down to one feature: automatic TLS.
With Caddy, a site block like this:
neu.example.com {
file_server {
root /data/www/neu.example.com
}
}
…automatically provisions a Let’s Encrypt certificate on first request, renews it automatically, and serves HTTP/2. No certbot cron job, no manual renewal, no certificate expiry incidents.
The trade-off: Caddy’s config syntax is simpler than Nginx’s but less flexible. For complex proxy setups with fine-grained headers, Nginx has an edge. For the 95% case — reverse proxy + static files + TLS — Caddy is significantly less operational overhead.
One critical lesson learned the hard way: always run caddy validate before caddy reload. Caddy’s graceful reload will apply an invalid config and crash the process, taking all sites down. Validate first, always.
Why Self-Hosted n8n
n8n is our automation layer — it runs scheduled workflows, handles webhook triggers, and connects Claude to external data sources. The cloud version starts at $24/month for the basic tier. Self-hosted on our existing VPS costs nothing additional.
The trade-off is real: you own the maintenance. n8n updates, container restarts, backup scheduling — these are your problem. For a solo-founder stack, that’s maybe 30 minutes a month. For a team, the managed version may be worth the cost.
One thing we didn’t expect: n8n’s self-hosted workflow import/export is excellent. We can version-control workflows as JSON, restore from backup in minutes, and move between environments without data loss. The cloud version’s collaboration features aren’t worth $24/month for a solo operation.
Postgres for Workflow State
n8n requires a database. SQLite works for single-user local setups; Postgres is the right choice for anything production-adjacent.
We run a single Postgres container serving both n8n and our custom tooling. Internal hostname postgres — reachable from any container in the same Docker network. Database app_db, single application user with limited grants.
One operational note: Docker container names matter for networking. Our container is named postgres — not my_postgres or postgres_1. When n8n’s DB_POSTGRESDB_HOST is set to postgres, it resolves to the container by name. This sounds obvious until you spend an hour debugging a connection refused error because a container was renamed during a compose file refactor.
Backup Strategy
Self-hosting means you own the backups. We run two:
Database backup:
docker exec postgres pg_dump -U app_user app_db > /backups/app_db_$(date -u +%Y%m%dT%H%M%SZ).sql
n8n volume backup:
tar -czf /backups/n8n_data_$(date -u +%Y%m%dT%H%M%SZ).tar.gz /var/lib/docker/volumes/n8n_data/_data/
Both run via cron. Both go to the same VPS. For a production setup, off-site replication is essential — we’re currently shipping backups to a separate storage location as part of a planned automation workflow.
The Static Site Layer
We recently added Astro as a static site generator, serving output via Caddy’s file_server directive. The workflow:
- Astro builds to
dist/locally on the VPS dist/contents are copied into the Caddy data volume- Caddy serves the static files directly — no Node.js process, no runtime
Performance is excellent. TTFB on a cold request is typically under 50ms from European edge locations. The absence of a Node.js runtime means zero cold-start latency and zero memory overhead from the web layer.
One trap we hit: Docker bind-mount inode splits (covered in detail in our deployment incident post). Config updates to bind-mounted files need to be written in-place, not via atomic replace.
When This Stack Is Wrong
Self-hosted infrastructure is the right choice when:
- You’re a solo founder or small team with engineering capacity
- Your traffic patterns are predictable
- You have time for ~30 min/month of maintenance
- Cost optimization is a real constraint
It’s the wrong choice when:
- You need instant horizontal scaling under variable load
- Your team doesn’t have infrastructure expertise
- Downtime has direct revenue impact
- You’re spending more time on infrastructure than on your product
We’re in the first category. If you’re in the second, the managed-cloud path has genuine value — the cost is buying back your time and attention.
Cost Breakdown
| Component | Cost |
|---|---|
| KVM VPS (2 vCPU, 8 GB RAM, 100 GB NVMe) | ~€12/month |
| Domain registration | ~€1/month amortized |
| Let’s Encrypt TLS | €0 |
| n8n (self-hosted) | €0 |
| Postgres (self-hosted) | €0 |
| Astro (static, open source) | €0 |
| Claude API (usage-based) | Separate |
| Total infrastructure | ~€13/month |
Claude API costs depend heavily on usage patterns. For development workloads with prompt caching enabled, a typical month runs $20–60. For production automation workflows, budget separately.
The Maintenance Overhead (Honest Assessment)
A month of running this stack looks like:
- 2–3 container restarts for n8n updates
- 1 Caddy config change (new site block, webhook, etc.)
- Weekly backup verification (5 minutes)
- Occasional Postgres query for state debugging
Total: roughly 1–2 hours/month. That’s the real cost of self-hosting. If your time is worth more than €12/month in infrastructure savings, the managed path is better economics. If you’re a technical founder who enjoys infrastructure ownership (we are), this stack is a reasonable choice.
This stack is documented in detail as part of CoveLab Foundation — including the Docker Compose templates, Caddy configs, backup scripts, and the maintenance patterns we’ve developed over months of production use.