OGuardAI
Getting Started

Installation

Canonical reference for every way to install and run OGuardAI

Deployment model: self-hosted. Every path below runs the OGuardAI runtime in your own infrastructure. There is no hosted or managed SaaS: the operator runs it and holds all secrets, so no third party ever holds your keys. Generate and provision the secret set with the Secrets and Key Ceremony runbook.

Availability: The registry, image and package installs below are private today and provided under a written agreement with Oronts. Request access at /request.

Which path is right for you?

GoalBest pathSection
Quick eval (30 seconds)docker run1. Quick Start
Local developmentDocker Compose minimal2. Docker Compose
Full local with NERDocker Compose full2. Docker Compose
Production VM / bare metalsystemd + reverse proxy6. Production Linux Server
Production containers, single hostDocker Compose, two instances2. Docker Compose
KubernetesHelm chart3. Kubernetes
Python SDK integrationpip install4. Package Managers
TypeScript SDK integrationnpm install4. Package Managers
AWS deploymentECS Fargate7. Cloud Deployments
GCP deploymentCloud Run7. Cloud Deployments
Azure deploymentContainer Apps7. Cloud Deployments

1. Quick Start

docker run -p 3000:3000 \
  -e GUARDAI_SESSION_SECRET=$(openssl rand -base64 32) \
  -e GUARDAI_HOST=0.0.0.0 \
  -e GUARDAI_ALLOW_DEV_NON_LOOPBACK=1 \
  ghcr.io/oronts/oguardai/server:latest
curl http://localhost:3000/v1/health

One binary. Built-in regex detectors. Sealed sessions. No Python, no Redis.

The baked config binds loopback under dev auth, so a container needs GUARDAI_HOST=0.0.0.0 to accept the published port and GUARDAI_ALLOW_DEV_NON_LOOPBACK=1 to accept that dev auth is exposed. This path is for evaluation; use a production profile or Helm for a real deployment.

Image architectures: All container images (server, detector-py, proxy, web) are published multi-arch. Each tag is a manifest list carrying a linux/amd64 and a linux/arm64 variant, so docker and Kubernetes pull the architecture matching the host. They run on x86-64 and on ARM64 (AWS Graviton, Azure Cobalt, Ampere) with no image change.


2. Docker Compose

All compose files are in deploy/docker/. Run from the repository root.

Minimal (server only, built-in detectors)

GUARDAI_SESSION_SECRET=$(openssl rand -base64 32) \
  docker compose -f deploy/docker/docker-compose.minimal.yml up

Server on port 3000. Regex detection covers: email, phone, IBAN, SSN, IP, URL, credit card, passport, DOB, address, customer/order ID, health ID.

Full Stack (server + NER detector + Redis)

export GUARDAI_SESSION_SECRET="$(openssl rand -base64 32)"
export GUARDAI_API_KEY_1="$(openssl rand -hex 32)"
export GUARDAI_DETECTOR_API_KEY="$(openssl rand -hex 32)"
export GUARDAI_REDIS_PASSWORD="$(openssl rand -hex 32)"
docker compose -f deploy/docker/docker-compose.yml up
ServicePortDescription
server3000Rust API server
detector9090Python NER (spaCy/GLiNER)
redis-Revocation, replay, and session store (internal network only)

Two instances behind nginx (one host)

export GUARDAI_SESSION_SECRET="$(openssl rand -base64 32)"
export GUARDAI_API_KEY_1="$(openssl rand -hex 32)"
export GUARDAI_DETECTOR_API_KEY="$(openssl rand -hex 32)"
export GUARDAI_REDIS_PASSWORD="$(openssl rand -hex 32)"
docker compose -f deploy/docker/docker-compose.ha.yml up

