Self-Hosting
What It Is
A self-hosted installation runs Jetstack on the organization's own machine, with Docker. By default it is a pair of environments, as in a managed installation:
- production, where the organization works;
- development, with its own data, where developers build and test before promoting the result to production.
Each environment is made of three containers:
- the application;
- its MariaDB database;
- its Redis cache.
An optional edge container, Caddy, provides HTTPS for both. An installation can also be production alone, and development can be added to it later.
The provider gives the organization an installer: one script that installs a given version and, run again from a newer installer, upgrades to it.
Why It Matters
Self-hosting keeps everything on the organization's infrastructure:
- data and files never leave the machine;
- secrets are generated on the machine and are never known to the provider;
- the running application calls no third-party service on its own. Fonts, icons, scripts and QR codes are served or generated locally. Only the connections the organization configures leave the machine: its mail and SMS gateways, its AI providers and its integrations.
The pair gives a self-hosted organization the same way of working as a managed one: changes are built and tested on development, reach production in one reviewed step, and the jetstack CLI connects to both.
The trade-off is that the organization operates it: backups, upgrades, the machine and its HTTPS.
How It Works
What The Installer Does
- Checks that Docker, Docker Compose v2 and
opensslare present. - Pulls the application image, logging in to the registry with a read-only token when one is embedded. An image loaded beforehand with
docker load, for machines without internet access, is used as it is. - Takes the stack definition (
compose.yaml,edge.yamland.env.example) out of the image itself, so the stack always matches the version. - For each environment, development first:
- on its first run, writes its
.envwith every password and key generated on the machine, readable only by its owner; - starts it and waits until it reports healthy;
- on its first run, creates the administrator and prints the password once.
- on its first run, writes its
- Starts the edge, which serves each environment's domain.
The Layout
Everything lives in /opt/jetstack/<name>:
| Folder | What it holds |
|---|---|
prod/ | Production: compose.yaml and its .env |
dev/ | Development: the same, with its own secrets |
edge/ | The edge Caddy and the Caddyfile the installer writes |
DEVELOP.md | How developers start |
Each folder is its own Compose project, with its own containers and volumes. The two applications also share the pair's network, where development reaches production during a promotion from its History screen.
Development
Development differs from production in four ways:
- it is marked as development (
JETSTACK_ENVIRONMENT), with a banner linking to production; - it never sends e-mail or SMS to real people (
JETSTACK_OUTBOUND_GUARD); - it is smaller (fewer workers, less memory);
- it has its own database, files and secrets, so nothing from production is copied into it.
Each environment knows the other (JETSTACK_PEER_URL). This is how jetstack connect https://<production domain> finds both environments, and how a promotion reaches production.
HTTPS
Choose one when the installer is generated:
- Built-in. The edge Caddy answers on ports 80 and 443 and obtains a Let's Encrypt certificate for each domain. The DNS record of each domain must point at the machine.
- Your own proxy. Each application listens on its own local address, by default
127.0.0.1:8080for production and127.0.0.1:8081for development, and the organization's reverse proxy forwards HTTPS for each domain to its address.
What Is Different From A Managed Installation
- No console. The provider's console does not reach a self-hosted installation; its operations API is switched off.
- Sign-in with Google or Microsoft uses the organization's own OAuth clients (
OAUTH_*in each.env, with the redirect URIhttps://<domain>/identity/<provider>). Production and development each need the redirect URI of their own domain. - Backups are the organization's. Each application keeps a nightly database dump on its volume; copying the volumes off the machine is up to the organization.
How To Use It
Install
On the machine:
- Point the DNS records of both domains at the machine (with built-in HTTPS).
- Copy the installer there.
- Run it with
sudo bash jetstack-install-<name>.sh. It first asks for the administrator's password, typed twice and never shown: the e-mail (and name) were given when the installer was generated. Press Enter instead to have a password generated for each environment. - Sign in to each environment as the administrator, a tenant administrator. A generated password is printed once at the end; change it after signing in.
- Give developers
DEVELOP.md. The jetstack CLI comes from production's Command-line tool page (https://<production domain>/cli/install), which only tenant administrators and superusers can open: they install it with the command shown there, or hand that command, which works for an hour, to a developer. Thenjetstack connect https://<production domain>in an empty folder.
Upgrade
Run the installer for the new version the same way. It keeps each .env and the data, and moves the image version:
- Development is upgraded first and must come up healthy.
- Only then is production upgraded. If development fails, the installer stops and production keeps running the previous version.
Schema changes are applied on start, and so are the new version's blocking platform migrations: data changes the new version needs before it serves anyone. Each runs once per environment. The environment turns healthy only after they finish, so the installer's health wait (JETSTACK_HEALTH_TIMEOUT) covers them.
Background migrations run after the environment is up, in short slices from its scheduler. To see where they stand, run this in the environment's folder (prod/ or dev/):
docker compose exec app jetstack-admin migrate status
A failed migration is listed there with its error. A blocking one is retried with every start. A background one is retried after 1, 5, 30 and 120 minutes; after that, retry it once the cause is fixed:
docker compose exec app jetstack-admin migrate retry <name>
There are no down migrations. To return to the previous version, run its installer: it works on the migrated data.
Add Development To An Older Installation
An installation made before pairs is production alone, directly in /opt/jetstack/<name>, with its own Caddy. A plain upgrade keeps it that way. To add development, run an installer that has a development domain with --add-dev:
- Development is installed in
dev/. - Production stays where it is, with its data, and joins the pair's network.
- Production's own Caddy is replaced by the edge in
edge/, which serves both domains. HTTPS pauses for a few seconds while the edge starts.
Later runs keep this layout.
Change Settings
Edit the environment's .env (prod/.env or dev/.env), then apply it by running the installer again, or with docker compose -f compose.yaml -f edge-network.yaml up -d in that folder. The Environment Reference lists every variable.
Give An App Its Own Domain
To serve a custom app, such as a website, on its own domain (see Entry Domains):
- Point the domain's DNS record at the machine.
- In
prod/.env, add it toJETSTACK_ENTRY_HOSTS, for exampleJETSTACK_ENTRY_HOSTS=www.acme.com=website:exclusive. - Run the installer again. The edge serves every domain listed in
.env, and obtains a certificate for the new one. - With your own Google or Microsoft sign-in clients, register
https://<domain>/identity/<provider>as a further redirect address.
Behind your own proxy, forward the domain to the production application as you do for its other domains.
Back Up
For each environment, back up three things:
- Its Docker volumes (
docker volume ls, prefixed withjetstack-<name>-prodorjetstack-<name>-dev): files, database and cache. - Its
.envfile. It holds the keys that encrypt secrets stored in the environment; without it a restored database cannot read them. - The nightly database dumps in the environment's tenant directory, which are a convenient, consistent copy.
Production's backups matter most. Development can be rebuilt by promoting production's configuration again, but its test records would be lost.
Installer Options
--only prod / --only dev
What it controls:
- installs or upgrades one environment and leaves the other as it is
Default:
- both environments, development first
How to configure it:
- pass it on the command line:
sudo bash jetstack-install-<name>.sh --only dev
What it affects:
- installing development alone before production knows it leaves production without its peer; the installer says so, and one run without
--onlyfixes it
--add-dev
What it controls:
- adds development to an installation made before pairs, and moves HTTPS to the edge
Default:
- off: an older installation stays production alone
How to configure it:
- pass it once on the command line, with an installer generated with a development domain; it cannot be combined with
--only
What it affects:
- production joins the pair's network and learns its development; its data and folder stay as they are
JETSTACK_DIR
What it controls:
- the folder of the installation
Default:
/opt/jetstack/<name>
How to configure it:
- set it when running the installer:
sudo JETSTACK_DIR=/srv/jetstack bash jetstack-install-<name>.sh
What it affects:
- every later run must use the same folder, or it installs a new pair
JETSTACK_ADMIN_PASSWORD
What it controls:
- the administrator's password, for an installer run without a terminal (from automation)
Default:
- unset: in a terminal the installer asks for the password; without one, it generates a password for each environment and prints it once
How to configure it:
- set it when running the installer, at least 12 characters:
sudo JETSTACK_ADMIN_PASSWORD='…' bash jetstack-install-<name>.sh
What it affects:
- production and development get the same administrator password; it is not printed. It is used only when an environment's administrator is created: on its first run, or on the run after a failed attempt (the installer retries until the administrator exists)
JETSTACK_HEALTH_TIMEOUT
What it controls:
- how long, in seconds, the installer waits for an environment to report healthy before it stops
Default:
600
How to configure it:
- set it when running the installer, for example
JETSTACK_HEALTH_TIMEOUT=1200on a slow machine
What it affects:
- when development does not become healthy in time, production is not upgraded
Example
A hospital runs Jetstack inside its own network, behind its existing reverse proxy.
- The provider generates an installer:
- production
jetstack.hospital.local, developmentjetstack-dev.hospital.local; - HTTPS by "your own proxy", on
127.0.0.1:8080and127.0.0.1:8081; - the registry token not embedded.
- production
- The hospital loads the image from a file with
docker load, then runs the installer. - The hospital points its proxy at the two addresses, one per domain.
- Its developers connect with
jetstack connect https://jetstack.hospital.local, build on development and promote to production. - Upgrades arrive as a new installer and an image file. Running it upgrades development first, so a problem shows there before it reaches production.
Best Practices
- Keep each
.envout of version control and back it up separately from the volumes. - Treat an installer with an embedded registry token as a secret, and have the provider rotate the token when one leaks.
- Let development take each new version first, and try the organization's key processes there before production follows;
--only devupgrades it alone. - Watch the disk: uploads, databases and dumps of both environments live on the machine.