Skip to main content

The $25 Hetzner Stack: What 8 vCPUs, Coolify, and SQLite-on-NVMe Actually Prove

10 min readDora NodaDora Noda
Share
On this page

A WhatsApp-platform team moved off "complex cloud setups" onto one rented box and reported back that it just works. Their Show HN post from January 30, 2026 is the most concrete vertical-scaling receipt in recent memory: a full production stack on a single Hetzner VPS for about $25 a month. Here is the entire spec sheet, and the verdict up front.

LayerChoice
Hardware1× Hetzner VPS, ~$25/mo: 8 vCPUs, 32 GB RAM, 200 GB NVMe
PaaSCoolify: GitHub-webhook red/blue deploys, SSL, Docker management
AppNext.js plus Bun
DatabaseSQLite, a file on local NVMe
QueuesBullMQ over a local Redis
Object storageSelf-hosted S3-compatible storage, same node

The verdict: copy the shape, not the backup assumption. The headroom math is real — 32 GB of RAM on one node genuinely absorbs what a microservices fleet spreads across a dozen metered services — but "backups are just a file copy" is wrong on a live database, and nothing in the post answers what happens when machine two arrives. This post works out both gaps concretely: the Litestream sidecar that fixes backups for this exact stack, and the three real paths to a second machine.

What the $25 bought (and when)​

Wetarseel, the team behind the post, builds a WhatsApp Business API platform — bulk messaging, chatbots, campaign automation. Their stated goal was to reduce both the monthly bill and the "mental tax" of managing distributed services. Coolify gives them the Heroku-like surface (push to GitHub, red/blue deploy lands, TLS renews itself) while everything underneath sits on one fixed bill and one SSH host.

Two timestamp caveats before anyone prices this out today. First, $25 is a January 2026 number. Hetzner's June 15, 2026 price adjustment repriced x86 cloud tiers upward by roughly 2.2–2.7×: the shared 8-vCPU CPX42 went from $29.99 to $81.99 a month and the dedicated 8-vCPU/32 GB CCX33 from $73.99 to $162.99 (US dollars, VAT excluded, Germany region). The ARM CAX line barely moved — CAX41 went from €31.49 to €40.99 — so the spiritual successor to this build today is arguably ARM, not a like-for-like x86 box.

Second, the stated spec never mapped cleanly onto one list plan: closest on price was the shared CPX42 tier (old price €25.49, but 16 GB of RAM, not 32), closest on RAM the dedicated CCX33 (old price €62.49). Treat the $25 as the author's rounded bill, not a SKU. Neither caveat dents the actual argument — a single fixed bill versus metered sprawl — but re-price before you copy the architecture.

Headroom math: what 32 GB and 8 vCPUs absorb​

The post's thesis is that "vertical scaling is underrated," and the numbers back it up for this workload shape. Here is what one node consolidates, with rough footprints for a small-team production load:

ServiceMetered-cloud shapeFootprint on the node
Next.js app + Bun workers2–4 web dynos/containers, always on~2–4 GB RAM, 2–3 cores under load
SQLite (whole database)Managed Postgres, smallest tierPage cache + file; effectively free until the working set exceeds RAM
BullMQ + RedisManaged Redis or per-job runnerA few hundred MB; Redis idles near zero
S3-compatible storagePer-GB object storage + egressLocal disk; 200 GB NVMe included in the bill
Coolify + Docker overheadN/A (the PaaS premium)~1–2 GB RAM, negligible CPU at rest

The mechanism is unglamorous: RAM is the great consolidator. Thirty-two gigabytes means the database working set, the queue broker, the app runtimes, and the OS page cache all coexist without paging, swapping, or timing out across a network boundary. SQLite reads that would be a managed-database round trip become local file reads served from page cache.

There is no connection pooler to tune, no VPC peering for the cache, no per-service minimum spend. Each eliminated boundary is both latency and a line item gone.

The CPU side deserves equal weight, because 8 cores is what makes "everything on one node" operationally calm rather than contended. Request serving, background workers, Redis, and the storage daemon each get real cores instead of throttled shares, so a queue burst does not steal latency from web traffic the way it does on a 2-vCPU box. That is the part of the spec sheet that quietly does the most work: core count buys workload isolation without a scheduler.

But the spec sheet hides one contention point: builds. Coolify builds images on the node by default, and a Next.js production build will happily saturate all 8 cores for minutes — while serving production traffic from the same cores. On a bursty deploy cadence this is the one place the single node stops feeling infinite. The concrete fixes are boring and effective: build off-node (Coolify supports a dedicated remote build server), or accept brief contention and deploy off-peak. Either way, name builds as a capacity line item instead of discovering them during an incident.

Gap 1: "it's just a file copy" is wrong on a live database​

The post's riskiest sentence is its most quotable: SQLite "simplifies backups (it's just a file copy)." For a stopped database, true. For a live one taking writes, a raw cp of the database file can produce a corrupt backup — and in WAL mode, which any production SQLite deployment uses, the file on disk is not even the whole database: recently committed transactions may still live in the -wal file, so copying the main file alone can silently drop committed data. SQLite's own backup documentation exists precisely because file copies of hot databases are unreliable; the safe primitives are the Online Backup API, .backup, or VACUUM INTO.

