Deployment¶
Three ways to run humpyard: a release binary, the container image, or the macOS launchd service (which uses the binary). Releases are described at the end.
Whatever you use, you need a config file (examples/quickstart.toml is
the smallest; examples/config.toml adds failover across two providers; check
one with humpyard check-config) and the provider API keys in environment variables named
by the config (api_key_env). Keys are never stored in the config.
First config: humpyard init¶
humpyard init (0.10.0 and later) writes a working config.toml for you. Export your provider key, then name the provider:
export GROQ_API_KEY=...
humpyard init --provider groq --out config.toml
humpyard serve --config config.toml
- Built-in providers are
groq,openrouterandnvidia;--custom NAME,BASE_URL,KEY_ENV,MODELadds any OpenAI-compatible endpoint. Run it without--providerin a terminal to be asked. - Each key is read only from the environment variable the config names. It is never printed, stored or asked for.
- It lists each provider's models and writes a model only if the provider lists it and it answers one small request, so a retired model never reaches the file. A provider whose variable is unset is written unchecked.
- The file listens on
127.0.0.1and has two targets,fastanddeep. It sends one test request through the result and prints the reply and the target that served it. An existing file is never overwritten. - Pass
--out -to print the configuration to standard output instead of creating a file, with progress messages routed to standard error. An existing file is never checked in that mode. --listen 0.0.0.0:8080(anything not on loopback) also creates a virtual key with a daily token budget and prints it once; the file holds only its hash.
Release binary¶
Each GitHub release has tarballs for macOS arm64
(aarch64-apple-darwin) and Linux x86_64 / arm64 (*-unknown-linux-gnu), plus SHA256SUMS.
shasum -a 256 -c SHA256SUMS --ignore-missing # sha256sum -c on Linux
tar xzf humpyard-v0.12.0-aarch64-apple-darwin.tar.gz
install -m 755 humpyard-v0.12.0-aarch64-apple-darwin/humpyard ~/.local/bin/
- The binaries are not signed. If macOS quarantines a downloaded one:
xattr -d com.apple.quarantine ~/.local/bin/humpyard. - Linux binaries are built on Ubuntu 24.04 and need glibc 2.39 or newer. On an older distribution use the container image.
Container image¶
docker run -d --name humpyard -p 127.0.0.1:8080:8080 \
-v "$PWD/config.toml:/etc/humpyard/config.toml:ro" \
-v humpyard-data:/data \
-e GROQ_API_KEY -e OPENROUTER_API_KEY \
ghcr.io/gschaetz/humpyard:0.12.0
In the config, set listen = "0.0.0.0:8080" (inside the container the default loopback address is
unreachable; quickstart.toml already does) and the ledger path to /data/ledger.db so usage
survives upgrades. The command publishes the port on 127.0.0.1 only; drop that prefix (and add
virtual keys first) to reach it from other machines.
- The image is distroless, runs as a non-root user (65532), has no shell, config or secrets, and is
about 70 MB.
docker stoptriggers the graceful shutdown (the default stop timeout of 10 s is shorter than the 30 s grace period; usedocker stop -t 45or lowershutdown_grace_secs). - There is no built-in container health check (no shell or curl in the image); probe
GET /healthzfrom outside. - Tags:
X.Y.Zfor every release;latestonly moves for releases at 1.0 or later. - A bind-mounted
/datamust be writable by uid 65532. - The package is public (verified with an anonymous pull of 0.1.0): the image's
org.opencontainers.image.sourcelabel links it to this public repository. Each architecture carries a build provenance attestation (buildx default), shown as anunknown/unknownentry indocker buildx imagetools inspect.
macOS service (launchd)¶
Layout (all under your home directory, nothing needs root):
| Path | What |
|---|---|
~/.local/bin/humpyard |
the binary |
~/.config/humpyard/config.toml |
the config |
~/.config/humpyard/env |
provider keys, chmod 600: lines like OPENCODE_API_KEY=... or export ... |
~/Library/Logs/humpyard.log |
stdout and stderr |
deploy/launchd/install.sh # checks the files exist, installs the agent, starts it
tail -f ~/Library/Logs/humpyard.log
launchctl bootout gui/$(id -u)/dev.humpyard.gateway # stop and unload
The service starts at login, restarts if it dies (10 s throttle) and loads the env file in the
shell that launches it, so keys never appear in the plist, in launchctl print or in logs.
ExitTimeOut is 50 s: launchd waits that long after SIGTERM, which covers the default 30 s grace
period plus the ledger flush. Change the environment file, then
launchctl kickstart -k gui/$(id -u)/dev.humpyard.gateway to restart.
Reloading the configuration¶
Edits to routes, selector rules, targets, providers, keys, budgets and health settings apply to a
running gateway without a restart; requests already running (streams included) finish on the
configuration they started with. A file that does not load, validate or build is never applied:
the previous configuration keeps serving and the failure is logged and shown in GET /v1/health
(reload.ok, reload.message, and config.fingerprint for the version in use).
| Environment | How to reload |
|---|---|
| any | set reload_poll_secs = 15 (top level, 0 = off): the file is re-read on that interval and reloaded when its content changed. A broken file is attempted once per change. |
| macOS binary or launchd service | kill -HUP $(pgrep -x humpyard) (the service replaces its shell with the binary, so this finds the right process) |
| Docker | docker kill -s HUP humpyard (a bind-mounted file must be edited in place, or the container restarted, if your editor replaces the file) |
| Kubernetes | mount the config from a ConfigMap and set reload_poll_secs: the image has no shell or kill, and the kubelet refreshes the mounted file on its own schedule (up to about a minute), after which the next poll picks it up (allow a minute or two for the kubelet) |
Not reloadable, and refused as a whole with a message naming the setting: listen, the ledger
path, shutdown_grace_secs, reload_poll_secs, and adding the first key to (or removing the last
from) a keyless gateway. Provider API keys come from the process environment, which a running
process cannot change, so rotating one needs a restart. Carried over by a reload: the health of
endpoints that did not change, budget counters, and the session state of unchanged routes.
Operating notes¶
GET /healthzanswers 200 while the process is up;GET /v1/health(with a key when the gateway has keys) shows which provider endpoints are being skipped (routing.md).- Ledger and budgets live in the SQLite file; back it up by copying the
.dbtogether with its-walfile, or stop the service first. - Upgrading: replace the binary (or pull the new tag) and restart. The ledger schema is created on open; before 1.0, read the release notes for schema changes.
Releases (maintainers)¶
Releases are made only by pushing a version tag (ADR 0010); nothing publishes from branches or pull requests.
- In a PR, bump
versioninCargo.tomland merge it. - From an up-to-date
main:git tag vX.Y.Z && git push origin vX.Y.Z. - The
releaseworkflow verifies that the tag equals theCargo.tomlversion and that the commit is onmain, builds each target on a runner of its own architecture, builds the two image architectures, and only when all of that has succeeded creates the GitHub release (pre-release while the version is 0.x) and the multi-architecture image manifest.
A failed run before the last job publishes nothing public except per-architecture image tags
(X.Y.Z-amd64, X.Y.Z-arm64) in the registry. To retry, delete the tag and push it again.