Railway
The image’s ENTRYPOINT boots the server from RELIQUARY_TC_CONFIG. Railway builds the repository’s
Dockerfile (railway.json at the repository root selects it), and the server serves HTTP on the
port Railway injects as PORT. Nothing here needs a shell on the host: the config file and the
secrets are supplied as described below.
Environment precedence
Section titled “Environment precedence”For every variable in the table below, the order is environment over config file over default.
An empty variable counts as unset, so PORT="" changes nothing. The overlays run inside the same
config loader the server boots with, so config explain reports which variable supplied each value.
Config file
Section titled “Config file”Put the config file on the volume and point RELIQUARY_TC_CONFIG at it. This page does not ship a
template; the file is yours to write. A working starting point, with the paths used below:
{ "cacheDir": "/data/cache", "vaults": [{ "id": "main", "path": "/data/vault" }], "transports": { "stdio": false, "http": { "host": "0.0.0.0", "allowedHosts": ["vault.example.com"] } }, "auth": { "mode": "jwt", "resource": "https://vault.example.com/mcp", "requireJti": true }}Two settings belong in the file and have no environment overlay:
transports.http.hostmust be0.0.0.0. The default127.0.0.1is reachable only from inside the container, and Railway’s proxy cannot reach it.PORTdeliberately does not change the bind address, so a laptop that setsPORTis not exposed to its network.transports.stdiomust befalse. Railway gives the container no stdin, so the stdio transport would see an immediate end of input on every boot.
Environment variables
Section titled “Environment variables”| Name | Config path | Required | Secret |
|---|---|---|---|
RELIQUARY_TC_CONFIG |
selects the config file | yes | no |
PORT |
transports.http.port; also sets transports.http.enabled to true unless the file sets it to false |
yes on Railway, which injects it | no |
RELIQUARY_TC_PUBLIC_URL |
auth.resource (the origin plus /mcp) and the host appended to transports.http.allowedHosts |
recommended; without it, set auth.resource in the file |
no |
RELIQUARY_TC_VAULT_PATH |
vaults: the path of the one vault |
optional when the file names exactly one vault; refused when it names several | no |
RELIQUARY_TC_CACHE_DIR |
cacheDir |
optional when the file sets it | no |
RELIQUARY_TC_JWT_SECRET |
auth.jwtSecret |
required for auth.mode jwt without a JWKS key source |
yes |
RELIQUARY_TC_JWKS_URI |
auth.jwksUri |
optional; for an external authorization server | no |
RELIQUARY_TC_AS_SETUP_TOKEN |
named by auth.as.setupTokenEnv |
only for claiming the bundled authorization server | yes |
RELIQUARY_TC_AS_RESET_TOKEN |
named by auth.as.resetTokenEnv |
only for a password reset | yes |
the variable named by embeddings.apiKeyEnv |
embeddings.apiKeyEnv |
when a hosted embeddings provider is used | yes |
RELIQUARY_TC_PUBLIC_URL must be https://host or https://host:port, with no path, query or
credentials. Anything else refuses to boot, and the error names the variable without echoing its
value.
PORT must be a whole number from 1 to 65535. A non-loopback host with auth.mode none is
refused at load, so a container that binds 0.0.0.0 without authentication does not start.
Volume
Section titled “Volume”Mount one volume at /data, then point the variables at it:
RELIQUARY_TC_VAULT_PATH=/data/vaultRELIQUARY_TC_CACHE_DIR=/data/cacheRELIQUARY_TC_CONFIG=/data/config.json
On a volume that holds the config file and nothing else, the first boot creates /data/vault itself,
since RELIQUARY_TC_VAULT_PATH names it. A truly empty volume has no /data/config.json, so the first
boot still fails at config resolution: the config file has to be on the volume first. A vault path
written only there is never created.
The container runs as the unprivileged bun user (uid 1000). The volume must be writable by that
user, or the first boot cannot create the cache.
Authentication
Section titled “Authentication”Pick one of the two modes. Both need auth.mode set to jwt in the file.
- Bundled authorization server. Enable
auth.asas described in Turning it on. Its issuer is set in the file. Claim the operator account with the one-time token inRELIQUARY_TC_AS_SETUP_TOKEN, and set a new password withRELIQUARY_TC_AS_RESET_TOKENwhen the host has no shell. Remove both variables once they have done their job. - JWT with a minted token. Set
RELIQUARY_TC_JWT_SECRETin Railway and mint tokens against the same config and secret, withreliquary-tc token mint, on any machine that has them. Keep the secret out of the file.
Embeddings
Section titled “Embeddings”The default embeddings provider, local, runs a model bundled in the image and needs cacheDir,
which the config file already sets. A hosted provider avoids the model download. Name the key’s
environment variable with apiKeyEnv and store the key itself in Railway:
{ "embeddings": { "provider": "openai", "model": "text-embedding-3-small", "dimensions": 1536, "apiKeyEnv": "OPENAI_API_KEY" }}See Embeddings for the other providers.
Git-backed vault (-git image)
Section titled “Git-backed vault (-git image)”To keep the vault on the volume in step with a Git repository, run the -git image instead of the
repository’s Dockerfile. A build from the Dockerfile produces the default image, which has no git
tools. Create the service from the Docker image ghcr.io/the-40-thieves/reliquary-tc:latest-git
(or pin :<version>-git). The -git tags are first published with the next release.
The image’s entrypoint runs git-sync --supervise, which clones the remote into /data/vault on the
first boot, pulls on an interval, and starts the server. The rest of this page still applies: the
same volume at /data, the same config file, and the same PORT and public URL. Add these:
RELIQUARY_TC_VAULT_GIT_URL(required):git@host:org/repo.git,ssh://, orhttps://host/org/repo.git.RELIQUARY_TC_VAULT_GIT_SSH_KEY(secret, required for SSH URLs): the private key contents, a deploy key with write access.RELIQUARY_TC_VAULT_GIT_TOKEN(secret, required for HTTPS URLs): the token for the remote.RELIQUARY_TC_VAULT_GIT_KNOWN_HOSTS(optional):known_hostslines that pin the remote’s host key.RELIQUARY_TC_VAULT_GIT_WEBHOOK_SECRET(optional, secret, at least 32 characters): enables the webhook.
Keep the vault path in the config file at /data/vault, and the cache at /data/cache. The sync state
lives in /data/cache/git-sync. The volume must be writable by the bun user, as above.
The webhook is https://<railway host>/hooks/vault-sync, where <railway host> is the service’s public
domain. Its host must be in allowedHosts: set RELIQUARY_TC_PUBLIC_URL to the public origin, or list
the host in the file, as the health check above also requires. Configure the forge to sign
requests with the webhook secret. Details are in docs/SYNC.md.
What this does not do yet
Section titled “What this does not do yet”- No published template and no Deploy button. Publishing one needs the owner’s Railway account. Render has a Blueprint, see Render.
- Liveness health check.
railway.jsonsetshealthcheckPath: /healthz; it is liveness only, andallowedHostsmust include the Railway hostname or the probe gets 403. - No refusal of public boot for the other modes. Only the non-loopback, unauthenticated combination above is refused.