Files
omnigent-ai--omnigent/deploy/kubernetes/README.md
T
Zeyi (Rice) Fan b9d53f0a96 feat(server): add deployment-wide release feature flags (#4775)
## Related issue

Follow-up to #4673.

## Summary

- Add a typed, default-off release-feature registry driven by one comma-separated `OMNIGENT_FEATURES` environment variable, with strict validation and lifecycle metadata.
- Gate the web Usage route/navigation and page-only report enrichment while preserving the existing `GET /v1/usage` CLI API.
- Migrate web-driven harness installation to the same immutable startup snapshot and wire rollout configuration across Docker, Kubernetes, Render, Railway, and Databricks.

ELI5: the server reads one list of enabled features when it starts, enforces that same list on backend routes, and tells the web app which controls and pages to show.

```text
OMNIGENT_FEATURES
        |
        v
  FeatureFlags snapshot
     /             \
backend gates    GET /v1/info
                       |
                       v
                 frontend gates
```

## Test Plan

- `uv run pytest tests/server/test_feature_flags.py tests/host/test_local_server.py tests/server/integration/test_utility_endpoints.py tests/server/integration/test_hosts_install_harness.py tests/server/integration/test_hosts_store_credential.py tests/server/routes/test_usage_report.py tests/server/test_openapi_drift.py -q`
- `cd web && pnpm vitest run src/lib/capabilities.test.ts src/lib/harnessSetup.test.ts src/App.test.tsx src/shell/Sidebar.test.tsx`
- `uv run pytest tests/e2e_ui/sessions/test_usage_page_feature.py -q`
- `uv run python scripts/dump_openapi.py --check`
- `pre-commit run --files <changed files>`
- Verified default-off and enabled Usage route/sidebar behavior, strict unknown-feature rejection, legacy CLI usage compatibility, and harness route enforcement.

## Demo

- Default off: the updated visual baselines show the original sidebar without the Usage row.
- Enabled Usage page: https://github.com/user-attachments/assets/8385d4f0-47ad-430f-bf2c-06c35af6c499

## Type of change

- [ ] Bug fix
- [x] Feature
- [x] UI / frontend change
- [ ] Refactor / chore
- [x] Docs
- [ ] Test / CI
- [ ] Breaking change

## Test coverage

- [x] Unit tests added / updated
- [x] Integration tests added / updated
- [x] E2E tests added / updated
- [x] Manual verification completed
- [x] Existing tests cover this change
- [ ] Not applicable

## Coverage notes

Manually reviewed the default-off visual output and verified that the Usage route is absent while the capability is disabled. Targeted backend and frontend tests cover both flag states, capability parsing, startup snapshots, and harness enforcement.

## Changelog

Usage and web-driven harness setup can now be enabled per deployment with `OMNIGENT_FEATURES`.

Signed-off-by: Zeyi (Rice) Fan <zeyi.f@databricks.com>
2026-08-14 10:57:20 -07:00

14 KiB

Omnigent on Kubernetes

Deploy Omnigent to any Kubernetes cluster using Kustomize. The manifests pull the prebuilt image and set up a persistent volume and health checks. They also include an Ingress so you can serve the app over HTTPS at a public web address, but that part is optional — it only matters when people need to reach the server over the internet, and it pulls in two extra add-ons (ingress-nginx and cert-manager). For local or dev use, ignore it and connect with kubectl port-forward (see Verify the deployment).

What gets provisioned

  • Deployment — single-replica pod running ghcr.io/omnigent-ai/omnigent-server, served on port 8000.
  • Service — ClusterIP on port 80 → 8000.
  • Ingress (optional) — serves the app over HTTPS at a public web address, using cert-manager for the certificate. Skip it if the server isn't going on the internet.
  • PVC — 10 Gi volume at /data/artifacts for the artifact store, minted cookie secret, and admin credentials.
  • ConfigMap + Secret — environment config and database credentials.

Prerequisites

  • A Kubernetes cluster (1.25+)
  • kubectl with Kustomize support (kubectl kustomize or standalone kustomize)
  • A PostgreSQL database (managed or in-cluster — see below)
  • Only if you're putting the server on a public web address: an ingress controller (e.g. ingress-nginx) and cert-manager

Install cluster add-ons for ingress and cert management (optional)

Skip this unless you're putting the server on a public web address. (For local or dev use you'll reach it with kubectl port-forward, or you can let your own load balancer or proxy handle HTTPS instead.) Otherwise, if your cluster doesn't already have an ingress controller and cert-manager, install them (pin the versions to taste):

