Deploying Protocol Doors

The compatibility doors (see Compatibility & Doors) default to 127.0.0.1 so an unauthenticated port is never accidentally exposed. To let another container, host, or pod reach a door, you do three things: (1) enable the door, (2) override its bind address, (3) publish the port. This page is the whole story for Docker, Kubernetes, and bare-metal clusters.

Every door honors RELATA_<DOOR>_BIND as a plain override on every profile — no license needed. The HTTP and gRPC listeners bind 0.0.0.0 automatically on the server and cluster profiles; the 8 compat doors default to loopback on every profile and opt up only when you say so.

The 60-second recipe

Pick the doors you actually use and copy the pattern. The bearer token is the password for every protocol.

# Enable Mongo + Postgres + Redis, bind all to 0.0.0.0 so other hosts/containers can reach them
RELATA_BEARER_TOKEN=change-me \
RELATA_MONGO_ENABLE=true      RELATA_MONGO_BIND=0.0.0.0 \
RELATA_PG_ENABLE=true         RELATA_PG_BIND=0.0.0.0 \
RELATA_REDIS_ENABLE=true      RELATA_REDIS_BIND=0.0.0.0 \
relata serve

If RELATA_BEARER_TOKEN is set you can drop the explicit _ENABLE=true lines — every door auto-enables on a token. Keep the _BIND=0.0.0.0 overrides; they're what makes the door reachable off-loopback. (pgwire is the one exception — it auto-starts the moment a token is present, no enable flag needed, and it's fail-closed without a token.)

Docker — publish every door you enable

RELATA_<DOOR>_BIND=0.0.0.0 makes the door listen on all interfaces inside the container; you still need -p to publish the port to the host. A door enabled but not -p-published is unreachable from outside the container.

docker run -d \
  -p 9090:9090        `# HTTP REST (always on)` \
  -p 5433:5433        `# Postgres / pgvector` \
  -p 27017:27017      `# MongoDB wire` \
  -p 6379:6379        `# Redis RESP` \
  -p 7474:7474        `# Neo4j HTTP Cypher` \
  -p 7687:7687        `# Neo4j Bolt` \
  -p 8123:8123        `# ClickHouse HTTP` \
  -p 9000:9000        `# ClickHouse native TCP` \
  -p 9191:9191        `# S3-compatible` \
  -p 50051:50051      `# gRPC` \
  -p 8815:8815        `# Arrow Flight` \
  -e RELATA_PROFILE=server \
  -e RELATA_BEARER_TOKEN=change-me \
  -e RELATA_MONGO_BIND=0.0.0.0 \
  -e RELATA_PG_BIND=0.0.0.0 \
  -e RELATA_REDIS_BIND=0.0.0.0 \
  -e RELATA_S3_BIND=0.0.0.0 \
  -e RELATA_FLIGHT_ENABLE=true \
  -e RELATA_FLIGHT_BIND=0.0.0.0 \
  -v "$PWD/relata-data:/data/relata" \
  --name relata ghcr.io/relatadb/relata:2.0.0

Only publish the doors you actually use — every published port is attack surface. To bind to a specific interface instead of all interfaces, use the host IP (e.g. RELATA_MONGO_BIND=10.0.0.5 or -p 10.0.0.5:27017:27017).

Kubernetes — declare containerPort + the env pair

A door enabled without a matching containerPort is silent: it binds inside the pod but no Service routes to it. Declare every door you use.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: relata-db
spec:
  serviceName: relata-db
  replicas: 1
  template:
    spec:
      containers:
        - name: relata-db
          image: ghcr.io/relatadb/relata:2.0.0
          ports:
            - { containerPort: 9090,   name: http }
            - { containerPort: 5433,   name: pgwire }
            - { containerPort: 27017,  name: mongo }
            - { containerPort: 6379,   name: redis }
            - { containerPort: 7474,   name: neo4j-http }
            - { containerPort: 7687,   name: bolt }
            - { containerPort: 8123,   name: clickhouse-http }
            - { containerPort: 9000,   name: clickhouse-native }
            - { containerPort: 9191,   name: s3 }
            - { containerPort: 50051,  name: grpc }
            - { containerPort: 8815,   name: flight }
          env:
            - { name: RELATA_PROFILE, value: server }
            - { name: RELATA_BEARER_TOKEN, valueFrom: { secretKeyRef: { name: relata-secrets, key: admin-token } } }
            # Bind the doors you expose to 0.0.0.0 (default is loopback).
            - { name: RELATA_MONGO_BIND,      value: "0.0.0.0" }
            - { name: RELATA_PG_BIND,         value: "0.0.0.0" }
            - { name: RELATA_REDIS_BIND,      value: "0.0.0.0" }
            - { name: RELATA_S3_BIND,         value: "0.0.0.0" }
            - { name: RELATA_FLIGHT_ENABLE,   value: "true" }
            - { name: RELATA_FLIGHT_BIND,     value: "0.0.0.0" }
            # ...add one BIND per door you publish...

