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

  1. Checks that Docker, Docker Compose v2 and openssl are present.
  2. 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.
  3. Takes the stack definition (compose.yaml, edge.yaml and .env.example) out of the image itself, so the stack always matches the version.
  4. For each environment, development first:
    • on its first run, writes its .env with 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.
  5. Starts the edge, which serves each environment's domain.

The Layout

Everything lives in /opt/jetstack/<name>:

FolderWhat 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.mdHow 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:8080 for production and 127.0.0.1:8081 for 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 URI https://<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:

  1. Point the DNS records of both domains at the machine (with built-in HTTPS).
  2. Copy the installer there.
  3. 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.
  4. 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.
  5. 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. Then jetstack 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:

  1. Development is upgraded first and must come up healthy.
  2. 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:

  1. Development is installed in dev/.
  2. Production stays where it is, with its data, and joins the pair's network.
  3. 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):

  1. Point the domain's DNS record at the machine.
  2. In prod/.env, add it to JETSTACK_ENTRY_HOSTS, for example JETSTACK_ENTRY_HOSTS=www.acme.com=website:exclusive.
  3. Run the installer again. The edge serves every domain listed in .env, and obtains a certificate for the new one.
  4. 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:

  1. Its Docker volumes (docker volume ls, prefixed with jetstack-<name>-prod or jetstack-<name>-dev): files, database and cache.
  2. Its .env file. It holds the keys that encrypt secrets stored in the environment; without it a restored database cannot read them.
  3. 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 --only fixes 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=1200 on 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.

  1. The provider generates an installer:
    • production jetstack.hospital.local, development jetstack-dev.hospital.local;
    • HTTPS by "your own proxy", on 127.0.0.1:8080 and 127.0.0.1:8081;
    • the registry token not embedded.
  2. The hospital loads the image from a file with docker load, then runs the installer.
  3. The hospital points its proxy at the two addresses, one per domain.
  4. Its developers connect with jetstack connect https://jetstack.hospital.local, build on development and promote to production.
  5. 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 .env out 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 dev upgrades it alone.
  • Watch the disk: uploads, databases and dumps of both environments live on the machine.