My Complete Self-Hosted Stack for SaaS in 2026
Cloud

My Complete Self-Hosted Stack for SaaS in 2026

Contabo, Coolify, Docker, Cloudflare, Postgres, R2, Resend, Uptime Kuma, Turborepo and Docker Hub. Here's every piece of the stack I actually run, what each one replaced, and why I picked it over the alternatives.

Published September 4, 202611 min readUpdated Sep 6, 2026

Written by · Full-Stack Agentic AI Software Engineer — AI Agents, Automation & Revenue Systems for GTM/RevOps teams

In brief

What does a complete, self-hosted infrastructure stack for a SaaS actually look like in 2026, piece by piece?

One Contabo VPS running Coolify and Docker hosts the app and its Postgres database; Cloudflare sits in front for DNS, CDN, and WAF; Cloudflare R2 holds file storage; Resend sends transactional email; Uptime Kuma watches all of it; Turborepo builds the monorepo and pushes finished images to Docker Hub, which Coolify pulls on deploy. Each piece replaced a specific paid service, chosen for a specific reason, not as a blanket anti-cloud stance.

  • Contabo + Coolify + Docker replaced a PaaS bill and gave push-to-deploy back without a platform fee
  • Cloudflare's free tier replaced paid DNS, CDN, and a chunk of what a WAF service would otherwise cost
  • Postgres runs in Docker on the same box the app runs on — no managed database bill
  • R2 replaced S3-style storage specifically for its zero egress fees, and Resend replaced a self-hosted mail server on purpose, not by default
  • The box runs more than the app: an analytics tool, a status page, and small internal tools all live on the same infrastructure

Evidence notes

Contabo's current lineup

Cloud VPS 4 — 4 vCPU, 8 GB RAM, 100 GB SSD — €5.50/month for the first 24 months, promotional pricing, EUR. Checked against contabo.com/en/vps in September 2026.

Coolify's current release

Latest stable is v4.3.1 (12 August 2026); v4.4-rc.1 (19 August 2026) adds first-class OIDC. Ships Traefik as the default reverse proxy.

Cloudflare R2 pricing

Standard storage $0.015/GB-month, Infrequent Access $0.01/GB-month, zero egress at every usage level — the actual reason it replaced S3-style storage. Free tier: 10 GB storage, 1M Class A ops, 10M Class B ops.

Docker Hub rate limits

Anonymous pulls: 100 per 6 hours per IP. Authenticated free (Docker Personal): 200 per 6 hours. The stricter 2025 limits some posts still cite were announced but never enforced.

I get asked what I actually run often enough that it's worth writing down properly, once, instead of half-answering it in a comment thread each time. This is the full inventory: what each piece is, what it replaced, and why I picked it specifically over the obvious alternative, in the order a request actually hits them. Nothing here is a benchmark or a bill total — it's the architecture, stated plainly, piece by piece.

This is the concrete version of the rule I use to decide what to self-host in the first place: self-host what fails loudly and where I already have the skill, pay for what fails silently or needs specialist reputation. Every choice below traces back to one side of that line.

What's the box everything runs on?

A Contabo Cloud VPS 4 — 4 vCPU, 8 GB RAM, 100 GB SSD, €5.50/month for the first 24 months as of September 2026. I picked Contabo over the more commonly recommended Hetzner mainly on price and availability outside the EU-only restriction that applies to Hetzner's cheapest CX and CAX lines; Hetzner also raised prices sharply in mid-2026, with some tiers up over 170%, which made the comparison less obvious than it used to be. The important caveat, and one worth repeating every time this price gets quoted: it's a promotional rate for the first two years, in EUR, and any honest comparison against a PaaS bill has to account for what it becomes after that. People arriving at my complete self-hosted stack for SaaS in 2026 from a marketing team will find XenGrowth, who work on the commercial side of this closer to their day.

What actually deploys the app?

