ManyLayers ships as one container image holding three services and a database tool. Run the gateway close to your applications for the lowest latency and to keep prompts inside your network; run Workspace beside it for the console and admin API.
You only need this page to run your own installation. On ManyLayers Cloud the gateway is already running at https://app.manylayers.io/v1 and the console at https://app.manylayers.io. In a self-hosted install those addresses are your own: expose the gateway (port 8180) and the workspace (port 8190) on hostnames you control, and use them wherever the docs show the hosted ones.

How it works

ServiceBinaryDefault portServes
Gateway/gateway8180/v1/* — the data plane. Stateless; scale it.
Workspace/workspace8190/admin, /api/v1/gateway, /auth, /scim, /w, /ui (console). Runs background workers once; do not scale it for traffic.
Deployer/deployer8200/admin/deployments, /admin/library, /admin/training-jobs. Optional.
—/dbtool—Schema migrations and maintenance.
Every service also serves /health, /healthz, /ready, /readyz, /version and /metrics. All three read the same config file and the same PostgreSQL database.
DependencyRequiredUsed for
PostgreSQLYesConfiguration, keys, policies, audit log, usage. Readiness fails without it.
RedisFor more than one gateway replicaShared rate-limit windows, budget counters, the exact-match cache and event fan-out. Without it each replica keeps its own in-memory state.
QdrantOptionalShared semantic cache (cache.semantic.store: qdrant) and knowledge bases.

Run it

The repository has three stacks in infra/docker/, each one compose file plus one env file:
FileUse
docker-compose.dev.ymlBuilt from source, ports on localhost, debug logs.
docker-compose.demo.ymlPrebuilt :demo images.
docker-compose.prod.ymlRegistry images (ghcr.io/tigergate/manylayers, set by IMAGE_PREFIX and IMAGE_TAG), read-only root filesystem, resource limits. Postgres, Redis, Workspace and Deployer bind 127.0.0.1; only the gateway is published on all interfaces.
make env-files                 # creates infra/docker/.env.{dev,demo,prod} from the templates and generates MANYLAYERS_ENCRYPTION_KEY
# edit infra/docker/.env.prod: POSTGRES_PASSWORD, REDIS_PASSWORD, and the same passwords inside DATABASE_URL and REDIS_URL
make prod-pull                 # or: make prod-build to build from source
make prod-migrate              # one-shot schema migration
make prod-up                   # gateway, workspace, deployer, postgres, redis
make prod-ready                # readiness of all three services
make prod-deploy runs pull, migrate, start and health check in one step. Pin IMAGE_TAG to a real tag rather than latest. Put the gateway behind your TLS ingress or load balancer; keep Workspace and Deployer on a private network or behind a VPN. For local development run make dev-migrate then make dev-up.
No service migrates the schema on startup. Run dbtool migrate (the compose migrate job) before starting or upgrading, so several gateway replicas never race on a migration.
The Helm chart under infra/helm deploys only the gateway and predates the three-service split, so it does not install Workspace or Deployer. For Kubernetes, translate the production compose file: a Deployment per service, the env file as a Secret, the config file as a ConfigMap, and the migrate job as a Kubernetes Job.

Health and readiness

EndpointUse forChecks
/health, /healthzLivenessNothing — always 200 {"status":"ok"} while the process runs.
/ready, /readyzLoad balancer / readinessPostgres ping, Redis ping when configured, and drain state; 503 not_ready on any failure. Bounded to 2 seconds.
/versionBuild stampBuild version, commit and time, plus the environment name.
Never point liveness at /ready: a database blip would restart healthy replicas. The image has no shell or curl, so container health checks run the binary itself — /gateway -readycheck (or -healthcheck) probes its own port over loopback and exits 0 or 1. /workspace and /deployer take the same flags.

Graceful shutdown

On SIGTERM each service:
  1. Marks itself draining — /ready returns 503, /health stays 200.
  2. Keeps serving for SHUTDOWN_DRAIN_DELAY (default 0s) so load balancers stop sending new requests.
  3. Stops accepting connections and lets in-flight requests finish for up to SHUTDOWN_TIMEOUT (default 20s; YAML server.shutdown_timeout). If the deadline passes it logs shutdown deadline exceeded and exits non-zero.
Set SHUTDOWN_DRAIN_DELAY to 5–10s behind a real load balancer or in Kubernetes, and keep your orchestrator’s grace period above SHUTDOWN_DRAIN_DELAY + SHUTDOWN_TIMEOUT (the production compose file uses stop_grace_period: 40s).

Scaling

  • Gateway: add replicas behind a load balancer; they hold no local state. Configure Redis so rate limits, budgets and the exact cache are shared — without it each replica enforces limits independently.
  • One Redis: run a single Redis (or one logical cluster). Several independent Redis instances would each keep their own limiter state, multiplying every caller’s limit.
  • Workspace and Deployer: one instance each; Workspace runs the workflow, connector, billing and sweep workers.
  • Credential key: set MANYLAYERS_ENCRYPTION_KEY explicitly. Without it each service generates data/credentials.key locally, and replicas cannot read each other’s stored provider keys.

Environment variables

Environment variables override the config file. Inside the file, ${VAR}, ${VAR:-default} and ${vault:secret/data/path#key} are expanded at load time.
VariableUsed byPurpose
GATEWAY_PORTgatewayListen port (default 8180; overrides server.listen).
WORKSPACE_PORTworkspaceListen port (default 8190).
DEPLOYER_PORTdeployerListen port (default 8200).
DATABASE_URLallPostgreSQL URL; overrides database.url.
REDIS_URLallredis://[:password@]host:port/db; overrides redis.*.
SHUTDOWN_TIMEOUTallIn-flight drain deadline, e.g. 20s.
SHUTDOWN_DRAIN_DELAYallTime to stay up after turning unready, e.g. 10s.
LOG_LEVELalldebug, info (default), warn, error.
ENVIRONMENTallEmpty, dev, development, local or test give text logs; anything else gives JSON.
MANYLAYERS_ENCRYPTION_KEYallSeals provider keys and trace-destination headers. Overrides connectors.encryption_key.
MANYLAYERS_ENCRYPTION_KEY_FILEallWhere a generated key is kept when none is set (default data/credentials.key).
MANYLAYERS_PII_VAULT_KEYSallv1:<base64>,v2:<base64> keys for reversible PII redaction. Set the same value on every service.
MANYLAYERS_PII_VAULT_KEY_VERSIONallVersion used to seal new values.
PUBLIC_WORKSPACE_URLallConsole origin(s) allowed by CORS, comma-separated. Unset = same-origin only.
PUBLIC_GATEWAY_URL, PUBLIC_DEPLOYER_URLworkspaceWhere the console sends /v1 and deployment calls in a split-origin setup.
MANYLAYERS_OTLP_DEBUGgateway1 prints every OTLP export to stderr. Debug only.
VAULT_ADDR, VAULT_TOKENallHashiCorp Vault (KV v2) for ${vault:…} references.
SLACK_WEBHOOK_URLallInternal notifications webhook when notifications.slack.webhook_url is empty.
STAFF_PASSWORDdbtoolPassword for dbtool staff-create (at least 12 characters; read from stdin if unset).
MANYLAYERS_UI_DIR, MANYLAYERS_UI_DEV_SERVERworkspaceDevelopment only: serve the console from disk or a Vite dev server.
The shipped container config (configs/gateway.container.yaml) also reads these through ${…} references:
VariablePurpose
MANYLAYERS_AUDIT_LOG_BODIEStrue stores request and response bodies (after PII redaction) in the audit log. Default false.
MANYLAYERS_ACCESS_TOKEN_TTL, MANYLAYERS_REFRESH_TOKEN_TTLConsole access-token lifetime (default 15m) and login lifetime (default 720h).
RESEND_API_KEY, RESEND_FROMEmail for sign-in codes, invitations and budget alerts through Resend.
MANYLAYERS_PUBLIC_URLPublic console origin that invitation links point at.
Provider keys such as OPENAI_API_KEYReferenced from models[].upstream_api_key or from a console provider credential reference.

Next steps

Metrics

Scrape /metrics on every replica.

gateway.yaml reference

Every config key the services read.

Caching

Share the cache across replicas with Redis.

Request Logging & Audit

Retention and body capture for your deployment.