Container Deployment

Deploy Decision Gate in a bounded container environment.

This guide describes the current Decision Gate OSS container contract and provides operator steps for building and running the MCP server image.

The container image is a server artifact. It runs decision-gate serve and is suitable for production-like integration and qualification work. It is not evidence that the permanent commit, recovery, or multi-node assignment profile is launch-qualified.

Current Container Contract

  • Entrypoint: decision-gate
  • Default command: serve --config CONFIG_PATH --allow-non-loopback, where the container preset uses /etc/decision-gate/decision-gate.toml
  • Config mount: /etc/decision-gate/decision-gate.toml
  • Transport: HTTP (SSE optional)
  • Auth: required bearer authentication; caller-controlled certificate or proxy-subject headers are never identity authorities
  • TLS: terminated upstream by default (server.tls_termination = "upstream")
  • Persistence: no durable run-state store by default; SQLite is an explicit local-persistence configuration
  • Runtime: non-root, minimal privileges, stdout/stderr logs
  • Writable paths: /var/lib/decision-gate (only when SQLite enabled)

The absence of a local store does not make evaluator mutations stateless or safe for same-RunKey multi-writer routing. Current code has exact-head/idempotency mechanisms, but they have not been refined against the PF-03 accepted-run model and the process-lifetime profile has no restart-durability claim. A hosted deployment must route mutation for one RunKey(namespace, run_id) through one resolved process and pin identical scenario-law bytes for that identity. This is an operator restriction, not durable authority transfer or failover. Pinning an entire namespace to one process is conservative current routing, not the permanent serialization key. The assignment-stable multi-node target routes each RunKey to one co-located evaluator/accepted-run authority and consumes exact external platform witnesses; see the deployment integration standard.

The current initial-profile configuration cannot represent network evidence acquisition, remote MCP evidence sources, subprocess/custom execution, or remote evaluation. HTTP/SSE server transport carries tool requests to the DG node and does not enable those excluded acquisition families.

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-gate is a placeholder. Replace your-org with your GitHub org or user (for example, ghcr.io/decision-gate/decision-gate).
  • IMAGE_TAG=dev is a local/dev example. For releases, use a version tag (for example, vX.Y.Z) and optionally publish latest.

Tags and Release Policy

Local/dev:

  • decision-gate:dev for ad-hoc testing.
  • Local/dev tags are not policy-grade release artifacts.

Release:

  • No official GHCR image is currently published from this repository.
  • Operators who want a registry image should build and push their own image to an org-controlled registry and retain their own provenance trail.
  • Release workflows still emit supply-chain evidence for source-first tagged releases and local release-parity validation.

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.bind must be non-loopback (e.g., 0.0.0.0:8080).
  • server.auth.mode must be bearer_token or mtls.
  • 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-loopback is part of the default container command. If you override the command, include --allow-non-loopback or set DECISION_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"

[accepted_run_store]
type = "sqlite"
path = "/var/lib/decision-gate/decision-gate.db"
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"}'

Caller-asserted certificate-subject headers are intentionally unsupported. When a proxy or ingress terminates TLS, it must strip untrusted forwarding headers and pass the reviewed bearer credential unchanged. Server-terminated TLS likewise protects the connection but does not create a separate application-identity mechanism.

Health Endpoints

Decision Gate exposes standard Kubernetes probes:

  • GET /healthz for liveness
  • GET /readyz for 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:your-tag
          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 workflows generate and verify supply-chain artifacts for source-first tags and local parity checks. The commands below remain useful for manual verification of operator-built images.

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.