This matters more than it looks, because Coolify's backup story does not cover this database. Coolify's v4 line ships first-class scheduled dumps for Postgres, MySQL, MariaDB, and MongoDB to local disk or S3-compatible storage — dump-based engines, each with a real backup tool underneath. A SQLite file sitting in an app volume is invisible to all of it.

The author's claim that Coolify "handles the plumbing (SSL, networking, backups)" is true for three of those four words. For the SQLite file, backups are the operator's job, and the job starts by not trusting cp.

The fix that preserves the stack's single-node simplicity is Litestream: continuous SQLite replication to S3-compatible storage with automatic restore, running as a sidecar next to the app. It is actively maintained — v0.5.17 shipped August 31, 2026, and the 0.5 series' LTX replica format is what current platform integrations build on.

For this exact stack the shape is:

yaml
# litestream.yml — replicate the app database to the node's own S3-compatible store
dbs:
  - path: /data/app.db
    replicas:
      - url: s3://backups/app-db
        endpoint: http://minio:9000

Run Litestream as the container's entrypoint wrapper (litestream replicate around the app process), point it at the self-hosted S3-compatible storage already on the node — plus one off-node bucket, because a backup that lives on the same disk as the database is a copy, not a backup — and restores become litestream restore onto a fresh volume. Point-in-time recovery comes free with the replication stream. Total added moving parts: one static binary and a YAML file. That is the backup story the Show HN post owes its readers, and it keeps the "boring single server" promise intact: the database is still one file, but now it is one file with a tested, continuous, off-node replica.

One honest limit: Litestream replicates a single writer. It fixes durability, not availability — if the node dies, you restore onto a new node and accept the downtime window. Which is exactly the second gap.

Gap 2: the second-machine migration path​

"The goal was to see how far we could get before needing a distributed system" is a fine experiment design, but the post never prices the end of the experiment. Three things break the day machine two arrives, and they break for the same reason — locality:

  1. SQLite's file locality. The database is fast because it is local. A second app server cannot open the same file, and NFS-sharing a SQLite database is a documented path to corruption. The data layer needs a new home before the app layer can scale out.
  2. Local Redis and BullMQ. Queues, scheduled jobs, and rate-limit state live in a Redis nobody else can reach. Splitting workers onto machine two means either moving Redis to its own reachable host or accepting two disjoint queue worlds.
  3. Node-local object storage. Assets uploaded to the on-node S3-compatible store are addressed to one disk. A second server either proxies back to machine one (making it a single point of failure with extra steps) or the bucket migrates to shared storage.

None of this is fatal; each has a priced path. The three honest options:

Path A: stay on SQLite with LiteFS. Fly.io's LiteFS replicates SQLite across nodes through a FUSE layer, keeping the single-file operational model while adding read replicas and primary failover. The caveat is maturity: LiteFS is still pre-1.0 with no official support, and its own docs carry sharp-edge warnings around primary leases and machine lifecycle. Adopt it with eyes open and keep the Litestream off-site replica regardless — replicated is not backed up.

Path B: migrate to Postgres. The conventional exit. Coolify provisions managed-style Postgres with scheduled S3 dumps on day one, so the migration buys into the backup story the stack currently lacks. The cost is the migration itself plus re-learning connection management — but for a team whose data has outgrown one file, this is usually the right trade, made once.

Path C: split stateless from stateful, keep one writer. Move the Next.js frontends and Bun workers onto machine two as Coolify SSH destinations while SQLite, Redis, and object storage stay on machine one. Coolify's multi-server model fits this directly — one dashboard, additional servers attached over SSH, each workload pinned to a server — and our second-machine showdown prices the added server at roughly $12 a month. Machine one becomes the stateful core; machine two absorbs CPU. This is the cheapest real scale-out, and it defers the database decision instead of forcing it under pressure.

Whichever path, notice what machine two costs: not a re-architecture, but a data-layer decision the single-file setup let the team postpone. That postponement is the genuine economic value of the $25 stack — months of zero database operations bought for the price of one VPS.

Verdict: who should copy this​

Copy this stack if your workload looks like Wetarseel's: a bounded app with a database that fits in RAM, background jobs that tolerate single-node Redis, assets that fit on one disk, and a team small enough that "one SSH host" is a feature. The headroom math shows why it wins: consolidation eliminates a dozen minimum spends and every network boundary between the app and its data.

Do not copy it if you need multi-writer scale today, if your compliance story requires automated tested restores you will not build (Litestream only works if someone tests the restore), or if you price it at $25 without checking today's Hetzner list. The architecture survived the June repricing — a single fixed bill still beats metered sprawl — but the headline number did not, and ARM is now the value lane.

The deeper lesson is about sequencing, not servers. Start boring: one node, one file for the database, continuous replication from day one. Let machine two be a planned data-layer decision, not a 3 a.m. discovery. The teams that scale gracefully are not the ones that began distributed; they are the ones that knew exactly which assumption would break first, and had the fix sketched before it did.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. When your single node grows into a fleet, Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex