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?
| Goal | Best path | Section |
|---|---|---|
| Quick eval (30 seconds) | docker run | 1. Quick Start |
| Local development | Docker Compose minimal | 2. Docker Compose |
| Full local with NER | Docker Compose full | 2. Docker Compose |
| Production VM / bare metal | systemd + reverse proxy | 6. Production Linux Server |
| Production containers, single host | Docker Compose, two instances | 2. Docker Compose |
| Kubernetes | Helm chart | 3. Kubernetes |
| Python SDK integration | pip install | 4. Package Managers |
| TypeScript SDK integration | npm install | 4. Package Managers |
| AWS deployment | ECS Fargate | 7. Cloud Deployments |
| GCP deployment | Cloud Run | 7. Cloud Deployments |
| Azure deployment | Container Apps | 7. 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:latestcurl http://localhost:3000/v1/healthOne 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 alinux/amd64and alinux/arm64variant, sodockerand 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 upServer 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| Service | Port | Description |
|---|---|---|
server | 3000 | Rust API server |
detector | 9090 | Python 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 upTwo 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-stoppedrestores 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 whattests/ha.pychecks. 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-productionprofile configures; do not rely on revocation alone. For server-side sessions,session.backend=redisstores each session encrypted at rest in a shared Redis (cross-replica), and also requiresGUARDAI_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=trueThe 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-prodWith Redis for Distributed Sessions
Note: Set
session.backend=redisandredis.external.urlfor server-side sessions the client references bysession_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 setrevocation.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 withrevocation.backend=redistoo.
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=trueThe 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-sdkTypeScript SDK:
npm install @oguardai/sdk # or: pnpm add @oguardai/sdk5. 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/binEach 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
oguardaiCLI and theoguardai-serverbinary; 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.
| Platform | Built | Signed | Executed in CI | Support level |
|---|---|---|---|---|
x86_64 Linux (x86_64-unknown-linux-gnu) | Yes | Yes | Yes | Supported: production, managed systemd service |
x86_64 macOS (x86_64-apple-darwin) | Yes | Yes | No | Development and evaluation |
ARM64 macOS (aarch64-apple-darwin) | Yes | Yes | No | Development and evaluation |
ARM64 Linux (aarch64-unknown-linux-gnu) | Yes, cross-built | Yes | No | Evaluation 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/serverthere instead. - A minimal base image also needs the OpenSSL 3 runtime package:
openssl-libson RHEL,libssl3on Debian and Ubuntu. scripts/install.shchecks these before it downloads anything and refuses a host below the floor, a non-glibc host, or one missinglibssl.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.targetCreate 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/policiesGenerate 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.envsudo systemctl daemon-reload && sudo systemctl enable --now oguardai-server
sudo journalctl -u oguardai-server -fThe 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 nginxManaged 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-serverList 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_PATHon 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 setGUARDAI_ALLOW_EPHEMERAL_AUDIT=1to explicitly accept an ephemeral trail. NFS, EFS, SMB, Azure Files, FUSE, and overlayfs are accepted with a durability warning under the default, and refused whenaudit.require_certified_filesystem: truerestores 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.yamlprofile setspolicy.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 setdetector.modetobothoradvanced. 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-central1A 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.yamlprofile setspolicy.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 setdetector.modetobothoradvanced. 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: 10az containerapp create --name guardai --resource-group guardai-rg --yaml containerapp.yamlA 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 detectorKubernetes (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:9090Or: 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
| Variable | Default | Description |
|---|---|---|
GUARDAI_SESSION_SECRET | (required) | Secret for AES-256-GCM session encryption (16 char minimum) |
GUARDAI_HOST | 127.0.0.1 | Server 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_PORT | 3000 | Server listen port |
GUARDAI_AUTH_MODE | dev | Authentication 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_BACKEND | log | Audit 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_STRICT | false | Fail 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_LOG | warn,oguardai_server=info,guardai_server=info,guardai::audit=info,tower_http=info | Log 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_BACKEND | gliner | Python detector engine: gliner, spacy, none |
NER_BACKEND_STRICT | true | Abort detector startup if the chosen NER backend cannot load; set false to allow the gliner -> spacy -> none fallback |
GLINER_MODEL | urchade/gliner_medium-v2.1 | GLiNER 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/capabilitiesRound-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.