Two server instances behind nginx round-robin on port 8080. Both select the shipped ha-production profile (--config /app/profiles/ha-production.yaml), which sets high_availability: true (the runtime's multi-replica advertisement) and shares all three at-most-once authorities across the replicas through the bundled Redis: revocation, replay, and idempotency (Redis holds only HMAC digests and counters, never raw PII). Sealed sessions keep request state client-held, so no session state or sticky sessions are needed.

This stack runs both instances on one Docker host, so the server process is the only redundant component. Redis, nginx, the detector, and the host are each a single point of failure; restart: unless-stopped restores them after a crash but does not keep serving during one. It exercises the runtime's multi-replica contract (any instance can serve any request; shared revocation, replay, and idempotency), which is what tests/ha.py checks. It is not a high-availability deployment: host-level availability needs the Helm chart across several nodes with an external Redis that has its own replication and failover; see Distributed Deployment and the chart README section "High availability".

Note: The sealed backend works well for HA because session state travels with each request, but any multi-instance deployment must share all three at-most-once authorities across replicas (revocation, replay, and idempotency on redis), which the ha-production profile configures; do not rely on revocation alone. For server-side sessions, session.backend=redis stores each session encrypted at rest in a shared Redis (cross-replica), and also requires GUARDAI_REPLAY_BACKEND=redis, because the server rejects a redis session backend that has no redis replay backend.


3. Kubernetes (Helm)

Chart location: deploy/helm/oguardai/.

Basic Install

helm install oguardai oci://ghcr.io/oronts/charts/oguardai --version 0.1.0 \
  --set session.secret="$(openssl rand -base64 32)" \
  --set auth.apiKeys[0].name=default \
  --set auth.apiKeys[0].key="$(openssl rand -hex 32)" \
  --set audit.backend=file \
  --set audit.strict=true \
  --set audit.allowEphemeral=true \
  --set revocation.allowEphemeral=true

The chart defaults to auth.mode=api_key, and a non-dev server start requires a durable, strict audit trail (audit.backend=file plus audit.strict=true), so both are set explicitly. Under a non-dev auth mode the chart also refuses to render until the two durability questions are answered. This evaluation install waives them: audit.allowEphemeral accepts an audit trail on a per-pod volume that is lost when the pod is rescheduled, and revocation.allowEphemeral accepts a revocation table on the same kind of volume, so a value revoked before the pod is replaced becomes restorable again. A production install answers them durably instead, with audit.persistence.enabled=true, a durable storage class (a block-backed class presenting ext4, XFS, or Btrfs is recommended), and revocation.backend=redis plus redis.external.url. NFS and SMB claims are accepted with a durability warning; memory-backed volumes are refused. See the Deployment guide.

Key Values

server:
  replicaCount: 1          # >1 (or autoscaling) requires revocation.backend=redis and idempotency.backend=redis, else the chart refuses to render
  resources:
    requests: { cpu: 250m, memory: 256Mi }
    limits:   { cpu: "1",  memory: 512Mi }
session:
  backend: sealed          # sealed | memory | redis (managed is planned)
  secret: ""               # or use existingSecret / existingSecretKey
  ttlSeconds: 3600
revocation:
  backend: memory          # memory | redis; MUST be redis when replicaCount > 1 or autoscaling, so a value revoked on one pod is refused by all
  allowEphemeral: false    # under a non-dev auth.mode the chart refuses to render on the pod-local memory table unless this accepts it
redis:
  external:
    url: ""                # redis://host:6379; required when revocation.backend (or session.backend) is redis
auth:
  mode: api_key            # api_key | dev (jwt not supported by chart)
audit:
  backend: log             # log | file; non-dev needs file + strict: true
  strict: false
  file:
    path: /audit/audit.log # emptyDir-backed volume unless you attach a PersistentVolume
detector:
  enabled: false
  auth:                    # X-Detector-API-Key shared secret; required when enabled
    apiKey: ""             # or existingSecret / existingSecretKey; allowInsecure: true is dev-only
    existingSecret: ""
    allowInsecure: false
  resources:
    requests: { cpu: 250m, memory: 512Mi }
    limits:   { cpu: "1",  memory: 1Gi }
autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70
ingress:
  enabled: false
  className: ""
  tls: []

With Ingress and TLS

These flags extend the Basic Install command above; on their own they omit the session secret, the API key, and the durability choices the chart requires, so the render fails closed.

helm install oguardai oci://ghcr.io/oronts/charts/oguardai --version 0.1.0 \
  --set ingress.enabled=true \
  --set ingress.className=nginx \
  --set ingress.hosts[0].host=oguardai.example.com \
  --set ingress.hosts[0].paths[0].path=/ \
  --set ingress.hosts[0].paths[0].pathType=Prefix \
  --set ingress.tls[0].secretName=oguardai-tls \
  --set ingress.tls[0].hosts[0]=oguardai.example.com \
  --set ingress.annotations."cert-manager\.io/cluster-issuer"=letsencrypt-prod

With Redis for Distributed Sessions

Note: Set session.backend=redis and redis.external.url for server-side sessions the client references by session_id; each session is encrypted at rest (AES-256-GCM), so Redis holds only ciphertext. A Redis session backend advertises a multi-replica topology, so also set revocation.backend=redis (the chart requires it, and a direct server start rejects redis sessions with a per-replica memory revocation table); the chart auto-enables the shared redis replay store for redis sessions. The sealed backend (default) also works for HA, carrying session state as a client-held encrypted blob with each request; run it across replicas with revocation.backend=redis too.

Namespace and RBAC

kubectl create namespace oguardai

# Create secrets (session key + API key)
kubectl create secret generic oguardai-secrets \
  --namespace oguardai \
  --from-literal=session-secret="$(openssl rand -base64 32)" \
  --from-literal=api-key-default="$(openssl rand -hex 32)"

helm install oguardai oci://ghcr.io/oronts/charts/oguardai --version 0.1.0 \
  --namespace oguardai \
  --set serviceAccount.create=true \
  --set session.existingSecret=oguardai-secrets \
  --set auth.existingSecret=oguardai-secrets \
  --set auth.existingSecretKey=api-key-default \
  --set audit.allowEphemeral=true \
  --set revocation.allowEphemeral=true

The two allowEphemeral flags carry the same evaluation waiver as the Basic Install: a per-pod audit trail and a pod-local revocation table, both lost when the pod is rescheduled.

The chart supports ServiceAccount, PodDisruptionBudget (auto-enabled when the guaranteed pod floor is above 1: replicaCount > 1, or autoscaling with minReplicas > 1), and NetworkPolicy.


4. Install from Package Managers

Rust CLI and server: distributed only as the signed release archive (5. Binary Download) and the container image. Nothing from the runtime workspace is published to crates.io; the runtime crates are proprietary and are not a public dependency.

Python SDK:

pip install oguardai-sdk

TypeScript SDK:

npm install @oguardai/sdk    # or: pnpm add @oguardai/sdk

5. Binary Download (GitHub Releases)

Each archive holds two binaries, the CLI (oguardai) and the server (oguardai-server). The tag is v$VERSION and the file name carries the bare version, so both appear in the URL.

VERSION=0.1.0    # the release version, without the leading v

# Linux x86_64
curl -fsSL https://github.com/oronts/OGuardAI/releases/download/v${VERSION}/oguardai-${VERSION}-x86_64-unknown-linux-gnu.tar.gz \
  | tar xz -C /usr/local/bin

# Linux ARM64
curl -fsSL https://github.com/oronts/OGuardAI/releases/download/v${VERSION}/oguardai-${VERSION}-aarch64-unknown-linux-gnu.tar.gz \
  | tar xz -C /usr/local/bin

# macOS x86_64
curl -fsSL https://github.com/oronts/OGuardAI/releases/download/v${VERSION}/oguardai-${VERSION}-x86_64-apple-darwin.tar.gz \
  | tar xz -C /usr/local/bin

# macOS ARM64 (Apple Silicon)
curl -fsSL https://github.com/oronts/OGuardAI/releases/download/v${VERSION}/oguardai-${VERSION}-aarch64-apple-darwin.tar.gz \
  | tar xz -C /usr/local/bin

Each archive is published alongside a .sha256 checksum, a Cosign signature (.sig plus .pem), and an SPDX SBOM. Verify the checksum before installing.

macOS is the CLI and server binaries for development and evaluation, not a managed service. macOS installs the oguardai CLI and the oguardai-server binary; it does not create a service user, correct ownership for a separate runtime identity, or install a managed service. Start the server yourself, sourcing the secret-bearing env file in the same shell, or wire your own launchd job. The managed service (dedicated runtime identity, systemd unit, health-gated upgrade) is Linux/systemd only.

Platforms

The release build produces one archive per platform below. "Executed in CI" means the packaged binary is actually run during the pipeline (the test suite plus a startup smoke run), not merely compiled. A cross-built platform is compiled for a target the build host cannot run, so it is never executed in the pipeline.

PlatformBuiltSignedExecuted in CISupport level
x86_64 Linux (x86_64-unknown-linux-gnu)YesYesYesSupported: production, managed systemd service
x86_64 macOS (x86_64-apple-darwin)YesYesNoDevelopment and evaluation
ARM64 macOS (aarch64-apple-darwin)YesYesNoDevelopment and evaluation
ARM64 Linux (aarch64-unknown-linux-gnu)Yes, cross-builtYesNoEvaluation only

ARM64 Linux is cross-built, is not exercised in CI, and needs a newer GNU libc floor than the x86_64 binary (see the Linux host requirements below), so treat it as evaluation-only. The installer recognizes the x86_64 Linux and both macOS targets without a probe; for ARM64 Linux it first confirms the artifact exists in the release before downloading.

Linux host requirements

The Linux release binary is dynamically linked against GNU libc and OpenSSL 3. It is built on a RHEL 9 userland so it loads on the oldest hosts the installer names.

  • x86_64 needs GNU libc 2.34 or newer and OpenSSL 3: RHEL 9+, Ubuntu 22.04+, Debian 12+.
  • An older glibc (RHEL 8, Ubuntu 20.04, Debian 11) or a non-glibc distro (Alpine and other musl systems) cannot load the native binary. Run the container image ghcr.io/oronts/oguardai/server there instead.
  • A minimal base image also needs the OpenSSL 3 runtime package: openssl-libs on RHEL, libssl3 on Debian and Ubuntu.
  • scripts/install.sh checks these before it downloads anything and refuses a host below the floor, a non-glibc host, or one missing libssl.so.3, with a message that names the fix.
  • The aarch64 Linux binary is cross-built and needs GNU libc 2.39 or newer. It is not part of the standard release matrix and is not exercised in CI, so treat it as evaluation-only.

6. Production Linux Server (systemd)

The systemd service runs the same release binary, so the host must meet the Linux host requirements above (x86_64 on GNU libc 2.34+ with OpenSSL 3).

Create /etc/systemd/system/oguardai-server.service:

[Unit]
Description=OGuardAI AI Data Protection Runtime
Documentation=https://OGuard.ai/docs
After=network.target

[Service]
Type=simple
User=guardai
Group=guardai

ExecStart=/usr/local/bin/oguardai-server --config /etc/guardai/oguardai.yaml
EnvironmentFile=/etc/guardai/oguardai.env

Restart=on-failure
RestartSec=5
TimeoutStartSec=30
# Match the server's cumulative default shutdown and audit-finalization envelope.
TimeoutStopSec=180

# Security hardening
ProtectSystem=strict
ProtectHome=yes
NoNewPrivileges=yes
ReadWritePaths=/var/lib/guardai
# The policy tree is root-owned and read-only to the runtime identity. This re-mounts it read-only
# for the service (later-wins ordering, so it must stay after ReadWritePaths), so a runtime
# compromise can read policy but never rewrite it.
ReadOnlyPaths=/var/lib/guardai/policies
# OCR/PDF scratch on a memory-backed runtime dir: %t is /run (tmpfs), so a scratch file stays in RAM
# (swap or hibernation aside), off disk. RuntimeDirectory makes it 0700 and removes it at stop.
# TMPDIR steers the runtime here. No separate size cap: pages are charged to MemoryMax, sharing host
# /run. Bound concurrent OCR overlap with limits.max_blocking_ops (or CPUQuota=) on many-core hosts.
RuntimeDirectory=guardai
RuntimeDirectoryMode=0700
Environment=TMPDIR=%t/guardai
PrivateTmp=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictNamespaces=yes
MemoryDenyWriteExecute=yes
SystemCallArchitectures=native
# Allow the base service call set plus the sandbox calls (seccomp, landlock) the runtime uses to
# confine its OCR and PDF children, then subtract the dangerous classes. @resources stays allowed so
# the runtime and the PDF child can set their own rlimits.
SystemCallFilter=@system-service @sandbox
SystemCallFilter=~@privileged @obsolete @cpu-emulation @debug @mount @reboot @swap @raw-io @clock @module
# The runtime serves HTTP and dials the detector and Redis; no other socket family is needed.
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
# Cgroup ceilings for the service. MemoryMax covers the server and its transient in-process OCR and PDF
# children; TasksMax sits above the runtime's Tokio blocking pool so it bounds runaway process creation.
MemoryMax=512M
TasksMax=1024
# Optional CPU ceiling; size it to the host. One core: CPUQuota=100%
# CPUQuota=100%

[Install]
WantedBy=multi-user.target

Create the service account and private directories first. The audit parent remains owned by the runtime user and must not be replaced by a symlink:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin guardai
sudo install -d -o root -g guardai -m 0750 /etc/guardai
sudo install -d -o guardai -g guardai -m 0700 /var/lib/guardai/audit
# Policies are root-owned and read-only to the runtime identity, matching the unit's ReadOnlyPaths.
# Install at least the default policy under this tree; the runtime reads policy but never rewrites it.
sudo install -d -o root -g guardai -m 0750 /var/lib/guardai/policies

Generate secrets and create /etc/guardai/oguardai.env:

cat > /etc/guardai/oguardai.env <<EOF
GUARDAI_SESSION_SECRET=$(openssl rand -base64 32)
GUARDAI_AUTH_MODE=api_key
GUARDAI_API_KEY_1=$(openssl rand -hex 32)
# Non-dev (api_key) start requires a durable, strict audit trail. The stable,
# runtime-owned parent was created above; the runtime manages the private leaf.
GUARDAI_AUDIT_BACKEND=file
GUARDAI_AUDIT_FILE_PATH=/var/lib/guardai/audit/audit.log
GUARDAI_AUDIT_STRICT=true
# Mirrors the server's built-in filter, which applies only when RUST_LOG is unset.
# Setting RUST_LOG replaces that filter outright, so keep the bare warn floor, the
# oguardai_server bin target and guardai::audit. The first two carry the startup report
# and the output guard's block reasons, and a filter naming only guardai_server mutes
# both; guardai::audit=info is what lets the audit trail past the warn floor.
RUST_LOG=warn,oguardai_server=info,guardai_server=info,guardai::audit=info,tower_http=info
EOF
chmod 600 /etc/guardai/oguardai.env
sudo systemctl daemon-reload && sudo systemctl enable --now oguardai-server
sudo journalctl -u oguardai-server -f

The runtime creates the live leaf at exact mode 0600. An existing leaf must be regular, owned by guardai, single-link, and compatible with safe narrowing to 0600; symlinks and other unsafe objects are rejected. See Compliance Controls before archiving or exporting evidence.

Reverse Proxy (nginx)

Put OGuardAI behind nginx with TLS termination:

# guard_safe records a fixed route label, never the raw target, so a query string carrying PII stays out of the access log.
map $uri $guard_route {
    default                 other;
    /v1/transform           transform;
    /v1/transform/stream    transform_stream;
    /v1/rehydrate           rehydrate;
    /v1/rehydrate/stream    rehydrate_stream;
    /v1/detect              detect;
    /v1/evaluate-policy     evaluate_policy;
    /v1/health              health;
    /v1/capabilities        capabilities;
    /readyz                 readyz;
}

log_format guard_safe '$remote_addr $time_iso8601 $request_method $guard_route '
                      '$status $body_bytes_sent $request_time $upstream_response_time';

server {
    listen 443 ssl;
    server_name guardai.yourcompany.com;

    ssl_certificate     /etc/ssl/guardai.crt;
    ssl_certificate_key /etc/ssl/guardai.key;

    access_log /var/log/nginx/guardai.access.log guard_safe;

    # Stream raw request bodies straight upstream instead of buffering them to a client-body temp file on disk.
    proxy_request_buffering off;
    client_max_body_size 50m;
    client_body_buffer_size 16k;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # SSE streaming support
        proxy_buffering off;
        proxy_cache off;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}
sudo ln -s /etc/nginx/sites-available/guardai /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Managed Upgrades and Rollback

scripts/install.sh installs each release into its own directory under /usr/local/lib/guardai/versions/<tag> and points a single current symlink at the live version. The /usr/local/bin/oguardai and /usr/local/bin/oguardai-server entries resolve through that symlink, so an upgrade stages the new binaries, validates the installed config with the staged binary, then flips the whole set live at once.

The health gate runs only when the service was already running before the upgrade, captured before any symlink is touched. In that case the installer restarts the service into the new version and waits for /readyz. If the new version does not become ready within the unit's TimeoutStartSec, the installer restores current to the previous version, restarts the service, and exits non-zero. A host where the service was not running is never force-started, so the operator keeps the enable-it-yourself hand-off.

The one-time migration from a pre-versioned (flat) install keeps the previous binaries as versions/pre-versioned, which is the rollback target for that first upgrade. After a successful upgrade the immediately previous version is retained as the next rollback target and older versions are pruned; a version that failed its readiness check is left on disk for inspection, and a same-version re-run prunes nothing.

To roll back by hand, point current at the version you want and restart the service:

sudo ln -sfn versions/<tag> /usr/local/lib/guardai/current && sudo systemctl restart oguardai-server

List the versions kept on the host with ls /usr/local/lib/guardai/versions.


7. Cloud Deployments

Audit trail: A non-dev (api_key) start requires a durable, strict audit trail (GUARDAI_AUDIT_BACKEND=file + GUARDAI_AUDIT_FILE_PATH on a persistent path + GUARDAI_AUDIT_STRICT=true); the server refuses to boot otherwise. On platforms with memory-backed filesystems (tmpfs, ramfs, hugetlbfs), either mount a durable filesystem for the audit path or set GUARDAI_ALLOW_EPHEMERAL_AUDIT=1 to explicitly accept an ephemeral trail. NFS, EFS, SMB, Azure Files, FUSE, and overlayfs are accepted with a durability warning under the default, and refused when audit.require_certified_filesystem: true restores the strict allowlist.

AWS ECS (Fargate)

Builtin regex detection, single container. The provided task selects the shipped builtin profile, then explicitly switches to log audit with the ephemeral-audit waiver so it can boot on Fargate. It has no shared Redis store and is an evaluation baseline. The session secret and API key come from Secrets Manager. EFS uses NFS, which the file backend accepts with a durability warning by default and refuses under the strict allowlist. Protected production needs ECS with EC2 capacity and a durable block volume, or EKS with a durable block storage class.

A detector sidecar can use a localhost URL. The runtime resolves every address for the hostname and accepts it only when every result is loopback, so a mixed or rebound hostname still fails closed.

Google Cloud Run

Builtin regex detection, single container. The server selects the shipped builtin.yaml profile and takes the HA overrides for the autoscaling service; never hand-assemble a config from env vars.

Builtin regex only, NER as a follow-up: the builtin.yaml profile sets policy.skip_incompatible_policies, so it boots with the regex-only policy subset and drops any policy that needs the NER detector rather than failing closed. That drop is deliberate for this evaluation topology, and disclosed here, not silent. Adding name, company, and location detection is a follow-up: run the detector sidecar and set detector.mode to both or advanced. The detector URL is then governed by the endpoint-allowlist runtime capability, so the address must resolve to an allowlisted target or the server fails closed at startup.

# Create secrets first:
echo -n "$(openssl rand -base64 32)" | gcloud secrets create oguardai-session-secret --data-file=-
echo -n "$(openssl rand -hex 32)" | gcloud secrets create oguardai-api-key --data-file=-

gcloud run deploy guardai \
  --image ghcr.io/oronts/oguardai/server:0.1.0 \
  --port 3000 \
  --args=--config,/app/profiles/builtin.yaml \
  --set-env-vars GUARDAI_HIGH_AVAILABILITY=true,GUARDAI_REVOCATION_BACKEND=redis,GUARDAI_REPLAY_BACKEND=redis,GUARDAI_IDEMPOTENCY_BACKEND=redis,GUARDAI_RATE_LIMIT_BACKEND=redis,GUARDAI_REDIS_URL=redis://your-redis-host:6379,GUARDAI_AUDIT_BACKEND=log,GUARDAI_AUDIT_STRICT=false,GUARDAI_ALLOW_EPHEMERAL_AUDIT=true,GUARDAI_API_KEY_1_ALLOWED_POLICIES=default,GUARDAI_API_KEY_1_ALLOWED_DESTINATIONS=external_llm,GUARDAI_API_KEY_1_ALLOWED_OUTPUT_CHANNELS=customer_email \
  --set-secrets GUARDAI_SESSION_SECRET=oguardai-session-secret:latest,GUARDAI_API_KEY_1=oguardai-api-key:latest \
  --min-instances 1 --max-instances 10 \
  --cpu 1 --memory 512Mi --region us-central1

A detector sidecar can use a localhost URL. The runtime resolves every address for the hostname and accepts it only when every result is loopback, so a mixed or rebound hostname still fails closed.

Pin the image to an immutable digest in production: replace :0.1.0 with the ghcr.io/oronts/oguardai/server:0.1.0@sha256:<digest-you-pulled> digest you pulled and verified. This command explicitly selects log audit and the ephemeral-audit waiver. Cloud Run's available NFS and FUSE mounts are accepted by the file backend only with a durability warning, and their reliability depends on the platform, so treat this as an evaluation topology. For protected production, use a VM or Kubernetes deployment with one durable block volume per audit writer.

Azure Container Apps

Builtin regex detection, single container. The server selects the shipped builtin.yaml profile and takes the HA overrides for the autoscaling replicas; never hand-assemble a config.

Builtin regex only, NER as a follow-up: the builtin.yaml profile sets policy.skip_incompatible_policies, so it boots with the regex-only policy subset and drops any policy that needs the NER detector rather than failing closed. That drop is deliberate for this evaluation topology, and disclosed here, not silent. Adding name, company, and location detection is a follow-up: run the detector sidecar and set detector.mode to both or advanced. The detector URL is then governed by the endpoint-allowlist runtime capability, so the address must resolve to an allowlisted target or the server fails closed at startup.

# containerapp.yaml
properties:
  configuration:
    ingress:
      external: true
      targetPort: 3000
    secrets:
      - name: session-secret
        keyVaultUrl: <session-secret-key-vault-uri>
        identity: system
      - name: api-key
        keyVaultUrl: <api-key-key-vault-uri>
        identity: system
  template:
    containers:
      - name: guardai
        image: ghcr.io/oronts/oguardai/server:0.1.0
        args: ["--config", "/app/profiles/builtin.yaml"]
        env:
          - name: GUARDAI_SESSION_SECRET
            secretRef: session-secret
          - name: GUARDAI_API_KEY_1
            secretRef: api-key
          - name: GUARDAI_HIGH_AVAILABILITY
            value: "true"
          - name: GUARDAI_REVOCATION_BACKEND
            value: redis
          - name: GUARDAI_REPLAY_BACKEND
            value: redis
          - name: GUARDAI_IDEMPOTENCY_BACKEND
            value: redis
          - name: GUARDAI_RATE_LIMIT_BACKEND
            value: redis
          - name: GUARDAI_REDIS_URL
            value: redis://your-redis-host:6379
          - name: GUARDAI_AUDIT_BACKEND
            value: log
          - name: GUARDAI_AUDIT_STRICT
            value: "false"
          - name: GUARDAI_ALLOW_EPHEMERAL_AUDIT
            value: "true"
          - name: GUARDAI_API_KEY_1_ALLOWED_POLICIES
            value: default
          - name: GUARDAI_API_KEY_1_ALLOWED_DESTINATIONS
            value: external_llm
          - name: GUARDAI_API_KEY_1_ALLOWED_OUTPUT_CHANNELS
            value: customer_email
    scale:
      minReplicas: 1
      maxReplicas: 10
az containerapp create --name guardai --resource-group guardai-rg --yaml containerapp.yaml

A detector sidecar can use a localhost URL. The runtime resolves every address for the hostname and accepts it only when every result is loopback, so a mixed or rebound hostname still fails closed.

Pin the image to an immutable digest in production: replace :0.1.0 with the ghcr.io/oronts/oguardai/server:0.1.0@sha256:<digest-you-pulled> digest. This example explicitly selects log audit and the ephemeral-audit waiver. Azure Files uses SMB and is rejected by the file backend's custody checks. Treat this as an evaluation topology. For protected production, use AKS or a VM with one supported local block filesystem per audit writer.

8. Python NER Detector (optional)

Adds NLP entity recognition (person names, companies, locations, medical terms) beyond built-in regex.

Docker (included in full compose):

docker compose -f deploy/docker/docker-compose.yml up detector

Kubernetes (Helm): set detector.enabled=true (see 3. Kubernetes); the chart pulls the same ghcr.io/oronts/oguardai/detector-py image as the compose stack.

The detector is strict by default, so it aborts startup if the chosen backend cannot load; set NER_BACKEND_STRICT=false to allow the gliner -> spacy -> none fallback instead.

The detector handles raw PII, so its /detect endpoint fails closed (503) until GUARDAI_DETECTOR_API_KEY is configured; set the same value on the server so it sends the X-Detector-API-Key header. For local development only, GUARDAI_DETECTOR_ALLOW_INSECURE=true runs the detector without a key.

Point the server to the detector, in oguardai.yaml:

detector:
  advanced_url: http://localhost:9090

Or: GUARDAI_DETECTOR_URL=http://localhost:9090, plus the matching GUARDAI_DETECTOR_API_KEY


9. Configuration Reference

oguardai.yaml

server:
  host: "127.0.0.1"          # loopback under dev auth; a real auth mode may bind 0.0.0.0
  port: 3000
session:
  backend: sealed            # sealed | memory | redis (managed is planned)
  ttl_seconds: 3600
policy:
  default: default
  directory: /app/policies
auth:
  mode: dev                  # dev | api_key | jwt | oidc
transform:
  context_strategy: full
  max_context_tokens: 4096
detector:
  advanced_url: ""           # Python NER URL (optional)

Environment Variables

VariableDefaultDescription
GUARDAI_SESSION_SECRET(required)Secret for AES-256-GCM session encryption (16 char minimum)
GUARDAI_HOST127.0.0.1Server bind address (loopback by default under dev auth)
GUARDAI_ALLOW_DEV_NON_LOOPBACK(unset)Accept exposing dev auth on a non-loopback bind (evaluation only); required when a container publishes the port under dev auth
GUARDAI_PORT3000Server listen port
GUARDAI_AUTH_MODEdevAuthentication mode: dev, api_key, jwt, oidc
GUARDAI_API_KEY_<NAME>(none)Registers an API key (auto-upgrades dev to api_key)
GUARDAI_DETECTOR_URL(none)Python NER service URL
GUARDAI_DETECTOR_API_KEY(none)Shared secret for the server-to-detector hop (X-Detector-API-Key); set on both sides
GUARDAI_REDIS_URL(none)Redis URL for the shared revocation, replay, and session backends
GUARDAI_AUDIT_BACKENDlogAudit sink: log (ephemeral) or file (durable, hash-chained). Non-dev requires file + strict
GUARDAI_AUDIT_FILE_PATH(none)Runtime-managed append-only log in a stable, trusted, non-symlink parent (required with the file backend)
GUARDAI_AUDIT_SECRET(none)Independent audit-chain root; falls back to the session secret when unset
GUARDAI_AUDIT_STRICTfalseFail a request closed (503) when its audit event cannot be recorded
GUARDAI_ALLOW_EPHEMERAL_AUDIT(unset)Waive the non-dev requirement for a durable, strict audit trail
RUST_LOGwarn,oguardai_server=info,guardai_server=info,guardai::audit=info,tower_http=infoLog filter. Setting it replaces the default outright; keep the bare warn floor and the oguardai_server bin target or the startup report and the output guard's block reasons go unlogged, and keep guardai::audit=info or the log audit backend leaves no audit trail
NER_BACKENDglinerPython detector engine: gliner, spacy, none
NER_BACKEND_STRICTtrueAbort detector startup if the chosen NER backend cannot load; set false to allow the gliner -> spacy -> none fallback
GLINER_MODELurchade/gliner_medium-v2.1GLiNER HuggingFace model name

10. Verification

After any install method:

# Liveness probe (unauthenticated)
curl http://localhost:3000/livez

# Health check (requires auth when enabled)
curl -H "X-API-Key: $GUARDAI_API_KEY" http://localhost:3000/v1/health

# List capabilities
curl -H "X-API-Key: $GUARDAI_API_KEY" http://localhost:3000/v1/capabilities

Round-trip test:

# Transform
RESPONSE=$(curl -s -X POST http://localhost:3000/v1/transform \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $GUARDAI_API_KEY" \
  -d '{"input": "Contact Erika at erika.mustermann@example.com or call 555-0123."}')
echo "$RESPONSE" | jq .

# Rehydrate
SESSION_STATE=$(echo "$RESPONSE" | jq -r '.session_state')
curl -s -X POST http://localhost:3000/v1/rehydrate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $GUARDAI_API_KEY" \
  -d "{\"output\": $(echo "$RESPONSE" | jq '.safe_text'), \"session_state\": \"$SESSION_STATE\"}" \
  | jq .

Port note: The server default port is 3000 (server.port / GUARDAI_PORT), and every install method on this page publishes it as host port 3000, except the two-instance compose stack, which fronts both instances with nginx on host port 8080. If port 3000 is taken on your host, map another one (for example -p 8080:3000) and substitute it in the examples.