Then expose each door through a Service. A single ClusterIP for internal callers, or a LoadBalancer/Ingress per protocol for external clients (most compat protocols aren't HTTP, so Ingress usually isn't the right fit — prefer LoadBalancer or NodePort for Mongo/Postgres/Redis/Bolt/ClickHouse/S3).

apiVersion: v1
kind: Service
metadata:
  name: relata-mongo
spec:
  selector: { app: relata-db }
  type: LoadBalancer
  ports:
    - { port: 27017, targetPort: 27017, name: mongo }

Repeat per protocol. For a full worked example (PVC, readiness/liveness, multi-tenant mode), see Kubernetes Deployment.

Full port reference (all 11 networked surfaces)

DoorEnable flagPortBind varDefault bind
HTTP RESTalways on9090RELATA_HTTP_BIND0.0.0.0 on server/cluster, 127.0.0.1 on free
gRPCalways on50051RELATA_GRPC_BIND0.0.0.0 on server/cluster, 127.0.0.1 on free
Postgres + pgvectortoken required5433RELATA_PG_BIND127.0.0.1
MongoDBRELATA_MONGO_ENABLE27017RELATA_MONGO_BIND127.0.0.1
RedisRELATA_REDIS_ENABLE6379RELATA_REDIS_BIND127.0.0.1
Neo4j HTTPRELATA_NEO4J_ENABLE7474RELATA_NEO4J_BIND127.0.0.1
BoltRELATA_BOLT_ENABLE7687RELATA_BOLT_BIND127.0.0.1
ClickHouse HTTPRELATA_CLICKHOUSE_ENABLE8123RELATA_CLICKHOUSE_BIND127.0.0.1
ClickHouse nativeRELATA_CLICKHOUSE_NATIVE_ENABLE9000RELATA_CH_NATIVE_BIND127.0.0.1
S3-compatibleRELATA_S3_ENABLE9191RELATA_S3_BIND127.0.0.1
Arrow FlightRELATA_FLIGHT_ENABLE8815RELATA_FLIGHT_BIND127.0.0.1

MCP and SPARQL ride on the HTTP listener (/mcp, /sparql) — no separate port.

Cluster mode — one door port per node

In a multi-node cluster (see Cluster Setup), every node defaults to the same door ports. If you run three nodes on one host for testing, two of them will silently WARN-fail the door binds and lose ⅔ of your door capacity. Either:

  • Run one node per host (production), or
  • Give each node a distinct RELATA_<DOOR>_PORT in test, or
  • Disable the doors on reader/indexer nodes (RELATA_MONGO_ENABLE=false, etc.) and route door traffic only to coordinator/writer nodes.

Doors are stateless front-ends over the same governed store — any node can serve any door; writes funnel through the planner regardless of which node received them.

Security checklist before production

  • RELATA_BEARER_TOKEN is a strong random value (e.g. openssl rand -hex 32), not change-me.
  • Only the doors you use have RELATA_<DOOR>_BIND=0.0.0.0; the rest stay on loopback.
  • Only the doors you use are -p published / have a Service.
  • TLS is terminated either by Relata (RELATA_TLS_CERT/RELATA_TLS_KEY, RELATA_PG_TLS_CERT/RELATA_PG_TLS_KEY, RELATA_GRPC_TLS_CERT/_KEY) or by a reverse proxy / sidecar in front (RELATA_PLAINTEXT_OK=true only if you terminate TLS upstream).
  • For S3 in production, set RELATA_S3_SECRET_KEY to a dedicated SigV4 secret (defaults to the bearer token otherwise) and leave RELATA_S3_ALLOW_PLAINTEXT unset.
  • RELATA_TENANCY_MODE=multi is set only if you actually want per-tenant isolation (it's the one real cluster-only gate).

See also