DEV Community

SoftwareDevs mvpfactory.io
SoftwareDevs mvpfactory.io

Posted on • Originally published at mvpfactory.io

Replacing Your Kubernetes Cluster with a Single SQLite-Backed Binary

---
title: "Replace Kubernetes with SQLite: The $5 VPS Architecture for Early-Stage SaaS"
published: true
description: "A hands-on guide to embedding SQLite with Litestream continuous replication as your production backend  WAL-mode tuning, S3 recovery, single-binary deployment, and when to migrate to PostgreSQL."
tags: architecture, devops, cloud, postgresql
canonical_url: https://blog.mvpfactory.co/replace-kubernetes-with-sqlite-the-5-dollar-vps-architecture
---

## What We Will Build

Let me show you a pattern I use in every early-stage project: a single compiled binary embedding SQLite in WAL mode, continuously replicated to S3 via Litestream, deployed to a $5 VPS with zero container orchestration. By the end of this tutorial, you will have a production-ready architecture that handles thousands of concurrent reads, costs under $11/month, and recovers from disaster in seconds.

No Kubernetes. No Helm charts. No 2 AM pages about pod scheduling.

## Prerequisites

- A VPS ($5-$10/month from any provider — Hetzner, DigitalOcean, etc.)
- A Go or Rust toolchain for building your binary
- An S3-compatible bucket (AWS S3, Backblaze B2, MinIO)
- [Litestream](https://litestream.io/) installed on your VPS
- Basic familiarity with SQLite and systemd

## Step 1: Understand What You Are Replacing

Here is the gotcha that will save you months of over-engineering. Most early-stage teams spin up Kubernetes on day one for 50 daily active users. Look at the real cost comparison:

| Component | Kubernetes Stack | SQLite + Litestream |
|---|---|---|
| Compute | 3-node cluster (~$150/mo) | Single $5-$10 VPS |
| Database | Managed PostgreSQL (~$15-$50/mo) | Embedded SQLite ($0) |
| Replication/Backups | Automated DB backups (~$5/mo) | Litestream to S3 (~$0.50/mo) |
| Load Balancer | Cloud LB (~$18/mo) | Caddy reverse proxy ($0) |
| Container Registry | Registry + CI/CD pipeline | `scp` a binary |
| **Monthly Total** | **$200-$450** | **$6-$11** |

That is a 20-40x cost reduction with far less stuff that can break.

## Step 2: Set Up WAL-Mode Tuning for Concurrent Reads

SQLite's default journal mode serializes everything. WAL (Write-Ahead Logging) mode unlocks concurrent reads while writes remain serialized — exactly what a typical SaaS read-heavy workload needs.

Here is the minimal setup to get this working. Set these pragmas at connection time:

Enter fullscreen mode Exit fullscreen mode


sql
PRAGMA journal_mode=WAL;
PRAGMA busy_timeout=5000;
PRAGMA synchronous=NORMAL;
PRAGMA cache_size=-20000; -- 20MB cache
PRAGMA foreign_keys=ON;
PRAGMA wal_autocheckpoint=1000;


The docs do not mention this, but `synchronous=NORMAL` instead of `FULL` is the key performance lever. You lose durability guarantees only in catastrophic OS-level crashes — and Litestream's continuous S3 replication already covers that failure mode. In benchmarks, this combination handles 10,000+ reads/second and 1,000+ writes/second on modest hardware.

## Step 3: Configure Litestream for Continuous Replication

Litestream does not take periodic snapshots. It streams WAL frames continuously with sub-second replication lag, giving you point-in-time recovery granularity measured in seconds.

The architecture looks like this:

Enter fullscreen mode Exit fullscreen mode


plaintext
[Your Binary (Go/Rust/etc.)]


[SQLite - WAL Mode]


[Litestream] ──stream──▶ [S3 Bucket]


Point-in-time recovery from any snapshot


Recovery is a single command:

Enter fullscreen mode Exit fullscreen mode


bash
litestream restore -o /data/app.db s3://your-bucket/app.db


Your entire database restores from S3 in seconds for typical early-stage datasets. Compare that to restoring a PostgreSQL dump or waiting for a managed database snapshot to provision.

## Step 4: Deploy with a Single Binary

Here is your entire production deployment script:

Enter fullscreen mode Exit fullscreen mode


bash

!/bin/bash

scp myapp user@vps:/opt/myapp/myapp-new
ssh user@vps 'systemctl stop myapp && \
mv /opt/myapp/myapp-new /opt/myapp/myapp && \
systemctl start myapp'


No container registry. No image layers. No pod scheduling. No rollout strategies. Your CI pipeline builds a binary, copies it to the server, and restarts the service. Downtime is under one second.

For an early-stage SaaS with a handful of users, this is honestly better than the alternative. Every layer of orchestration you remove is a layer that cannot page you at 3 AM.

## Step 5: Define Your Migration Triggers Upfront

This is the section most advocates skip, and it is the most important one. SQLite is single-writer. Know your ceilings before you start:

| Metric | SQLite Comfortable Range | When to Migrate |
|---|---|---|
| Concurrent write transactions | < 50/sec sustained | When you consistently exceed this |
| Database file size | < 10 GB | When queries over large tables slow |
| Horizontal read scaling | Never | When a single box cannot serve reads |
| Multi-region requirements | Not feasible | When latency mandates geo-distribution |

The write concurrency ceiling is what you will hit first. A SaaS handling user-generated content (form submissions, API calls, event tracking) typically crosses the discomfort zone around 500-1,000 monthly active users generating write-heavy workloads. Read-heavy products like dashboards or content delivery can push well beyond that.

The migration path is well-trodden: export to PostgreSQL using `pgloader`, update your queries (SQLite's SQL dialect is 95% compatible), and deploy. Budget a weekend.

## Gotchas

1. **Forgetting WAL mode.** Without it, SQLite serializes all reads and writes. Always set `journal_mode=WAL` before anything else.
2. **Setting `synchronous=FULL` out of fear.** With Litestream replicating continuously, `NORMAL` is safe and dramatically faster. Save `FULL` for contexts without replication.
3. **No migration trigger defined.** Decide upfront: "When sustained writes exceed 50/sec, we start the PostgreSQL migration." Without a concrete number, you will either migrate too early (wasting time) or too late (degrading user experience).
4. **Over-engineering the deployment.** Resist the urge to containerize a single binary. `scp` and `systemctl restart` is your friend until traffic proves otherwise.
5. **Ignoring cognitive load.** Simple infrastructure matters more than you think. When your entire backend is one process and one file, you spend less mental energy on ops and more on the product. During long architecture sessions, I keep [HealthyDesk](https://play.google.com/store/apps/details?id=com.healthydesk) running to remind me to step away from the desk — turns out your deployment architecture matters less than your spine's architecture.

## Conclusion

Start with SQLite + Litestream if you are pre-product-market-fit. The $200+/month you save compounds, and the operational simplicity lets a solo developer or small team move faster. Set WAL mode, tune your pragmas, point Litestream at an S3 bucket, and treat your binary as the artifact — not a container image.

Add complexity only when traffic demands it, and let the data drive that decision instead of your anxiety about scale. Scale your ambition first. Scale your servers when the metrics tell you to.
Enter fullscreen mode Exit fullscreen mode

Top comments (0)