wyolet/relay) and GHCR (ghcr.io/wyolet/relay), both built for linux/amd64 only.
Version tags drop the leading
v: release v0.11.6 is wyolet/relay:0.11.6. Pin a version anywhere you care about what runs; latest and standalone move with every release.
Both images carry the catalog at /catalog and set RELAY_CATALOG_DIR=/catalog and RELAY_AUTO_SEED_IF_EMPTY=1, so an empty database is seeded with the catalog on first boot. To move to a newer catalog without a new image, see Pinning a catalog version.
Every container listens on two ports: 8080 for the inference API and 8081 for the admin UI and control API. Variables not covered here are in Configuration.
Standalone image
/var/lib/postgresql/data on first run, starts it on 127.0.0.1 inside the container (it is never exposed), then starts relay against it. The image also sets RELAY_CONTROL_PORT=8081, RELAY_INFERENCE_API_URL=http://localhost:8080 and RELAY_COOKIE_SECURE=false, so the admin UI works over plain HTTP on localhost. Override any of them with -e.
Postgres writes its log to postgres.log on the data volume, so docker logs shows only relay. Set RELAY_PG_LOG=stdout to stream the Postgres log to the container output instead.
First-boot credentials
On an empty data directory the entrypoint generates three things and saves them on the volume:RELAY_MASTER_KEY, which wraps the keys stored secrets are encrypted under (see Stored secrets and key rotation).RELAY_ADMIN_TOKEN, a bearer token for the control API.- A user
adminwith a random password, created only when the config directory holds no files yet.
-e is used as given and never overwritten or regenerated. To bring your own users, mount a directory of kind: User files and point RELAY_CONFIG_DIR at it; the entrypoint then creates no admin user.
Persisting data
Without a volume, every new container starts from an empty data directory and generates a new master key, new credentials and a freshly seeded catalog.docker stop followed by docker start on the same container keeps its state; only a new docker run without a volume starts clean.
With the volume above, everything relay needs to restart lives in one directory:
Retrieving or pinning the master key
After first boot, read the generated values back from the running container or from the volume:Reaching it from other machines
The image’s defaults assumelocalhost. Behind a TLS-terminating proxy, set RELAY_COOKIE_SECURE=true so the session cookie is marked Secure, and set RELAY_INFERENCE_API_URL to the public inference URL the admin UI should call (for example https://api.relay.example.com). The standalone image runs one Postgres in one container; for more than one replica, use the lean image.
Compose stack
The repository’s rootdocker-compose.yml runs the lean image with its own Postgres 16 and Valkey 8, each on a named volume (pg-data, valkey-data):
admin with RELAY_ADMIN_PASSWORD. Compose reads these variables from .env next to the file, or from the shell:
The stack runs relay with
RELAY_STATE_BACKEND=redis against the bundled Valkey, so sessions, rate-limit counters and breaker state survive a relay restart. As with the standalone image, usage events go to a file inside the relay container until you switch the usage-logging section to postgres.
docker compose down keeps the volumes; docker compose down -v deletes them, and with them the catalog, your configuration and every stored host key.
Lean image against your own stores
RELAY_PG_DSNis the only required variable. Give relay a database of its own. Pending migrations run at boot under a Postgres advisory lock, so several replicas can start at once; see Upgrading.RELAY_MASTER_KEYmust be the same on every replica and stable across restarts.- With more than one replica, set
RELAY_STATE_BACKEND=redis. With the defaultmemory, every process keeps its own sessions, rate-limit counters and breaker state. Catalog changes reach every replica through PostgresLISTEN/NOTIFYwithout further configuration, usually within about a second. - Batch payloads are files under
RELAY_BATCH_PAYLOAD_DIR. Any replica may run a batch item or serve its results, so with more than one replica that directory must be storage every replica shares. - Keep the control port off the public network: publish only
8080, or bind the control listener to a private address withRELAY_CONTROL_LISTEN_HOST. - Probe
/livezfor liveness and/healthzfor readiness on the inference port./healthzanswers503until Postgres responds and the catalog has loaded.
RELAY_ADMIN_TOKEN as a bearer token, or mount a kind: User file into RELAY_CONFIG_DIR and read the password from the environment so the file holds no secret:
users/admin.yaml