Choose a path
- Compose — single host, simplest ops. Default for labs and many production gateways.
- Swarm — multi-node cluster with overlay networking. Still one gateway replica; stateful volumes stay on one data node.
- install.sh — downloads stack files, generates gateway.env, and deploys either mode for you. See /docs/enterprise/install.
This page covers the Gateway-only stack: Gateway, Postgres, and optional NATS. Download the Compose files and environment example from https://zenithgw.com/install/.
Stack shape
| Service | Role |
|---|---|
| zenith | OpenAI-compatible /v1, local /admin, activation client |
| postgres | Gateway state: licenses, pools, encrypted provider keys |
| nats (optional) | Telemetry / cluster rate coordination via JetStream |
| Volume | Mount | Why it matters |
|---|---|---|
| gateway-data | /var/lib/zenith | master.key — must survive recreate |
| pg-data | /var/lib/postgresql/data | Gateway database |
| nats-data | /data/jetstream | JetStream when NATS is enabled |
Production default publishes only the gateway host port (9090). Postgres and NATS stay on the Compose/Swarm network unless you intentionally publish them.
Prerequisites
- Docker Engine with the Compose plugin (Compose path) or an initialized Swarm (Swarm path).
- Pinned CalVer image from ghcr.io/vestavision/zenith-gateway — avoid floating :latest in production.
- gateway.env derived from https://zenithgw.com/install/gateway.env.example with unique secrets.
- HTTPS issuer URL for production activation (ZENITH_ISSUER_URL).
gateway.env feeds ${VAR} interpolation in the Compose/Swarm YAML and supplies values listed under the zenith service environment. A key present only in the file but omitted from that environment list never reaches the process. Required: ZENITH_ADMIN_SESSION_SECRET, ZENITH_BOOTSTRAP_API_KEY, POSTGRES_PASSWORD / ZENITH_POSTGRES_DSN. Brand, SMTP, public base URL, and similar product settings are configured in /admin/organization/settings after activation — not via env.
Compose
mkdir -p ~/zenith-gateway && cd ~/zenith-gateway
curl -fsSL https://zenithgw.com/install/compose.yml -o compose.yml
curl -fsSL https://zenithgw.com/install/gateway.env.example -o gateway.env.example
cp gateway.env.example gateway.env
docker compose --env-file gateway.env \
-f compose.yml \
--profile nats up -ddocker compose --env-file gateway.env \
-f compose.yml --profile nats downSwarm
mkdir -p ~/zenith-gateway && cd ~/zenith-gateway
curl -fsSL https://zenithgw.com/install/compose.swarm.yml \
-o compose.swarm.yml
curl -fsSL https://zenithgw.com/install/gateway.env.example -o gateway.env.example
cp gateway.env.example gateway.env
docker swarm init
set -a
source ./gateway.env
set +a
docker stack deploy -c compose.swarm.yml zenith-gateway
docker stack services zenith-gateway
docker service logs zenith-gateway_zenithPrefer curl -fsSL https://zenithgw.com/install.sh | bash -s -- --yes --mode swarm --swarm-data-node YOUR_NODE when you want env generation and verify handled for you. Add --publish-deps only when you need host Postgres/NATS ports.
# Remove stack (keeps volumes unless you purge them separately)
docker stack rm zenith-gatewayHard rules
- Secrets are deploy-time env interpolation from the shell (or install.sh sourcing gateway.env). The published Swarm stack does not create Docker configs/secrets — wire Swarm secrets yourself if policy requires it.
- Changing POSTGRES_PASSWORD after first deploy requires deleting the Postgres volume before redeploy — the volume keeps the original DB password.
- Demo/lab wipe: delete gateway-data and pg-data together (and optionally nats-data). Wiping only one side leaves an unreadable or orphaned state.
Upgrade
- Pin ZENITH_GATEWAY_IMAGE (or install.sh --tag) to the new CalVer release.
- Compose: pull and up -d with the same gateway.env and volumes.
- Swarm: export gateway.env and docker stack deploy again; leave ZENITH_SWARM_DATA_NODE_HOSTNAME unchanged.
- Version or instance ID changes may require re-activation — confirm /readyz after upgrade.
Backup & recovery
Always back up Postgres and gateway-data together. Losing master.key while keeping the database (or the reverse) breaks encrypted provider/OIDC secrets. On mismatch the process stays up in recovery at /admin/recovery/master-key (/readyz 503, no crash-loop): restore the matching volume, or owner-confirm RESET ENCRYPTED SECRETS to wipe ciphertext and rebind.
Terminate TLS on an edge proxy in front of the gateway. The container listens on plain HTTP inside the network; set the public origin under Settings → Runtime after activation.
Verify
- /healthz → 200 while the process is up.
- /admin/activate → upload .lic → activate against the issuer.
- Empty org continues to /setup for owner creation; then /readyz → 200.
- Configure a provider and pool alias; call the OpenAI SDK at /v1 (see Gateway OpenAI SDK).