# ingress-nginx — use the provider manifest that matches your cluster
# (this is the kind one; for EKS/GKE/AKS use that provider's manifest or Helm chart):
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml

# cert-manager:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml

# wait until both are ready:
kubectl wait -n ingress-nginx --for=condition=Ready pod \
  -l app.kubernetes.io/component=controller --timeout=180s
kubectl wait -n cert-manager --for=condition=Available deployment --all --timeout=180s

Create a cert-manager issuer (optional)

Skip this unless you're using the Ingress. cert-manager fetches the HTTPS certificate for the Ingress from a ClusterIssuer named letsencrypt-prod (the cert-manager.io/cluster-issuer annotation in base/ingress.yaml). That issuer is not shipped here — create one before deploying, or change the annotation to match an issuer you already have. Two common choices:

# Production — real certificates from Let's Encrypt
# (needs a public domain and an Ingress reachable from the internet):
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: you@example.com
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
      - http01:
          ingress:
            ingressClassName: nginx
# Local / dev — self-signed (no public DNS needed; browsers will warn):
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  selfSigned: {}

Apply your chosen issuer with kubectl apply -f <file>. Without it, cert-manager logs IssuerNotFound and no certificate is issued (the server still runs — only TLS is affected).

Release features

Release features are deployment-wide and off by default. Set the comma-separated OMNIGENT_FEATURES value in base/configmap.yaml, apply your Kustomize target, and restart the Deployment so every pod receives one fresh startup snapshot:

kubectl kustomize deploy/kubernetes/base/ | kubectl apply -f -
kubectl rollout restart deployment/omnigent
kubectl rollout status deployment/omnigent

Use the same restart after removing a feature for rollback. See designs/FEATURE_FLAGS.md for known keys.

Deploy with an external database

