Deployment¶
deploy/ in this repository is a self-contained deployment of Pystino: the
gateway and its console, alone, with no chat. Everything is readable files you
edit and git pull: a compose file, a documented .env.example, and the
Caddy and Authelia configuration mounted into their stock images.
Want the chat too? Use the Cerea deploy kit (kit/ in the Cerea repository) instead. It is the same variable names and .env conventions, so
moving to it later is: copy this .env and the compose project name over,
./configure again to add the chat's secrets, then docker compose up -d.
Serving chats you don't deploy here¶
A central Pystino can serve chat deployments that live elsewhere: each one
(the satellite preset in the deploy kit) runs only the chat, pointed here
with OPENAI_BASE_URL at this gateway's public /v1, signing users in
against this gateway's identity provider, and reading this gateway's
ledger with each person's own token. Quotas, accounting and model access
stay central, administered here.
Two limits are structural, and worth knowing before you promise anything:
- One chat client.
GATEWAY_OIDC__CHAT_CLIENT_IDis a single value, and/v1/session/announcerefuses tokens issued to any other client. Several satellite chats must share that one client id, with each chat's redirect URI registered on it.GATEWAY_OIDC__ACCEPTED_CLIENTSnames the clients that may sign in at all — add each satellite's identity-provider client if it differs from the bundled one. -
Erasure reaches one chat.
GATEWAY_CHAT__ERASURE_URLis a single URL: deleting a person here erases their data on that chat, not on any other satellite deployment. A person's data on a second chat must be erased there, by its operator. -
GATEWAY_CHAT__ENABLED(defaultfalse) says this gateway serves a chat. The Cerea kit sets it totruewhenever its chat profile is active; Pystino's owndeploy/compose.yamlleaves itfalse. Code agents and local connectors are chat concepts, so when it isfalsetheir group flags are hidden in the console, refused by the admin API (400), left out of thecapabilitiesthat/v1/meand/api/mereport, and the machine-client gate does not apply. The data (groups.code_agents_allowed,groups.local_connectors_allowed) exists either way, so turning the setting on later needs no migration.
What runs¶
deploy/compose.yaml builds nothing: every service names an image, and
add-ons are compose profiles chosen by COMPOSE_PROFILES in .env.
| Service | Profile | What it is |
|---|---|---|
postgres |
always | the gateway's database (with pgvector) |
bootstrap |
always | one-shot on every up: the bundled Authelia's signing key and first user — created only if absent, never overwritten |
valkey, migrate, gateway |
always | the gateway and its console; there is no gateway profile here, because the gateway is the only thing this deployment serves |
proxy |
always | stock Caddy, reading deploy/caddy/ (mounted read-only) |
authelia |
authelia |
the bundled identity provider at /authelia, stock image, reading deploy/authelia/ |
redaction, extractor |
redaction |
Presidio, pattern-only in the published image |
Setting up¶
cp deploy/.env.example deploy/.env
chmod 600 deploy/.env
$EDITOR deploy/.env # every variable is explained where it stands
cd deploy && docker compose up -d --wait
There is no ./configure here (unlike the full stack) — filling in .env by
hand is the point of a self-contained, gateway-only deployment: nothing to
read but a compose file and a .env.example. Every secret names the command
that mints it (openssl rand -hex 32, or, for the bundled Authelia's
digests, docker run --rm authelia/authelia:4.39.22 authelia crypto hash
generate …).
The first person to sign in with the email in PYSTINO_BOOTSTRAP_ADMIN_EMAIL
becomes an administrator. That bootstrap is honoured once per deployment:
once any administrator exists it never fires again, so deactivating every
administrator does not re-arm it. With the bundled Authelia, the
first account's password is whatever AUTHELIA_ADMIN_PASSWORD_DIGEST is a
digest of — you chose it when you minted the digest, so there is nothing to
read back afterwards.
Images. The gateway and redaction images are public on the GitHub
Container Registry (ghcr.io/paoloviviani/pystino-gateway,
pystino-redaction), at the version deploy/compose.yaml pins; docker compose
pull needs no login.
TLS modes¶
Three, chosen by TLS_MODE (deploy/.env.example has the exact
SITE_ADDRESS/TLS_DIRECTIVE pair each needs):
acme— a public DNS name, ports 80 and 443 reachable: Caddy gets a Let's Encrypt certificate.internal— Caddy's own CA; browsers warn. Development and private networks.upstream— TLS ends in front of this host (a load balancer, an edge proxy such as a NetBird gateway) and reaches Caddy as plain HTTP onHTTP_PORT. Point the edge athttp://<this host>:<HTTP_PORT>, and have it: pass the originalHost; forward WebSocket upgrades; and not buffer responses, because streamed completions need that. The proxy trustsX-Forwarded-Foronly fromTRUSTED_PROXIES(defaultprivate_ranges) — widen it to the edge's own range to see real client addresses in logs.
Identity¶
People sign in through an OpenID Connect provider — the bundled Authelia or
your own (OIDC_ISSUER, OIDC_INTERNAL_BASE_URL, OIDC_CONSOLE_CLIENT_ID/
_SECRET). There is no password door. One provider at a time, named by the
OIDC_* variables in deploy/.env; the gateway re-reads them at every
start and the console shows the provider read-only. To combine several
sources of users, federate them in your own IdP (Keycloak, Authentik and the
like, deployed separately) and point the stack at it. Who is an administrator, how groups sync,
and linking accounts by email are all environment settings too — see
Identity for every variable and what the gateway
checks at startup.
Servers never call the public origin: the gateway reaches the IdP at its
internal URL (OIDC_INTERNAL_BASE_URL, http://authelia:9091/authelia for
the bundled one), with forwarded headers naming the public issuer. There is no
CA bundle to maintain.
The bundled Authelia's accounts are managed from the console's Users page; see Bundled accounts. Groups and roles live in the console.
Break-glass, when nobody can administer or the IdP is gone, is one command; its preconditions, what it changes and what to do if the deployment itself may be compromised are in Identity: break-glass.
Upgrading¶
A release that changes deploy/caddy/ or deploy/authelia/ also changes
their service's pystino.config-rev label in deploy/compose.yaml
(deploy/pin.py keeps that label in step; CI runs deploy/pin.py --check),
so up -d recreates the proxy or Authelia exactly when their mounted files
changed — compose otherwise never notices a mounted file changing. Snapshot
the volumes first: down-migrations are not promised.
Editing deploy/caddy/ or deploy/authelia/ yourself, without a release?
docker compose up -d --force-recreate proxy authelia, or run
deploy/pin.py and commit the result.
Adding a component¶
A Caddy snippet for deploy/proxy.d/ (git-ignored, so git pull never
conflicts with yours) — a mounted directory, so adding a file never meets
the stale-inode trap of a single-file mount. Reload with
docker compose exec proxy caddy reload --config /etc/caddy/Caddyfile.
A provider catalogue override for deploy/providers.d/ (YAML, git-ignored
too) — see Provider catalogue. The gateway reads it
once at startup, so restart it after a change.
Backups¶
deploy/.env (every secret: GATEWAY_SECRET_KEY decrypts provider keys,
AUTHELIA_STORAGE_KEY every user's subject — neither can be regenerated
without losing data), and the volumes postgres-data, authelia-config and
authelia-data.