Self-hosting

Self-hosting

Bring the whole stack up on infrastructure you control — one Docker Compose command, a k3s manifest set, or an air-gapped tarball with no registry access.

Every service — gateway, control plane, console, agent artifacts, Postgres and a Caddy ingress with TLS — comes up from one Docker Compose command, on infrastructure you control. Every secret is generated on first boot; the first administrator’s password is printed once, in the logs, and stored nowhere else.

The whole stack self-hosts with one command; an air-gap bundle ships every image.

What you need

  • Docker Engine 24+ with the Compose plugin (docker compose version).
  • Ports 443 (and 80 for the letsencrypt TLS mode) free on the host.
  • For a real hostname: DNS for DSH_HOSTNAME pointing at the host.

Get the bundle

Customers get the deployment bundle from the licensing portal’s Downloads page, not by cloning this repository. Two bundle types are offered there: an air-gapped tarball with every image embedded, or a lighter compose bundle that pulls its images from the registry using the credentials in your licence e-mail.

Install

Extract the bundle and, optionally, set a hostname and TLS mode in .env — the default is localhost with a self-signed certificate. With the air-gapped tarball:

tar -xzf dsh-stack-0.1.0-airgap.tar.gz && cd dsh-stack-0.1.0-airgap
for t in images/*.tar; do docker load -i "$t"; done
# optional: DSH_HOSTNAME / DSH_TLS_MODE in .env as above (no image variables needed)
docker compose up -d --wait
docker compose logs control-plane | grep -A6 'FIRST-BOOT ADMIN'

With the registry-access compose bundle, skip the docker load loop — sign in to the registry with the credentials in your licence e-mail instead, and the images pull automatically — then bring it up and check the log the same way.

Open https://<DSH_HOSTNAME>/ and sign in with admin@localhost (or DSH_ADMIN_EMAIL, if set) and that password.

.env knobs

Nothing is required — the stack works with no .env file at all. The two settings worth knowing:

  • DSH_HOSTNAME — the public hostname. Ends up in the Caddy site address and in the URLs enrolled host agents are told to dial. Default localhost.
  • DSH_TLS_MODEselfsigned (Caddy’s internal CA, the default), byo (drop cert.pem and key.pem into ./tls/), or letsencrypt (a public certificate for DSH_HOSTNAME; needs ports 80 and 443 reachable from the internet).

k3s

The same images also deploy to a k3s cluster from the manifest set in deploy/k3s/advisor/ — a StatefulSet for Postgres, one Deployment sharing the gateway and control-plane containers, a console Deployment, and an Ingress. Create the namespace, generate the secrets and apply the manifests:

kubectl create ns advisor
kubectl -n advisor create secret generic advisor-secrets \
  --from-literal=secret_key="$(openssl rand -hex 32)" \
  --from-literal=kek.b64="$(openssl rand -base64 32)" \
  --from-literal=db_password="$(openssl rand -hex 32)" \
  --from-literal=gateway_token="$(openssl rand -hex 32)" \
  --from-literal=admin_password="$(openssl rand -base64 24 | tr -d '/+=' | cut -c1-20)"

# 3. Everything else:
kubectl -n advisor apply -f deploy/k3s/advisor/
kubectl -n advisor rollout status deploy/advisor

The plumbing differs from the Compose stack — Traefik instead of Caddy, a Kubernetes Secret instead of the generated secrets volume — but it is the same set of images, unmodified.

Day 2

One operator CLI covers status, backup, upgrade and restore. The backup includes the key-encryption key, so guard the archive like a credential:

./dsh-stack status
./dsh-stack backup            # includes the KEK — guard the archive like a key
./dsh-stack upgrade 0.2.0
./dsh-stack restore <archive>
All documentation