Introduction
What Sthora is, the two ways to run it, the constraints that shaped it, and what it deliberately does not do.
Sthora is a self-hostable application deployment platform. One install script turns a fresh VPS into a portal that builds and runs Dockerised web applications behind an automatic TLS reverse proxy, with server and container monitoring.
What a deployment actually does
Every deployment, whatever triggered it, runs the same pipeline:
Lock. One in-flight deployment per application, enforced by a Redis lock on the
application id. A second request queues behind it or is refused with 409 — two deployments
of the same application never run at once.
Materialise the source into /var/lib/sthora/sources/<appId>/<deploymentId>/, either
by extracting an uploaded ZIP or by cloning the repository shallow and single-branch.
Detect the build type — a compose file, a Dockerfile, or neither, in that order. See
build types.
Sanitise the compose file. Parse it, reject anything on the reject list, overwrite the platform's own settings, inject routing labels and variables, and strip published ports. See compose files.
Build. docker compose build with BuildKit. Every line of output streams to the portal
and lands in a size-capped log file.
Deploy and health-gate. docker compose up -d --remove-orphans, then the container's own
HEALTHCHECK if it declares one, otherwise a TCP connect to the exposed port with backoff
until the health timeout.
Settle. On success, record the image tag and prune images beyond the retention count. On failure, mark the deployment failed with the captured error and leave the previous version running.
Two distribution modes, one build
| Self-hosted | Cloud | |
|---|---|---|
| Who runs the control plane | You, on your own VPS | Someone else, one hosted instance |
| Who runs the workloads | The same VPS | Servers you already own |
| How the control plane reaches them | Directly, over the local Docker socket | An agent that dials out from your machine |
| Limits | None — every ceiling is null | Whatever the plan says |
| Signup | Closed after the first account | Open, if the operator enabled it |
The difference is configuration, not code. Both modes go through one interface — every Docker
and filesystem operation is a ServerDriver call, and which machine it lands on is decided in
one place.
The constraints that shaped it
The target machine is 2 vCPU, 4 GB RAM, 40 GB disk — a $12–24/month instance. That is not a footnote; it drove the architecture.
- The platform budgets about 1 GB, leaving roughly 3 GB for your applications and builds.
- Build queue concurrency is 1. A
pnpm installinside a Node image is the memory spike, and two at once is an out-of-memory kill. - Redis runs
maxmemory-policy noeviction. BullMQ stores job state in Redis; an LRU policy silently evicts it and deployments disappear with no error. - Build logs never reach PostgreSQL. They stream over Redis pub/sub and land in a size-capped file; the database keeps a pointer, a byte count, and the last 4 KB.
- Metrics are pre-aggregated on write. 15-second samples live in Redis only; one row per minute reaches PostgreSQL, rolled up hourly after 7 days and deleted after 90.
- Builds refuse to start below the free-disk floor. A build that fills the root filesystem takes the platform down with it.
What it deliberately does not do
Kubernetes. Docker Swarm. Multi-node scheduling. Autoscaling. A preview environment per pull request. A bundled Prometheus, Grafana, or Loki. Buildpacks beyond Nixpacks. Serverless functions. A CDN.
Each is a rabbit hole a 2 vCPU box cannot afford, and pretending otherwise would make everything above worse.
Where to go next
Documentation
A self-hostable application deployment platform. One install script turns a fresh VPS into a portal that builds and runs Dockerised web applications behind an automatic TLS reverse proxy.
Requirements
The machine, the operating system, and the two values you have to decide before running the installer.