Coolify, running on that same VPS, is the layer that replaced the actual reason I was paying for a PaaS: push-to-deploy, automatic HTTPS, and container management without babysitting raw Docker commands by hand. I picked Coolify over Dokploy specifically for maturity and the size of its one-click service catalogue — Dokploy's Swarm-native foundation is a real advantage if multi-node is on the near-term roadmap, but Coolify already had more of what I needed solved. It ships Traefik as its default reverse proxy, and the current stable release as of September 2026 is v4.3.1, with a v4.4 release candidate adding first-class OIDC support.

That decision wasn't just about the feature list on the day I made it. Coolify's release cadence has kept pace with what a small production stack actually needs — structured audit logging and an instance-level MCP server with read-only tools both landed in 2026, and the beta Railpack build pack the same year added build-time env vars and multi-stage builds without touching the underlying Docker setup — which matters more over a year of actually running something in production than any single feature does on the day you pick the tool.

Where does the database live?

PostgreSQL, in a Docker container on the same box as the app. No managed database bill, because Postgres fails loudly — connection refused, a clear log line — and it's a piece of infrastructure I already know how to operate, back up, and restore. This is one of the clearest cases of the underlying rule doing its job: the failure mode here rewards running it myself instead of paying someone else to.

Whether that database belongs on the same box as the app at all, rather than a managed Postgres provider like Neon or Supabase, is a question worth answering deliberately instead of by default. Co-locating it keeps latency low and the bill simple, at the cost of the database and the app now competing for the same RAM and CPU under load — a tradeoff that's fine at the scale this stack is built for, and the first thing worth revisiting if that ever stops being true. There is a longer treatment of the operations side of this in The XenGrowth resource library.

What does owning all of this actually cost in security overhead?

Real, ongoing work, and it's worth naming rather than skipping past. Every piece of this stack that lives on the VPS instead of behind a managed platform is a piece I'm personally responsible for patching, monitoring for intrusion, and keeping behind a properly configured firewall. Securing a fresh box before anything gets deployed to it is where that starts, and it's not a one-time task — SSH configuration, key rotation, and fail2ban rules need occasional revisiting, not a single setup pass that's good forever. That overhead is the actual price of everything above being self-hosted instead of managed, and it's the honest reason this whole stack isn't a strictly free lunch just because the invoice is smaller.

What's in front of all of it?

Cloudflare, on the free tier, for DNS, CDN, and WAF. It replaced what would otherwise be a paid DNS provider plus a separate CDN bill, and it also hides the VPS's real IP address from direct traffic. There's no reason to self-host any of this — the free tier is genuinely capable, not a crippled trial, though it does cap at 5 custom firewall rules and can't drop edge cache TTL below 2 hours through its rule engine, which matters if the routing logic ever gets more elaborate than "basic."

Where do user files and uploads go?

Cloudflare R2, not S3, and not local disk on the VPS. R2's pricing is $0.015/GB-month for standard storage, with a cheaper Infrequent Access tier at $0.01/GB-month for anything accessed rarely, but the actual reason it won is zero egress fees at every usage level — the single biggest recurring cost complaint about S3-style storage is exactly the fee R2 doesn't charge. Local disk on the app server was never seriously in the running: durability there is a silent failure mode, and object storage durability is specifically the kind of specialist guarantee I don't want to re-derive myself.

What sends the emails?

Resend, and this one is on purpose, not by default — the full case for why email specifically stays managed is its own post, but the short version is that email deliverability fails silently and the fix is IP reputation, not infrastructure skill. Everything else on this list I run because I already hold the operational expertise the failure mode requires. Email is the one where I explicitly don't, and paying $20 a month past the free tier is cheaper than the alternative. XenGrowth on governed AI marketing workflows covers the AI agents and marketing automation side of this.

How do I know when something's actually broken?

Uptime Kuma, self-hosted on the same infrastructure, checking the app and its dependent services on a schedule and alerting when something doesn't respond. It's simple enough that the monitor itself being the point of failure is a manageable risk, which is exactly the property you want from the thing whose entire job is telling you when something else is down.

