kubectl access to the cluster. Check the prerequisites first. Options such as an external data tier, the executor fleet and models are on Configure and Models and local inference.
Install in three steps
1
Receive the license files and pull token by email
2
Run the one-liner
--license-file ./license.json instead of the three flags.The wizard runs preflight checks, installs with Helm and seeds an admin login. It prompts for:- Registry
- Chart source (OCI pull or local path)
- Data tier
- Admin email
- Organization display name
- StorageClass
- Agent code execution (
--code-execution/--no-code-execution). The runner embeds dockerd, so its pod is privileged, and a restricted PodSecurity policy refuses it - A base domain for the public hosts
- LLM provider keys, for any hosted providers your egress policy allows
3
Sign in as the seeded admin
The first install creates your organization and a bootstrap admin in your in-cluster Supabase. The closing summary prints how to sign in: see Signing In.
bash install.sh --edition airgap --manual prints a personalized runbook and runs nothing: see Advanced: review-first / manual install. Pipelines: Automation & CI.
What happens when you run the installer
The installer runs eight phases. Steps 1 to 6 take a few minutes. Step 7 takes 10–20 minutes. Each phase prints a[N/8] name: ... line for scripts to grep (Automation & CI). Every hook failure names the Job, prints its last 30 log lines, and ends with a FIX: line and a RERUN: line that resumes the install.
The eight phases in detail
The eight phases in detail
1
Resolve: load the latest release
One release manifest lists the version and SHA-256 digest of every xpander image, including
llm-gw and tool-repo-seed. The installer passes those pins to Helm. To repeat a release or install offline, pass --release-manifest ./release-manifest.json. A missing or invalid manifest stops the run. How releases work.2
Checklist: preconditions verified before anything changes
The registry answers
/v2/ (401 counts as up), agent-controller and deployment-manager exist at their pinned tags, an RWO StorageClass exists, the image pins are in place, --org-name and --admin-email are set, and a chart source exists. Any failure stops here with nothing to clean up.3
License: validated locally, applied as a Secret
Two
xpander-license/v2 envelopes, one kind: install and one kind: activation, bound to --org (a legacy v1 file: envelope, expiry and org id). They become the xpander-license Secret. The Ed25519 signature is verified in the preflight hook and again at deployment-manager boot.4
Chart: pulled over OCI
The
xpander-airgap chart comes from the gated OCI registry, your own mirror, or a .tgz via --chart-path.5
Values: secrets never touch the command line
Configuration goes to a temporary values file (mode 600, removed on exit), never to the helm command line, shell history or process list.
6
Install: helm, without --atomic on purpose
The first install runs without
--atomic, because --wait would hold the seed hooks that the api and agent-controller pods are waiting on. The smoke hook is the readiness gate. Upgrades always run with --atomic, so a failed upgrade rolls back and the previous revision keeps serving.7
Pipeline: the cluster comes up, hook by hook (10–20 min)
The installer watches the hook pipeline live:
8
Summary: URLs, password, logs
The closing box prints the derived URLs (with a base domain:
https://chat.<domain>, https://api.<domain>, https://supabase.<domain>), the bootstrap admin password, and how to read it later (see Signing in).The secret-scrubbed session log (mode 600) is under ~/.xpander/logs/, with latest.log pointing at the last run.When the mirror is empty, the wizard offers to fill it
Choose Mirror them now, give the xpander pull credentials and any destination credentials, and let the copy finish. ECR repositories are created when your AWS credentials allow. Non-interactively:--mirror now|guide|skip (now needs the credentials). Sealed networks use the archive flow below.
Sealed or manual mirroring
Mirror with the currentimage_sync.sh: it includes llm-gw and tool-repo-seed, and stops on a release without the gateway image. It needs skopeo and Python 3. The Docker pull/tag/push fallback does not preserve release digests.
On a connected workstation, download the manifest once and use that file for both the copy and the upgrade:
--dry-run prints the plan. Keep the JSON manifest: a single --tag or a legacy service-only manifest is not a complete release. Tag flags and repository retention: Releases.
Fully sealed network? Archive outside, carry the whole directory in, import inside:
install.sh, image_sync.sh and their release_manifest.py with the bundle. Get the validator from the immutable release_manifest-<sha256>.py URL in the scripts and save it as release_manifest.py beside them. The scripts verify its checksum. Import validates the full image set and digests first. Complete an incomplete archive on the connected side. Legacy archives need the previous importer.
The chart is an OCI artifact. Mirror it with the images (--chart-version <X.Y.Z> --chart-source registry-1.docker.io/xpanderaihub/xpander-airgap, landing at oci://<your-registry>/xpander/charts/xpander-airgap), or hand-carry the .tgz:
Using Nexus, Artifactory, or Harbor
Using Nexus, Artifactory, or Harbor
Hosted repository: mirror into it. Use it as Then install with Images cache on first pull. Keep the
--registry, and fill it with the wizard or image_sync.sh. --dest-user / --dest-token handle Basic auth:--registry nexus.corp.example:5000/xpander.Pull-through proxy, no mirror step. Create a Docker proxy repository upstream of Docker Hub (https://registry-1.docker.io) with the xpander read token as its upstream credentials, and keep the org path in the registry value:xpanderaihub segment: the chart builds <registry>/<name>, so <proxy-repo>/xpanderaihub/agent-controller maps to xpanderaihub/agent-controller upstream. A fully sealed network cannot use a proxy: use the archive flow into a hosted repository.The license
New installs use two sealed files (install.json + activation.json, xpander-license/v2): see Two-File Licensing for caps, expiry and renewal. The installer validates both and creates the xpander-license Secret:
global.license.publicKey, and deployment-manager refuses to start on a bad one.
- Your own
--valuesfile displaces the installer’s license block: also pass--set global.license.installKey=install.json --set global.license.activationKey=activation.json --set global.license.publicKey=<license public key>. - Renewing replaces only
activation.jsonand redeploys. See Renewing: re-activate. - Expired activation: the installer warns and preflight refuses a fresh install. A running install degrades in stages.
GET /v1/license/statusreturnsvalid,expiring,graceorexpired. Alert on anything pastexpiring. - Single-tenant: one license, one organization.
- Legacy single file (
xpander-license/v1):--license-file ./license.jsoncreates a Secret with onelicense.jsonkey, wired throughglobal.license.key. Renew by replacing that file and redeploying.
Next steps
Publish the hosts and sign in
Domains, ingress, DNS, TLS and the first sign-in.
Configure the install
Data tier, executor fleet, skill generation and skill credentials.
Connect your models
Your LLM endpoint, Amazon Bedrock or a local vLLM server.

