Skip to main content

Outhaul Is a Single-Binary Self-Hosted PaaS on SQLite: What a Deliberately Minimal Dokploy and Coolify Alternative Gets Right About the First Box, and Where It Stops

9 min readDora NodaDora Noda
Share
On this page

The whole PaaS is a 22 MB binary and a SQLite file. Point Outhaul at a fresh VPS, push a git repo, and it clones the repo, builds it with Nixpacks, and serves it behind Traefik with automatic HTTPS — no Node runtime, no Postgres container, no Swarm or Kubernetes underneath. That is the entire pitch of outhaul-dev/outhaul: the same push-to-container workflow as Dokploy and Coolify with a fraction of the moving parts.

The honest verdict up front: for a first app on a single VPS, the minimalism is real and well-aimed — one binary to upgrade, one file to back up. But Outhaul is alpha, pre-release software whose own README says to run it only on a disposable test server, and its ceiling is architectural rather than a missing feature. This post is about both halves: what minimal-by-construction concretely buys you, and what the migration looks like the day one box is not enough.

What one binary actually removes​

The self-hosted PaaS space has converged on a familiar shape: a web panel backed by a real database, a queue, and an orchestrator. Coolify v4 runs a Laravel app plus its own Postgres, Redis, queue workers, websocket service, and a separately orchestrated Traefik container. Dokploy is leaner but still carries a Node application, an internal Postgres, and Docker Swarm semantics. On small Hetzner hardware the idle footprint tells the story: roughly 1.2 GB of RAM for Coolify against 0.8 GB for Dokploy, per recent side-by-side measurements of the two panels on identical VPS hardware.

Outhaul deletes almost all of that. The comparison below is the core of the argument, so here it is as a parts count rather than adjectives:

OuthaulDokployCoolify v4
Control-plane processes1 Go binary (~22 MB)Node app + Postgres + Swarm agentLaravel app + Postgres/MySQL + Redis + workers + Soketi + Traefik
State storeOne SQLite file (outhaul.db, WAL mode)Postgres containerPostgres/MySQL container
Job queueIn-process worker, deployments table as queueExternal queue servicesHorizon queue workers
Upgrade verbReplace the binary, restart the servicePull images, migrate DB, restart stackPull images, migrate DB, restart stack
Backup verbCopy one SQLite file (+ volume dumps)Dump Postgres + volumesDump database + volumes
Idle RAM, panel onlyTens of MB~0.8 GB~1.2 GB

Two design decisions make this possible. First, storage is SQLite through a pure-Go driver with CGO off, migrations embedded and run at startup, all writes serialized through a single writer with WAL mode and a busy timeout. Deploy throughput on one box never stresses that. Second, there is no JavaScript build step at all: the web UI is embedded in the binary via embed.FS, routing is stdlib net/http, and live logs stream over server-sent events instead of websockets. Nothing to compile, no frontend toolchain to break on upgrade.

This is not just aesthetic. Every container you do not run is a container you do not patch, monitor, or debug at 2 AM. On a 2 GB VPS, the difference between tens of megabytes and a gigabyte of panel overhead is the difference between headroom for your app and a build step that OOMs.

What you still get on the first box​

Minimal control plane does not mean a minimal workflow. Outhaul covers the deploy loop a solo developer actually touches daily:

  • Three build paths: Nixpacks auto-detection, repos carrying their own Dockerfile, and docker-compose.yml multi-service stacks with per-service domains.
  • Deploys that don't break production: health-gated blue-green cutover — the new container only takes traffic after passing health checks — plus one-click rollback to any retained image without a rebuild.
  • Automatic HTTPS via Traefik and Let's Encrypt, with an optional Cloudflare Tunnel ingress for boxes with no public IP.
  • Managed databases: one-click Postgres, MySQL, or Redis with generated credentials, persistent storage, and scheduled dumps to any S3-compatible bucket.
  • Day-two basics: live log tailing and CPU/memory metrics in the browser, a template gallery (Grafana, n8n, Ghost, MinIO, and others), project workspaces, shared env vars, and secrets encrypted at rest with NaCl secretbox.

