Container Deployment
This guide defines the Decision Gate OSS container contract and provides operator-ready steps for building and running the MCP server image.
The container image is a server artifact. It runs decision-gate serve
and is intended for production-style deployment.
Contract Summary (Authoritative)
- Entrypoint:
decision-gate - Default command:
serve --config /etc/decision-gate/decision-gate.toml --allow-non-loopback - Config mount:
/etc/decision-gate/decision-gate.toml - Transport: HTTP (SSE optional)
- Auth: required (bearer token or mTLS proxy header)
- TLS: terminated upstream by default (
server.tls_termination = "upstream") - Persistence: stateless by default; SQLite is explicit
- Runtime: non-root, minimal privileges, stdout/stderr logs
- Writable paths:
/var/lib/decision-gate(only when SQLite enabled)
Build the Image
Local build:
docker build -t decision-gate:dev .
Multi-arch build (amd64 + arm64):
IMAGE_REPO=ghcr.io/your-org/decision-gate IMAGE_TAG=dev \
scripts/container/build_container.sh
Push multi-arch:
IMAGE_REPO=ghcr.io/your-org/decision-gate IMAGE_TAG=dev PUSH=1 \
scripts/container/build_container.sh
Notes:
IMAGE_REPO=ghcr.io/your-org/decision-gateis a placeholder. Replaceyour-orgwith your GitHub org or user (for example,ghcr.io/decision-gate/decision-gate).IMAGE_TAG=devis a local/dev example. For releases, use a version tag (for example,vX.Y.Z) and optionally publishlatest.
Tags and Release Policy
Local/dev:
decision-gate:devfor ad-hoc testing.- Local/dev tags are not policy-grade release artifacts.
Release:
ghcr.io/<org>/decision-gate:vX.Y.Z(immutable release tag).ghcr.io/<org>/decision-gate:latest(points at most recent release).- Release workflows emit supply-chain evidence including SPDX SBOMs, SLSA provenance statements, and Sigstore/cosign signatures for release subjects (Rust, Python, TypeScript, and container artifacts).
Configuration
The container expects a config file at:
/etc/decision-gate/decision-gate.toml.
Use the container preset as a baseline:
configs/presets/container-prod.toml.
Key requirements:
server.bindmust be non-loopback (e.g.,0.0.0.0:8080).server.auth.modemust bebearer_tokenormtls.server.tls_termination = "upstream"when TLS is terminated outside the container.
Run the Container
Minimal run (bearer token auth, upstream TLS termination):
docker run --rm -p 8080:8080 \
-v "$(pwd)/configs/presets/container-prod.toml:/etc/decision-gate/decision-gate.toml:ro" \
decision-gate:dev
Notes:
- Replace the demo token in the preset before production use.
--allow-non-loopbackis part of the default container command. If you override the command, include--allow-non-loopbackor setDECISION_GATE_ALLOW_NON_LOOPBACK=1.
In-Container TLS (Optional)
If you need TLS inside the container, set:
[server]
tls_termination = "server"
[server.tls]
cert_path = "/etc/decision-gate/tls/server.crt"
key_path = "/etc/decision-gate/tls/server.key"
Mount the certs and update your container runtime accordingly.
Durable Mode (SQLite)
By default, the container preset uses in-memory stores.
To enable SQLite durability, update the config:
[schema_registry]
type = "sqlite"
path = "/var/lib/decision-gate/schema-registry.db"
[run_state_store]
type = "sqlite"
path = "/var/lib/decision-gate/decision-gate.db"
journal_mode = "wal"
sync_mode = "full"
busy_timeout_ms = 5000
Run with a writable volume:
docker run --rm -p 8080:8080 \
-v "$(pwd)/configs/presets/container-prod.toml:/etc/decision-gate/decision-gate.toml:ro" \
-v decision-gate-data:/var/lib/decision-gate \
decision-gate:dev
Auth Expectations
Bearer token example:
curl -sS -X POST http://127.0.0.1:8080/rpc \
-H "Authorization: Bearer dg-container-demo-token" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
For mTLS proxy mode, set:
[server.auth]
mode = "mtls"
mtls_subjects = ["CN=decision-gate-client,O=Example Corp"]
Then have your proxy inject x-decision-gate-client-subject.
Health Endpoints
Decision Gate exposes standard Kubernetes probes:
GET /healthzfor livenessGET /readyzfor readiness
These endpoints are intentionally unauthenticated and return minimal status
only. /readyz performs lightweight readiness checks (state store + schema
registry) and returns HTTP 503 with {"status":"not_ready"} if dependencies
are unavailable.
curl -sS http://127.0.0.1:8080/healthz
curl -sS http://127.0.0.1:8080/readyz
Both endpoints return HTTP 200 with a JSON payload.
Kubernetes Example
apiVersion: apps/v1
kind: Deployment
metadata:
name: decision-gate
spec:
replicas: 1
selector:
matchLabels:
app: decision-gate
template:
metadata:
labels:
app: decision-gate
spec:
containers:
- name: decision-gate
image: ghcr.io/your-org/decision-gate:latest
ports:
- containerPort: 8080
securityContext:
runAsNonRoot: true
runAsUser: 10001
readOnlyRootFilesystem: true
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
volumeMounts:
- name: config
mountPath: /etc/decision-gate/decision-gate.toml
subPath: decision-gate.toml
readOnly: true
- name: data
mountPath: /var/lib/decision-gate
volumes:
- name: config
configMap:
name: decision-gate-config
- name: data
emptyDir: {}
For SQLite durability, replace emptyDir with a persistent volume claim.
Supply-Chain Artifacts
Decision Gate release/publish workflows now generate and verify supply-chain artifacts directly. The commands below remain useful for manual verification.
Container SBOM (example using syft):
syft packages decision-gate:dev -o spdx-json > decision-gate.sbom.spdx.json
Blob or artifact signing (cosign):
cosign sign-blob decision-gate.sbom.spdx.json
Provenance statement signing:
cosign sign-blob decision-gate.provenance.intoto.json
Release policy blocks when:
- Any High/Critical vulnerability is present.
- Any known-exploited CVE is present.
- Signature or provenance verification fails.