Install and deploy
This guide is for deployers. If somebody has already given you a Gateway Key, go to Use an existing Gateway Key. For listener, timeout, TLS, logging, and security settings, see Configuration reference.
Choose an installation method
Section titled “Choose an installation method”| Environment | Best for | Method |
|---|---|---|
| Docker | One host, NAS, development, or an existing container host | Official multi-architecture image; run init explicitly once |
| Kubernetes | Existing cluster and persistent volumes | One initialization Job, then one-replica Deployment |
| macOS | Local evaluation and development | Download the darwin-arm64 or darwin-amd64 release |
| Linux | Bare metal or VM | Download linux-arm64 or linux-amd64; optionally manage with systemd |
| Windows | Windows 10/11 or Windows Server | Linux container; WSL2 may follow the Linux procedure |
Commands below pin the current GitHub release, v0.8.4. Production container deployments should pin the reviewed image digest.
What every environment must know
Section titled “What every environment must know”Halro is one Go binary with an Admin Console; it needs no PostgreSQL, Redis, or separate frontend. Preserve three things separately:
config.yaml: listeners, TLS, storage locations, and security boundaries;- the data directory: database, Ledger, Audit chain, and Usage files;
- the Master Key: required to decrypt Provider credentials, excluded from encrypted backups, and stored separately from both data and Backup Keys.
Initialization, doctor, backup, and restore are offline commands: stop Halro first. Never copy only halro.db, and never combine database, Ledger, Audit, or Usage directories from different points in time.
Defaults bind only to 127.0.0.1. For remote access, preferably terminate TLS at Caddy, Traefik, or nginx while Halro remains on loopback. Never expose plaintext Gateway, Admin, or Metrics listeners to the public Internet.
Download and start
Section titled “Download and start”On Apple Silicon:
VERSION=v0.8.4ARTIFACT=halro-darwin-arm64.tar.gzBASE_URL="https://github.com/akz142857/Halro/releases/download/$VERSION"curl -fLO "$BASE_URL/$ARTIFACT"curl -fLO "$BASE_URL/$ARTIFACT.sigstore.json"curl -fLO "$BASE_URL/checksums.txt"curl -fLO "$BASE_URL/checksums.txt.sigstore.json"Install cosign 2.2 or newer, then verify the checksum list’s release identity, the archive digest, and the archive bundle:
COSIGN_IDENTITY="https://github.com/akz142857/Halro/.github/workflows/release.yml@refs/heads/main"COSIGN_ISSUER="https://token.actions.githubusercontent.com"cosign verify-blob --certificate-identity "$COSIGN_IDENTITY" --certificate-oidc-issuer "$COSIGN_ISSUER" --bundle checksums.txt.sigstore.json checksums.txtgrep "[[:space:]]$ARTIFACT$" checksums.txt | shasum -a 256 --checkcosign verify-blob --certificate-identity "$COSIGN_IDENTITY" --certificate-oidc-issuer "$COSIGN_ISSUER" --bundle "$ARTIFACT.sigstore.json" "$ARTIFACT"tar -xzf "$ARTIFACT"cd halro-darwin-arm64./halro version./halro start --config ./config.yamlOn Intel, replace arm64 with amd64. start creates a loopback-only configuration, Master Key, and data directory when absent. Open http://127.0.0.1:8081/admin/setup. Only after all verification passes, clear a blocking quarantine attribute with xattr -d com.apple.quarantine ./halro.
Allow access from other machines
Section titled “Allow access from other machines”Use a fixed binary, configuration, data directory, and Master Key under launchd or another process manager, with a TLS reverse proxy in front. When Admin is proxied, set:
admin: external_origin: https://halro.example.comsecurity: trust_proxy_headers: true trusted_proxy_cidrs: ["127.0.0.1/32"]Use the proxy’s real source CIDR; do not copy loopback when the proxy runs elsewhere.
Download and test
Section titled “Download and test”On x86-64:
VERSION=v0.8.4ARTIFACT=halro-linux-amd64.tar.gzBASE_URL="https://github.com/akz142857/Halro/releases/download/$VERSION"curl -fLO "$BASE_URL/$ARTIFACT"curl -fLO "$BASE_URL/$ARTIFACT.sigstore.json"curl -fLO "$BASE_URL/checksums.txt"curl -fLO "$BASE_URL/checksums.txt.sigstore.json"COSIGN_IDENTITY="https://github.com/akz142857/Halro/.github/workflows/release.yml@refs/heads/main"COSIGN_ISSUER="https://token.actions.githubusercontent.com"cosign verify-blob --certificate-identity "$COSIGN_IDENTITY" --certificate-oidc-issuer "$COSIGN_ISSUER" --bundle checksums.txt.sigstore.json checksums.txtgrep "[[:space:]]$ARTIFACT$" checksums.txt | sha256sum --checkcosign verify-blob --certificate-identity "$COSIGN_IDENTITY" --certificate-oidc-issuer "$COSIGN_ISSUER" --bundle "$ARTIFACT.sigstore.json" "$ARTIFACT"tar -xzf "$ARTIFACT"cd halro-linux-amd64./halro version./halro start --config ./config.yamlUse arm64 on ARM64. For a remote server, tunnel Admin with ssh -L 8081:127.0.0.1:8081 user@your-server, then open the loopback setup URL locally.
Run permanently with systemd
Section titled “Run permanently with systemd”sudo useradd --system --home /var/lib/halro --shell /usr/sbin/nologin halrosudo install -m 0755 ./halro /usr/local/bin/halrosudo install -d -o halro -g halro -m 0700 /var/lib/halro /var/lib/halro-secretssudo install -d -o root -g halro -m 0750 /etc/halrocurl -o /tmp/halro-config.yaml https://raw.githubusercontent.com/akz142857/Halro/v0.8.4/configs/config.example.yamlsudo install -o root -g halro -m 0640 /tmp/halro-config.yaml /etc/halro/config.yamlUse fixed storage paths:
storage: data_dir: /var/lib/halro/data master_key: mode: file file: /var/lib/halro-secrets/master.keyValidate and initialize once:
sudo -u halro /usr/local/bin/halro config check --config /etc/halro/config.yamlsudo -u halro /usr/local/bin/halro init --config /etc/halro/config.yamlCreate /etc/systemd/system/halro.service:
[Unit]Description=Halro LLM GatewayAfter=network-online.targetWants=network-online.target[Service]User=halroGroup=halroExecStart=/usr/local/bin/halro serve --config /etc/halro/config.yamlExecReload=/bin/kill -HUP $MAINPIDRestart=on-failureRestartSec=5sTimeoutStopSec=150sNoNewPrivileges=truePrivateTmp=trueProtectSystem=strictReadWritePaths=/var/lib/halro /var/lib/halro-secrets[Install]WantedBy=multi-user.targetsudo systemctl daemon-reloadsudo systemctl enable --now halrosudo systemctl status halrosudo journalctl -u halro -fKeep all listeners local until a TLS reverse proxy and correct external origin/trusted proxy range are configured.
Windows
Section titled “Windows”Windows 10/11: Docker Desktop
Section titled “Windows 10/11: Docker Desktop”Use Docker Desktop 4.34 or newer with WSL2, Linux containers, and Host networking. Host networking and Enhanced Container Isolation cannot both be enabled; use WSL2 or a Linux VM if ECI must remain enabled.
New-Item -ItemType Directory -Force C:\halro | Out-NullSet-Location C:\halrocurl.exe -o config.yaml https://raw.githubusercontent.com/akz142857/Halro/v0.8.4/configs/config.example.yamldocker compose run --rm halro-initdocker compose up -d halrodocker compose cp halro:/run/secrets/halro-master.key .\halro-master.keyif ((Get-Item .\halro-master.key).Length -ne 32) { throw "Master Key is not 32 bytes" }icacls .\halro-master.key /inheritance:ricacls .\halro-master.key /grant:r "$($env:USERNAME):(R)"Move the exported key into independent secret management or offline media. Use a Linux VM for Windows Server production.
Follow the Linux procedure in Ubuntu or another WSL2 distribution. Prefer a Linux VM or container host for production because WSL2 lifecycle, network, and mount behavior are development-oriented.
Docker
Section titled “Docker”The accepted GHCR image is v0.8.4 for linux/amd64 and linux/arm64. It is distroless, has no shell, runs as uid/gid 65532, defaults to serve, and serve never initializes automatically.
1. Prepare a complete configuration
Section titled “1. Prepare a complete configuration”curl -o config.yaml https://raw.githubusercontent.com/akz142857/Halro/v0.8.4/configs/config.example.yamlKeep the full file and change at least:
storage: data_dir: "/var/lib/halro/data" master_key: mode: file file: "/run/secrets/halro-master.key"data_dir must be a child of the mount, not /var/lib/halro, because Halro creates an atomic publication lock beside it.
2. Create the Compose file
Section titled “2. Create the Compose file”Save the following as compose.yaml:
services: halro-init: image: ghcr.io/akz142857/halro:v0.8.4 user: "65532:65532" command: ["init", "--config", "/etc/halro/config.yaml"] network_mode: host volumes: - ./config.yaml:/etc/halro/config.yaml:ro - halro-data:/var/lib/halro - halro-secrets:/run/secrets halro: image: ghcr.io/akz142857/halro:v0.8.4 user: "65532:65532" command: ["serve", "--config", "/etc/halro/config.yaml"] network_mode: host restart: unless-stopped volumes: - ./config.yaml:/etc/halro/config.yaml:ro - halro-data:/var/lib/halro - halro-secrets:/run/secrets:rovolumes: halro-data: halro-secrets:3. Initialize once and start
Section titled “3. Initialize once and start”docker compose run --rm halro-initdocker compose up -d halrodocker compose psdocker compose cp halro:/run/secrets/halro-master.key ./halro-master.keychmod 600 ./halro-master.keytest "$(wc -c < ./halro-master.key | tr -d ' ')" -eq 32Move the key to independent secret management. Use docker compose logs -f halro for logs. Host networking keeps the three loopback listeners on host loopback. Container health does not prove that external ports, certificates, proxy, or firewall are correct; test from the real client location.
4. Configure HTTPS certificates for production
Section titled “4. Configure HTTPS certificates for production”The recommended path is client → HTTPS reverse proxy → 127.0.0.1:8080/8081 → Halro. Keep tls.enabled: false and set the actual Admin origin, required MFA, and exact proxy CIDRs:
tls: {enabled: false, certificates: []}admin: external_origin: "https://admin.example.com" mfa_policy: "required"security: trust_proxy_headers: true trusted_proxy_cidrs: ["127.0.0.1/32", "::1/128"]Never use 0.0.0.0/0. Restrict Admin by VPN, firewall, identity proxy, or IP allowlist. Alternatively, let Halro terminate TLS directly:
server: gateway_listen: "0.0.0.0:8080" admin_listen: "0.0.0.0:8081" metrics_listen: "127.0.0.1:9090"tls: enabled: true certificates: - cert_file: /etc/halro/tls/fullchain.pem key_file: /etc/halro/tls/privkey.pemadmin: external_origin: "https://admin.example.com:8081" mfa_policy: "required"Mount ./tls:/etc/halro/tls:ro into both services. Keep private keys out of images and Git. SIGHUP reloads contents at unchanged paths; path or list changes require restart.
5. Deliver the first setup credential safely
Section titled “5. Deliver the first setup credential safely”When admin.external_origin is non-empty, Admin is non-loopback, or admin.setup_token_file is explicitly configured, setup requires a one-time Token. Generate a read-only file with the exact Halro version being deployed:
halro admin setup-token generate \ --ttl 30m \ --output /secure/path/setup-tokenThe command exclusively creates a 0600 file, refuses to overwrite a path, and never prints the Token to stdout, stderr, or logs. Send the file to secret management or mount it in Compose:
admin: setup_token_file: /run/secrets/halro/setup-token setup_token_ttl: 30mservices: halro: command: ["serve", "--config", "/etc/halro/config.yaml"] volumes: - ./config.yaml:/etc/halro/config.yaml:ro - /secure/path/setup-token:/run/secrets/halro/setup-token:roGive the envelope’s first line to the setup approver through the organization’s existing secret channel; do not grant log or container-exec access. The absolute expiry does not extend after restart. A missing, malformed, or expired file makes serve fail closed without log fallback.
Unattended deployment uses offline, idempotent admin bootstrap --if-needed --operation-id ... --password-file .... After administrator creation, remove the workload’s mount dependency before revoking the Secret; Halro no longer reads the file once an administrator exists. See Production Admin bootstrap and secret lifecycle for the full Docker/Kubernetes state machine, Audit verification, and compromise recovery.
Kubernetes
Section titled “Kubernetes”Official key_slots bootstrap flow
Section titled “Official key_slots bootstrap flow”The production Kubernetes/GitOps install state machine shipped in v0.8.3, and the v0.8.4 image carries it. The automated Job path currently supports only storage.master_key.mode: key_slots:
PVC / config / Secrets / default-deny NetworkPolicy ↓One-shot Bootstrap Job (init --if-needed → admin bootstrap) ↓Verification Job (doctor → audit verify) ↓Delete Jobs and bootstrap Secret; revoke temporary KMS identity ↓Start the one-replica production Deployment with halro servekubectl -n halro create secret generic halro-admin-bootstrap \ --from-file=admin-password=/secure/path/admin-passwordkubectl -n halro apply -f deploy/kubernetes/halro-bootstrap-job.yamlkubectl -n halro wait --for=condition=complete \ job/halro-bootstrap-install-id --timeout=15mkubectl -n halro delete job halro-bootstrap-install-id --wait=truekubectl -n halro apply -f deploy/kubernetes/halro-bootstrap-verify-job.yamlkubectl -n halro wait --for=condition=complete \ job/halro-bootstrap-verify-install-id --timeout=10mBootstrap/Verification Jobs and Runtime never overlap. They use a dedicated ServiceAccount, temporary KMS Lifecycle identity, and install-only GitOps lifecycle; Runtime needs no Secret API, log, or exec permission. See Production Admin bootstrap and secret lifecycle for the full security and recovery boundary.
v0.8.4: manual File Master Key template
Section titled “v0.8.4: manual File Master Key template”The following uses the accepted v0.8.4 image, preferably pinned by reviewed manifest digest. It is a manual single-node File Master Key template, not the new official automated Bootstrap Job. A secure post-generation export protocol for automating File Master Key custody remains future work.
The minimal design uses separate halro-data and halro-key PVCs, both backed up outside the cluster. AWS KMS production deployments may start from the hardened deploy/kubernetes/halro-aws-kms.yaml template in the main repository.
1. Prepare configuration and namespace
Section titled “1. Prepare configuration and namespace”server: gateway_listen: "0.0.0.0:8080"storage: data_dir: "/var/lib/halro/data" master_key: mode: file file: "/var/lib/halro-secrets/master.key"kubectl create namespace halrokubectl -n halro create secret generic halro-config --from-file=config.yaml=./config.yaml2. Create persistent volumes and initialize once
Section titled “2. Create persistent volumes and initialize once”Create separate ReadWriteOnce PVCs (20Gi data and 1Gi key), then run a one-shot Job as uid/gid 65532 with args: ["init", "--config", "/etc/halro/config.yaml"]. Mount the config Secret read-only at /etc/halro, data at /var/lib/halro, and key PVC at /var/lib/halro-secrets.
kubectl apply -f halro-storage-init.yamlkubectl -n halro wait --for=condition=complete job/halro-init --timeout=120skubectl -n halro logs job/halro-initIf initialization fails, inspect logs. Do not repeatedly delete keys or data; confirm the PVCs are new and empty, or restore a matching data-and-key set.
3. Back up the Master Key outside the cluster
Section titled “3. Back up the Master Key outside the cluster”Before starting Halro, mount halro-key read-only into a temporary, reviewed utility Pod without a service-account token. Copy and verify the key, then delete the Pod:
kubectl apply -f halro-key-export.yamlkubectl -n halro wait --for=condition=Ready pod/halro-key-export --timeout=120skubectl -n halro cp halro-key-export:/key/master.key ./halro-master.keychmod 600 ./halro-master.keytest "$(wc -c < ./halro-master.key | tr -d ' ')" -eq 32kubectl -n halro delete pod halro-key-exportMove it to external secret management or offline media and configure an independent snapshot for the key PVC before continuing.
4. Deploy exactly one replica
Section titled “4. Deploy exactly one replica”The Deployment must specify replicas: 1, strategy.type: Recreate, a 150-second termination grace period, uid/gid/fsGroup 65532, dropped capabilities, read-only root filesystem, and separate read-only config/key mounts. The command is serve --config /etc/halro/config.yaml -allow-insecure-public-listen; the flag acknowledges that cluster networking, not Halro TLS, protects the internal listener. Use exec readiness and liveness probes against loopback.
kubectl apply -f halro.yamlkubectl -n halro rollout status deployment/halrokubectl -n halro get podskubectl -n halro logs deployment/halrokubectl -n halro port-forward service/halro 8080:8080kubectl -n halro port-forward deployment/halro 8081:8081Open http://127.0.0.1:8081/admin/setup, then stop the Admin port-forward. Do not expose Admin as a public Service. Terminate TLS at Ingress/Gateway, plan separate restricted Admin and Metrics entry points, and pin the image as ghcr.io/akz142857/halro@sha256:....
First setup and acceptance
Section titled “First setup and acceptance”Create resources in this order:
Credential → Provider → Deployment → Route → Project → Gateway KeyFollow First Admin Console setup for model discovery, capability evidence, Price Version, Policy, Key scopes, and negative checks. Store the one-time Gateway Key in application secret management.
curl -fsS http://127.0.0.1:8080/health/readyexport HALRO_GATEWAY_KEY='gw_...'curl http://127.0.0.1:8080/v1/chat/completions \ -H "Authorization: Bearer $HALRO_GATEWAY_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"chat","max_tokens":64,"messages":[{"role":"user","content":"Hello"}]}'Before production: pin the release or digest, enforce TLS and network controls, back up the Master Key independently, then stop Halro and complete an encrypted backup, verification, and isolated restore drill.