Install
One script, run as root on a fresh Ubuntu or Debian machine. What it does, what it generates, and what to back up afterwards.
Run it
Fetch the script onto a fresh machine, then run it as root:
curl -fsSL https://sthora.com/install.sh -o install.sh
chmod +x install.sh
STHORA_HOSTNAME=cloud.example.com \
STHORA_ACME_EMAIL=ops@example.com \
./install.shRun it as root. It is idempotent and re-runnable: every step checks before it acts, so a second run repairs rather than duplicates.
Not yet verified end to end
The installer has been syntax-checked and everything it orchestrates is exercised by the test suite, but it has not been run end to end on a fresh machine. Expect to find something, and please say what you found.
What it does, in order
Preflight. Reads /etc/os-release and refuses anything that is not
Ubuntu or Debian; refuses an architecture that is not amd64 or arm64;
refuses below 2 GB of RAM or 20 GB of free disk on /, and warns below 4 GB
of RAM.
Docker. Installs Docker Engine from get.docker.com and enables it,
unless both the engine and the Compose v2 plugin are already present.
Swap. Creates a 2 GB swapfile and adds it to /etc/fstab, unless swap
already exists. Swap does not make a build fast; it makes the difference
between a build that is slow at its peak and one the kernel kills.
Docker daemon. Writes /etc/docker/daemon.json with json-file logging
capped at 10 MB across 3 files, live-restore, and a 10.100.0.0/16
address pool at /24, then restarts Docker.
Firewall. Installs ufw if missing, allows 22, 80, and 443, sets
default-deny inbound, and enables it. Port 22 is allowed before the firewall
is enabled, because doing it the other way round locks you out of the
machine you are installing on.
Credentials. Generates the PostgreSQL password, the Redis password, and
AUTH_SECRET with openssl rand, and writes them to /etc/sthora/.env at
mode 0600. Generates the master encryption key into
/etc/sthora/master.key at 0600.
Compose. Installs the platform's compose file at
/etc/sthora/docker-compose.yml, writes /etc/sthora/compose.env with the
image registry, version, and hostname, and creates the external
sthora-proxy network.
Start and migrate. Pulls the images, brings the stack up, waits up to
two minutes for the API to answer /health, then applies the database
migrations with prisma migrate deploy — which applies committed migrations
and can never generate one.
It finishes by printing the portal URL and the first-run URL.
What it reads from the environment
| Variable | Required | Meaning |
|---|---|---|
STHORA_HOSTNAME | Yes in practice | The hostname the portal is served on. Falls back to localhost, which is only useful for a local experiment. |
STHORA_ACME_EMAIL | Yes for certificates | Where Let's Encrypt sends expiry warnings. Certificates are not issued until it is set. |
STHORA_BASE_DOMAIN | No | Wildcard base domain. Every application also answers on <slug>.<baseDomain>. |
STHORA_EMAIL_FROM | No | Sender address for alerts and password resets. |
STHORA_SMTP_HOST | No | SMTP relay. Without it, email is skipped and logged. |
STHORA_VERSION | No | Image tag to install. Defaults to latest. |
STHORA_IMAGE_REGISTRY | No | Where images are pulled from. Defaults to ghcr.io/asynccoders. |
What it writes
/etc/sthora/.env and master.key are mode 0600; the directory is 0700.
Back up the master key
Back up /etc/sthora/master.key off the box
Every environment variable, every deploy key, every webhook secret, and every managed-service credential is encrypted under it. Losing it does not lose the platform — it loses every secret the platform is holding, unrecoverably.
Then
Open https://<your hostname>/setup and claim the first account.
Removing it
./install.sh --uninstallStops everything and removes the platform. Applications you deployed are separate compose projects; read uninstalling before you run it.