Deployment
CogniGate runs as containers. The repository ships a reference
docker-compose.yml
that brings up three services — the gateway, the analytics engine, and
PostgreSQL — with every one of them health-checked.
The reference deployment
cp .env.example .env
openssl rand -hex 32 # -> ANALYTICS_TOKEN
openssl rand -hex 24 # -> GATEWAY_BOOTSTRAP_KEY
docker compose up -d --wait
--wait returns only once the whole stack is actually serving rather than
merely started. The two images built here carry their own HEALTHCHECK — the
gateway on /healthz, analytics on /actuator/health — and PostgreSQL is
checked in the compose file, because the stock image ships without one.
Both are published to the GitHub Container Registry on every push to main,
for linux/amd64 and linux/arm64, and docker-compose.yml names them
alongside its build contexts. Pulling instead of building is what a host with
no Go toolchain and no JDK on it wants:
docker compose pull && docker compose up -d --wait
main tracks the default branch and sha-<commit> pins one commit of it.
latest resolves to the same image as main for now, and will follow releases
once one is tagged, so main is the tag to name if the branch is what you mean.
There is deliberately no depends_on: analytics on the gateway. The gateway
builds its analytics client but does not dial it at startup, buffers what it
cannot deliver, and drains the backlog when analytics answers. Waiting would
mean an outage in one service becoming an outage in both.
Two variables have no defaults, and the compose file refuses to start without them:
| Variable | What it guards |
|---|---|
GATEWAY_BOOTSTRAP_KEY | The admin plane, before any admin key has been minted |
ANALYTICS_TOKEN | The metering API on port 8081 |
A reference deployment that shipped with a known credential is a reference deployment nobody should copy, so neither has a fallback value.
Three more are optional:
| Variable | Default | Effect |
|---|---|---|
QUOTA_ENFORCEMENT | on | observe reports over-cap without refusing |
CACHE_ENABLED | false | Response caching (GW-12) |
POSTGRES_PASSWORD | dev value | Change it before production |
Before production
- Do not publish port 8081. The analytics service holds every tenant's usage. The reference file publishes it so a first-run deployment can be inspected; a real one should leave it on the internal network. Publishing 5432 is the same mistake with a larger blast radius.
- Retire the bootstrap key. It exists to mint the first real admin key
through
POST /admin/v1/admin-keys. Once you have one, takeGATEWAY_BOOTSTRAP_KEYback out of the environment — a long-lived root credential in a compose file is the thing it was meant to avoid. - Terminate TLS. The recommended shape is a private network or an
operator-owned reverse proxy. Where you have neither, set
CG_TLS_CERT_FILEandCG_TLS_KEY_FILE; one listener serves both planes, so the pair cannot leave one of them plaintext by accident. - Pin the schema. The analytics service runs Hibernate with
ddl-auto: update, which is fine for a first run and wrong for a system holding billing data. Move to Flyway or Liquibase and setvalidate. - Scrape
/metrics. It is on by default and unauthenticated, which is what a Prometheus scrape on a private network expects. SetCG_METRICS_TOKENif the path is reachable from anywhere it should not be.
Scaling
The gateway holds its configuration in memory. That is what makes it fast and what bounds how it scales, so it is worth being precise about:
- Usage is shared. Every replica ships its records to the same analytics service, so totals and breakdowns are correct across the whole fleet no matter which replica served a request.
- Configuration is not. Two gateways are two independent sets of tenants, keys, providers, aliases, routing rules and quotas. Creating a tenant on one replica does not create it on the others.
- Nor are caches, rate limiters or captures. A per-key rate limit of 50 rps across three replicas is 150 rps in practice.
So docker compose up --scale gateway=3 behind a load balancer works, provided
every replica is configured identically at startup and nothing calls the admin
plane afterwards. Configuration that must outlive a restart or be shared between
replicas has to be applied at startup, from outside.
Rolling a replica out is safe: shutting down fails /healthz immediately so
traffic is steered away, then gives in-flight requests drain_timeout to
finish. Open streams are not cut off mid-completion.