How does code actually get from a repo to that VPS?

Turborepo builds the monorepo, and the finished container images get pushed to Docker Hub rather than built directly on the VPS. The reasoning for that split is its own post, but the short version is that building on the same box that's serving production traffic competes for the exact CPU and RAM the app needs, and building elsewhere then pulling a pre-built image avoids that entirely. Docker Hub's authenticated free tier allows 200 pulls per 6 hours, well past what a Coolify deploy actually needs, and the much stricter limits some older posts still cite were announced in 2025 and never actually enforced.

The end-to-end version of that flow — what actually happens between a git push and a live deploy on this exact setup — is its own post, because there's a specific sequence worth getting right: build, tag, push to the registry, then have Coolify pull and swap the running container without a gap in traffic. Getting that sequence wrong is the difference between a deploy that's invisible to users and one that takes the app down for the thirty seconds it takes to notice and roll back.

Sizing the VPS itself isn't guesswork either — the actual tradeoff between vCPU and RAM for a box running this exact combination of app and database is a different question depending on whether the bottleneck turns out to be request concurrency or query memory, and it's worth answering deliberately instead of just picking the middle tier in a provider's pricing table. There is a longer treatment of AI search, GEO and discovery in XenGrowth on building one SEO and GEO content system.

Layer

Tool

Replaced

Why chosen

Server

Contabo Cloud VPS 4

Vercel / a managed PaaS bill

Price and availability outside Hetzner's EU-only cheapest tiers

Deploy / PaaS layer

Coolify + Docker

Vercel's push-to-deploy layer

More mature service catalogue than the closest self-hosted alternative

Database

PostgreSQL in Docker, same box

A managed Postgres bill

Fails loudly; already my skill set to operate

DNS / CDN / WAF

Cloudflare (free tier)

Paid DNS + CDN

Genuinely capable free tier, hides the origin IP

Object storage

Cloudflare R2

S3

Zero egress fees at every usage level

Email

Resend

A self-hosted SMTP server

Deliverability is a reputation problem, not an infrastructure one

Monitoring

Uptime Kuma (self-hosted)

A paid uptime service

Simple enough that it doesn't become its own point of failure

Build + registry

Turborepo + Docker Hub

Building directly on the production VPS

Keeps build load off the box serving live traffic

Is the app the only thing running on this box?

No, and this is the part that gets left out of most stack write-ups. Once Coolify and the reverse proxy are already handling one app, the marginal cost of a second small service is close to nothing, so a handful of supporting tools live on the same infrastructure rather than getting their own separate accounts.

What

Purpose

Why it's here and not a separate SaaS

Self-hosted analytics

Page-level and event traffic visibility without sending visitor data to a third party

Runs as one more container behind the same reverse proxy

Status page

Public uptime and incident history for the app, backed by Uptime Kuma's own status-page feature

No separate status-page subscription needed — it's the same monitoring tool

Internal tools

Small admin scripts and dashboards used only by me, not customer-facing

Never worth a dedicated hosting bill; they piggyback on infrastructure that already exists

What's the first piece of this that would actually need to change?

Almost certainly the database, and specifically its co-location with the app. Everything else on this list — the reverse proxy, the deploy tooling, the monitoring — scales by adding a second small instance without changing the underlying decision. Postgres sharing RAM and CPU with the app it serves is the one piece of this architecture with a natural ceiling, which is exactly why whether the database belongs on the same box at all is worth revisiting on its own schedule rather than waiting for it to become an emergency.

Everything above is also not a static list I set once and never touch. Coolify gets upgraded when a stable release ships something worth having — the move to v4.3.1 and the OIDC support landing in the v4.4 release candidate are the kind of change worth tracking, not ignoring until something breaks. The same goes for watching Docker Hub's own rate-limit policy, which has changed publicly once already and could again.