The gaps are explicit in the architecture docs rather than hidden: a single admin user (no teams), no metrics history or alerting, and no multi-server support. Two more deserve emphasis because they affect adoption decisions. First, the license is AGPL-3.0, where Dokploy and Coolify are both Apache-2.0 — teams with license policies need to check with legal before running it, which is friction the incumbents do not have. Second, and more important: the project is alpha with no upgrade or data-migration guarantees between versions. The README's warning is unambiguous — disposable test server only, never production, never a machine holding anything you care about. Evaluate the design, not the deployment readiness.

Why SQLite is fine here — and why it is the ceiling​

It is worth pausing on the database choice, because "SQLite in production" still raises eyebrows. For this workload the eyebrows can stay down. A single-box PaaS writes platform state at human speed: a deploy record here, a settings change there. SQLite in WAL mode with a single serialized writer handles that with orders of magnitude to spare, and crash recovery is a file that is either there or not — no WAL replay across containers, no connection pool to tune.

But the same choice draws the ceiling in permanent marker. Platform state lives in a file on the box. There is no second writer, no shared control plane, no story for two Outhaul instances coordinating — multi-server is listed as out of scope in the architecture's locked decisions, alongside multiple users and metrics history. Contrast that with Dokploy, where Swarm mode at least offers a documented (if operationally heavy) path to a second node, or Coolify v4, which manages remote servers as a first-class concept. With Outhaul, the second machine is not a feature that has not shipped yet; it is a shape the storage layer cannot take. That honesty is a virtue — the project tells you exactly where it stops — but it means the "where it stops" question from the title has a crisp answer: at the edge of the first box.

The second-box question: migrate or throw away​

So what happens the day one box is not enough? Say the app outgrows a single VPS, or the team grows past a single admin login, or someone asks for staging parity. There is no import path. No exporter feeds Outhaul's SQLite rows into Dokploy, Coolify, or a Kubernetes manifest. Concretely, here is the transfer-versus-rebuild accounting:

What transfers:

  • The application itself — it is a standard container image built by Nixpacks or your own Dockerfile, runnable anywhere.
  • The managed-database contents, via the S3 backup archives (plain dumps and volume snapshots, not a proprietary format).
  • The operational knowledge: Traefik routing concepts, Let's Encrypt habits, compose files that run unmodified on any Docker host.

What gets rebuilt:

  • Every platform-level definition: apps, domains, env vars, deploy history, schedules — all rows in a SQLite file no other tool reads.
  • The ingress and TLS wiring, re-expressed in the new panel's model.
  • Team access, alerting, and any multi-environment structure, from scratch.

That reads like a harsh ledger until you price the alternative. Rebuilding a handful of app definitions in a new panel is an afternoon's work, and the assets that are expensive to lose — the code, the data, the container images — all transfer cleanly. The question the TODO spec poses is whether starting minimal defers the second-box answer or just makes it cheaper to throw away, and the answer is unambiguously the latter. Outhaul does not defer the migration; it makes the sunk cost nearly zero. There is no cluster to unwind, no provider-specific CRDs to translate, no database to replicate out of. The migration is cheap precisely because there is so little platform to leave behind.

The genuinely expensive migrations in this space are the ones where the platform holds your state hostage: proprietary backup formats, config that only exists as clicks in a UI, data planes that cannot run without the control plane. A single SQLite file you can copy with scp is the opposite of that. If you must bet on a tool you will outgrow, bet on the one whose exit cost is measured in hours.

Who should run it, who shouldn't​

Be blunt about fit, because the alpha warning does most of the deciding:

  • Side projects and learning boxes: yes. If you want push-to-deploy on a cheap VPS and can afford to wipe the box, this is the cheapest way to get the full workflow.
  • Evaluators and minimalism enthusiasts: yes. The architecture — one binary, embedded UI, SQLite queue, Docker-label Traefik config — is a clean case study in how far restraint goes.
  • Teams, client work, anything with an SLA: no. Single admin user, no migration guarantees, alpha status, and AGPL-3.0 are four independent disqualifiers; any one of them settles it.
  • Anything expected to grow past one machine: start elsewhere. If you already know the second box is coming, pick the tool with a documented path to it rather than planning a throwaway migration on purpose.

Outhaul's real contribution may be the question it forces rather than the software itself: how much of your PaaS's control plane exists for your workload, and how much exists for workloads you will never run? For the first box, the answer turns out to be a 22 MB binary and a SQLite file. Everything past that edge is someone else's problem — by design, stated up front, and priced into the exit.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. 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