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
letsencryptTLS mode) free on the host. - For a real hostname: DNS for
DSH_HOSTNAMEpointing 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. Defaultlocalhost.DSH_TLS_MODE—selfsigned(Caddy’s internal CA, the default),byo(dropcert.pemandkey.peminto./tls/), orletsencrypt(a public certificate forDSH_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>