None of this is an anti-cloud stance. It's eight specific decisions, each one made against a specific alternative for a specific reason, and two of them — object storage and email — landed on the managed side on purpose. The through-line isn't "self-host everything you can"; it's picking the side of each decision where the failure mode and the required expertise actually line up with what I have.

Further reading from XenGrowth

Where this work meets go-to-market

For the marketing and revenue operations view of my complete self-hosted stack for SaaS in 2026, see XenGrowth, who work on the commercial side of this.

Which choices here are load-bearing?

Four questions on which parts of a stack are decisions and which are preferences. Most of what people argue about turns out to be the second kind.

1 / 4
Which of these is hardest to change later?

Apply this article

How to turn insights into execution

A practical sequence for teams turning concepts into production outcomes.

Self-HostingCoolifyContaboCloudflarePostgreSQLCloudflare R2ResendTurborepo

Audit your current state

Map the bottlenecks and constraints connected to the article’s core problem.

Choose one bounded change

Test the most useful recommendation on one workflow before widening the scope.

Measure what changed

Keep the parts that improve the work, document what failed, and make the next decision from evidence.

Next step

Need help applying this in your stack?

I can translate these patterns into a concrete implementation plan for your team.

Discuss implementationBack to blog

Replies usually within 24 hours.

Next Steps

Continue reading

Git Push to Production: My Self-Hosted Deployment Workflow End to End

No platform button, no black box. A monorepo commit turns into a running container on my own server through Turborepo, GitHub Actions, Docker Hub and Coolify — here's every step, including the ones that broke on me first.

Navigate

The Contabo + Coolify + Cloudflare Stack That Replaced My Vercel Bill

Three pieces, not thirty. Here's exactly what each one does, why that specific combination and not one of the dozen others I considered, and which parts of Vercel each one is actually standing in for.

Navigate

The Simplest Production Architecture for a Bootstrapped SaaS

One box, a handful of managed pieces around the edges, and a very short list of things you're not allowed to build yet. Here's the architecture, priced out to $20 a month, and the exact signal that tells you when to add each thing you skipped.

Navigate

vCPU or RAM? How to Size a VPS for Static Sites, Databases and Traffic Spikes

Four readers asked four versions of the same question, and each one has a different honest answer. Static files want RAM for page cache, Postgres wants RAM for shared_buffers, and "slow under traffic" is usually neither CPU nor RAM.

Navigate

Moving a SaaS Off Vercel to a €5.50 VPS: What the Numbers Actually Look Like

Nobody publishes their real Vercel invoice, so most migration posts trade in vibes instead of arithmetic. This one builds the comparison from Vercel's and Contabo's own published rates, states every assumption out loud, and shows exactly where the two lines cross.

Navigate

Should Your Database Live on the Same VPS as Your App?

The pitch for co-location is real: no network hop, no egress bill, one box to back up. So is the failure mode — one OOM event takes the app and the database down together, because they were never separate to begin with.

Navigate

Preview Environments Without Vercel: Branch Deploys on Your Own VPS

A preview URL per branch is the one Vercel feature people miss most after leaving. It's buildable on your own server, and it's genuinely harder than Vercel makes it look — mostly because of the database, which nobody's marketing page mentions.

Navigate

Best VPS Providers for Coolify: Contabo, Hetzner and the 2026 Price Shift

Every 'best VPS for Coolify' post still says Hetzner is the cheap option. That stopped being reliably true in June 2026 for two of its most popular tiers, and nobody's updated the recommendation yet.

Navigate

Why I Push to Docker Hub Instead of Building on My $10 Server

Build-elsewhere-pull-here isn't a preference, it's the only version of this that doesn't put a compile job in direct competition with the app for the same 8 GB. Here's what that split actually buys, and where a private registry earns its keep instead.

Navigate

How I Self-Host PostgreSQL for My SaaS (and When I Wouldn't)

Running Postgres in a container is easy. Running it in a way that survives a redeploy, a full disk, and an eventual major-version upgrade is the actual job. Here's the setup, tuned against Postgres's own defaults, and the honest list of where managed wins outright.

Navigate