Use this path when you have a managed Postgres (RDS, Cloud SQL, Neon, etc.).

  1. Edit the secret — set your real DATABASE_URL and generate a cookie secret:

    # deploy/kubernetes/base/secret.yaml
    DATABASE_URL: "postgresql+psycopg://user:pass@your-db-host:5432/omnigent"
    OMNIGENT_ACCOUNTS_COOKIE_SECRET: "$(openssl rand -hex 32)"
    
  2. Set your domain (skip if you're not using the Ingress) — replace omnigent.example.com in base/ingress.yaml with your domain, and make sure the letsencrypt-prod ClusterIssuer exists (see Create a cert-manager issuer).

  3. Apply:

    kubectl kustomize deploy/kubernetes/base/ | kubectl apply -f -
    
  4. Create the first admin. Open the app (via your Ingress host, or port-forward for a quick check — see Verify the deployment). With the default accounts provider the first visitor claims the instance: the Setup screen prompts for a username + password, and whoever finishes it first becomes the admin.

Deploy with in-cluster Postgres

The overlays/postgres/ overlay adds a single-replica Postgres 16 StatefulSet with its own 10 Gi PVC. Good for dev/testing clusters.

  1. Edit secrets — in overlays/postgres/secret-patch.yaml, replace changeme with real passwords:

    POSTGRES_PASSWORD: "<strong-password>"
    DATABASE_URL: "postgresql+psycopg://omnigent:<strong-password>@postgres:5432/omnigent"
    OMNIGENT_ACCOUNTS_COOKIE_SECRET: "$(openssl rand -hex 32)"
    
  2. Set your domain (skip if you're not using the Ingress) — edit the hostname in base/ingress.yaml, and make sure the letsencrypt-prod ClusterIssuer exists (see Create a cert-manager issuer).

  3. Apply:

    kubectl kustomize deploy/kubernetes/overlays/postgres/ | kubectl apply -f -
    

Deploy with OpenShell sandboxes

The overlays/openshell/ overlay configures the server to provision NVIDIA OpenShell sandboxes for managed sessions, and includes RBAC for the kubernetes-sigs/agent-sandbox CRD when the gateway uses a Kubernetes compute driver.

  1. Edit the configmap patch — set OMNIGENT_SANDBOX_SERVER_URL to the public URL sandboxes will dial back to, and optionally set OPENSHELL_GATEWAY to a specific gateway name:

    # deploy/kubernetes/overlays/openshell/configmap-patch.yaml
    OMNIGENT_SANDBOX_SERVER_URL: "https://omnigent.example.com"
    OPENSHELL_GATEWAY: "my-gateway"
    
  2. Edit secrets — in overlays/openshell/secret-patch.yaml, set the database URL, cookie secret, and the LLM API keys your harness needs:

    DATABASE_URL: "postgresql+psycopg://omnigent:<password>@your-db-host:5432/omnigent"
    OMNIGENT_ACCOUNTS_COOKIE_SECRET: "$(openssl rand -hex 32)"
    ANTHROPIC_API_KEY: "sk-ant-..."
    
  3. Gateway access — the server pod needs to reach the OpenShell gateway's gRPC endpoint. If the gateway runs in-cluster, make sure the NetworkPolicy allows it (the included policy allows all egress on 443 — tighten to taste). If the gateway stores its config/TLS material in a Secret, create openshell-gateway-config in the omnigent namespace and the deployment mounts it at ~/.config/openshell.

  4. Install the agent-sandbox CRD (optional) — if the OpenShell gateway delegates to the kubernetes-sigs/agent-sandbox controller:

    kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/agent-sandbox/main/config/crd/bases/sandbox.agent.k8s.io_agentsandboxes.yaml
    

    The overlay's RBAC already grants the server's ServiceAccount permission to manage AgentSandbox resources.

  5. Apply:

    kubectl kustomize deploy/kubernetes/overlays/openshell/ | kubectl apply -f -
    

For OpenShell + in-cluster Postgres, layer the postgres overlay on top (compose both bases in a new kustomization, or apply the postgres StatefulSet separately). See Network egress policy for the sandbox-side egress allow-list (server URL + LLM provider hosts).

Building a UBI image (Red Hat / OpenShift)

For RHEL and OpenShift environments that require UBI-compliant containers, use the UBI variant of the Dockerfile. It uses Red Hat Universal Base Image 9 (ubi9/python-312, ubi9/nodejs-20) and runs the server as non-root (UID 1001) by default — compatible with OpenShift's restricted-v2 SCC out of the box.

# from the repo root
docker build -t omnigent-server:ubi -f deploy/docker/Dockerfile.ubi .

Then reference the image in the OpenShift overlay by patching the Deployment or pointing your image stream at it.

Deploy on Red Hat OpenShift

The overlays/openshift/ overlay replaces the Ingress with an OpenShift Route (edge TLS, managed by the platform) and adds a restricted-v2-compatible SecurityContext. No ingress controller or cert-manager add-ons needed.

  1. Edit the secret in base/secret.yaml (same as the external-database path above).

  2. Set your route hostname — replace omnigent.apps.example.com in overlays/openshift/route.yaml with your cluster's apps domain.

  3. Apply:

    kubectl kustomize deploy/kubernetes/overlays/openshift/ | oc apply -f -
    

For in-cluster Postgres on OpenShift, use overlays/openshift-postgres/ instead — it combines the Postgres StatefulSet, OpenShift Route, and restricted security contexts:

# edit overlays/openshift-postgres/secret-patch.yaml with real passwords first
kubectl kustomize deploy/kubernetes/overlays/openshift-postgres/ | oc apply -f -

On-demand sandbox runners

The overlays/sandbox-runners/ overlay turns on the kubernetes managed sandbox provider: a host_type: managed session spawns one runner Pod that runs omnigent host as its entrypoint and dials back over the launch-token tunnel. It adds a dedicated runner namespace, a least-privilege server SA (scoped Pod + Secret rights, no pods/exec), and the sandbox: server config. The overlay swaps in the official omnigent-server-kubernetes image variant, which adds the kubernetes client extra the provider imports (the base server image omits it). See overlays/sandbox-runners/README.md for the full guide.

kubectl apply -k deploy/kubernetes/overlays/sandbox-runners
# then create the omnigent-creds harness Secret (see the overlay README)

Credentials & auth — two separate concerns, don't conflate:

  • Server auth. Front the server with header/oidc auth or run single-user; the built-in accounts mode refuses the per-session runner dial-back (403), a framework-level limit shared by all sandbox providers — see Auth.
  • Model keys (ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN / OPENAI_API_KEY / GIT_TOKEN / …) ride the omnigent-creds Secret projected into every runner Pod.

Both are detailed in overlays/sandbox-runners/README.md.

Verify the deployment

Check the rollout and reach the server without a public domain:

kubectl get pods -n omnigent          # omnigent (and, with the overlay, postgres) → Running
kubectl rollout status deploy/omnigent -n omnigent
kubectl logs -n omnigent deploy/omnigent          # server logs

# Port-forward the Service and open the app locally:
kubectl port-forward -n omnigent svc/omnigent 8000:80
# → http://localhost:8000   (health check: curl localhost:8000/health → {"status":"ok"})

The first boot runs database migrations before the server starts listening; the pod may restart once if the liveness probe fires during that window (see Resource sizing).

To test the Ingress itself instead of port-forwarding, point its hostname at a domain that already resolves to localhost — omnigent.localtest.me or <node-ip>.sslip.io — use the self-signed issuer above, and reach it through the ingress controller's published port.

Next steps: connect a host

The server is the control plane — agents run on hosts that register with it. A brand-new deployment has none, so connect at least one machine:

omnigent login https://omnigent.example.com          # authenticate the CLI
omnigent host  --server https://omnigent.example.com # register this machine

The host then appears in the web UI when you start a new chat. See the main README for the full host/auth reference.

Use your own IdP instead (OIDC) — optional

Optional. The default accounts provider (username + password) works out of the box; use this only to delegate authentication to an external OIDC provider. Add OIDC env vars to the secret:

kubectl create secret generic omnigent-oidc -n omnigent \
  --from-literal=OMNIGENT_AUTH_PROVIDER=oidc \
  --from-literal=OMNIGENT_OIDC_ISSUER=https://github.com \
  --from-literal=OMNIGENT_OIDC_CLIENT_ID=<client-id> \
  --from-literal=OMNIGENT_OIDC_CLIENT_SECRET=<client-secret> \
  --from-literal=OMNIGENT_OIDC_REDIRECT_URI=https://omnigent.example.com/auth/callback \
  --from-literal=OMNIGENT_OIDC_COOKIE_SECRET=$(openssl rand -hex 32)

Then add envFrom: [{secretRef: {name: omnigent-oidc}}] to the Deployment container spec (or merge the values into omnigent-secrets).

Resource sizing

The server idles around ~275 MB RSS. The manifests request 512 Mi and limit at 1 Gi — adjust to taste. The first boot against a remote Postgres runs migrations and takes ~1 minute; bump the liveness initialDelaySeconds to ~90s if you see the pod get killed during the first deploy.

Scaling

The server uses an in-memory runner registry, so only one replica is supported. Do not increase replicas unless the architecture is changed to use a shared registry (e.g. Redis).