
Everything I Ship Runs on One VPS: My Honest Solo-Dev Self-Hosting Stack for 2026
The default answer in 2026 is managed everything. Push the app to Vercel, point it at a serverless database, wire up a queue you'll never see, bolt on an agent platform, and let a dozen dashboards bill you in increments small enough to ignore. It's a fine default. It just isn't the one I run. Everything I ship — this blog, my side projects, the automations that post for me while I sleep, and the AI agent that watches the server — lives on a single VPS: one flat, boring monthly bill instead of a dozen metered ones.
This is the honest tour of that self-hosting stack: every service, why it earns its place on the box, and the part most 'self-hosting is basically free' posts skip — what it actually costs me in money, time, and the occasional bad afternoon. One of those afternoons, a stranger turned my CPU into a Monero miner. We'll get there.
The whole thing is one box
Start with the foundation, because it's almost anticlimactic: it's one Linux VPS. Four cores, a sensible amount of RAM, an SSD, a static IP. No Kubernetes, no autoscaling group, no orchestration layer I have to babysit. Every service I run is just a process on this single machine — and that constraint is the feature. It forces the whole stack to stay small enough to fit in my head.
# one modest VPS, four cores — everything it runs:
nginx → reverse proxy + TLS
payload (next) → thefalcon.dev, this blog
mastra (pm2) → Instagram automation
n8n → visual workflow automation
agent → sandboxed ops agent, Telegram-only
db → the data under all of itThe philosophy is boring on purpose: one box, plain long-running processes, dull dependable tools, and everything reproducible from a handful of config files plus a backup. If the server died tonight, I want to be back on a fresh VPS in an evening — not lose a weekend to it.
The web layer: Payload CMS runs this blog
The site you're reading runs on Payload CMS — a code-first, self-hosted, TypeScript-native headless CMS built on Next.js. The content lives in my own database, not someone else's SaaS, and the admin panel and the public site ship as a single Next.js app.
Why not a hosted CMS, or just Markdown in a repo? Two reasons. First, Payload is code: my collections — posts, projects, work, timeline — are TypeScript config I version like everything else, so the schema is mine and a change is a diff, not a support ticket. Second, and this is the part I didn't expect to lean on so hard, it exposes an MCP server. That lets me point an AI model straight at my content to draft, edit, and file posts as drafts without ever opening the admin UI. This very post was written through that pipeline and landed in my drafts for a human edit before it shipped.
What I gave up: no turnkey global CDN for the admin, nobody else getting paged when it falls over, and every upgrade is mine to run. For a personal site, that's a trade I'd make again every time.
The automation layer: Mastra and n8n, side by side
Two tools handle the 'do things on a schedule without me' work, and I run both on purpose, because they're good at genuinely different jobs.
Mastra is my code-first agent framework — the same stack I used to build a GitHub PR-reviewer agent. My Instagram automation — I call it Instagram-kit — is a Mastra app: it builds content and auto-publishes on a five-minute loop, with the run logic in TypeScript where I can actually test it. It lives behind PM2 and nginx on its own subdomain.
n8n is the visual counterpart. When a workflow is mostly wiring — this webhook triggers that HTTP call triggers a Telegram ping — dragging nodes around beats writing and maintaining glue code. So I let n8n own the boring integration plumbing and have it fire jobs at the Mastra app.
The honest version: running both is a little redundant, and I've been bitten right at the seam between them — a fire-and-forget call that silently dropped, a temp-directory permission that only showed up in production. The rule I eventually settled on: if it has real logic worth testing, it's Mastra; if it's plumbing between services, it's n8n.
The agent layer: an ops buddy in a cage
On the same box runs a self-hosted AI agent. It reads my Telegram, runs the occasional shell command, and handles small ops chores — 'summarize today's nginx errors,' 'ping me if disk crosses 80%.' It's genuinely useful, and it's also the single most dangerous thing on the server, because it takes orders in plain English and then executes them.
I know exactly how dangerous, because the first version got prompt-injected through an exposed API and spent an afternoon mining Monero for a stranger — as root. I wrote that whole incident up in its own post-mortem, so I'll spare the gory details here. The short version of the rebuild: its own unprivileged user, no exposed control plane at all, a hardened systemd sandbox, a CPU quota, and Telegram as the only way in. Same agent, same usefulness, blast radius shrunk to a shrug.
The lesson that now shapes the whole stack: anything on the box that can take untrusted input and act on it gets treated as already compromised. Least privilege here isn't a checkbox — it's the design.
The front door: nginx today, Cloudflare Tunnel next
In front of the public services sits nginx, doing the unglamorous reverse-proxy work: terminating TLS and routing each subdomain to the right local port. It's the workhorse, and it has never once surprised me.
The piece I'm actively changing is how those services are exposed. Today a few of them are reachable because ports are open and DNS points at my IP — and the miner incident was a loud reminder that every open inbound port is an attack surface I have to defend. So I'm moving exposure onto a Cloudflare Tunnel: the box makes one outbound connection to Cloudflare and nothing inbound is exposed at all. My origin IP disappears, access policies live at the edge instead of in iptables, and there's no 0.0.0.0 listener left for a scanner to find. For a one-person stack, closing every inbound door is the highest-leverage security move I have left.
How I actually drive all of this: Telegram
The control surface for the entire stack is a chat app. Telegram bots are how the agent talks to me, how Instagram-kit reports what it published, how n8n alerts land, and how I poke things from my phone without SSHing in. It sounds informal. In practice it's the best ops dashboard I've ever had, for the dumbest reason: it's the one I actually check.
It's also, not by accident, outbound-only — the bot polls Telegram; Telegram never connects to me — which fits the close-every-inbound-door theme exactly.
The honest tradeoffs of self-hosting vs. going fully managed
Here's the part the genre usually skips. Self-hosting this stack is not free, and it is not for everyone.
What it costs in money: one VPS bill — flat and predictable — instead of a spray of usage-based invoices. This is the clearest win. At my scale, one box is dramatically cheaper than the managed equivalent (hosted CMS + serverless functions + a managed queue + an agent platform), and nothing spikes the bill the month a post does numbers.
What it costs in time: I am the SRE. OS patches, TLS renewals, backups, the upgrade treadmill, and incident response at inconvenient hours are all mine. That Monero afternoon was the invoice for this line item, paid in full.
What it costs in resilience: one box is one box. If it goes down, it all goes down together — there's no region failover quietly saving me. I buy back some safety with backups and reproducible config, not with nines.
So when should you go managed instead? If downtime costs you real money, if you have customers and an SLA, or if your hours are worth more than the bill — pay for managed and don't think twice. Self-hosting earns its place when what you're optimizing for is control, understanding, and a flat predictable cost, and when 'down for an evening' is something you can live with.
The verdict
My whole stack — this blog, the automations, the agent, the side projects — runs on one VPS: nginx out front, Payload serving the web, Mastra and n8n doing the work, an agent locked in a sandbox, and Telegram as the remote control. It's small, it's mine, and it fits in my head.
That last part is the real reason I do it. Managed platforms trade understanding for convenience, and for plenty of teams that's exactly the right trade. But running it myself is how I actually learned how any of it works — including the lesson I learned the expensive way: in a self-hosted stack, the most important number isn't uptime or cost. It's blast radius.
More writing