7. Deployment view¶
Kionga has three deployments. They share images and contracts but differ in what they include and what they expect you to provide. The operator-facing procedures are in the product documentation: run locally, single server and deploy on Kubernetes.
| Local Compose | Single server | Kubernetes through Helm | |
|---|---|---|---|
| Definition | deploy/compose.yaml |
deploy/vm/compose.yaml, deploy/vm/up.py |
deploy/helm/kionga, deploy/config/crd/ |
| Images | Built from sibling checkouts | Released images pinned by digest | Released images pinned by digest |
| Includes | Every bundled service and engine | Gateway and Caddy only | Six platform Deployments; no data stores or engines |
| Identity | Dex with a test LDAP directory, plus local accounts | An external OIDC issuer | An external OIDC issuer |
| Per-user isolation | No (shared workbench) | No | Yes (KiongaWorkspace per grant) |
| Intended use | Development, evaluation, release smoke tests | A gateway-only pilot behind TLS | Multi-user installations |
Local Compose¶
flowchart TB
subgraph host["Docker host (developer machine)"]
subgraph net["Compose network mlaiops_default"]
gw["gateway"]
dex["dex + glauth"]
pg[("postgres")]
redis[("redis")]
kafka["kafka + kafka-rest"]
os[("objectstore")]
mlflow["mlflow"]
prefect["prefect-server"]
runner["pipeline-runner"]
sm["serving-manager"]
serve["model containers"]
ar["agent-runtime"]
tp["trace-proxy"]
lf["langfuse"]
rt["realtime-processor"]
fg["feature-gateway"]
sp["storage-proxy"]
jup["jupyter"]
ide["ide (profile)"]
end
sock["/var/run/docker.sock"]
lo["127.0.0.1 published ports"]
end
lo --- gw
runner --- sock
sm --- sock
sm --> serve
- Build.
make -C deploy local-uprunsdeploy/scripts/local-up.sh, which builds stale images from the sibling checkouts underKIONGA_SRC(default: the parent ofdeploy), retries transient pulls, moves a busy default port, and waits for health.KIONGA_SDK_SOURCE=releasebuilds the Python images against released SDK versions. - Network. All services join one Compose network. Inside it, services reach each
other by service name (
http://gateway:8080,http://mlflow:5000,http://objectstore:9000). From the host, the same services are on published ports bound toKIONGA_BIND_ADDRESS, default127.0.0.1(decision 0008). Jupyter, the log exporter and the log-export test targets stay on loopback even when the bind address is widened. A connection the gateway health-checks must use the in-stack hostname, because the check runs inside the gateway container. - State. Named volumes:
postgres-data,redis-data,objectstore-data,prefect-data,jupyter-data,sso-providers, and the coding-agent volumes for the IDE. Long-running services userestart: unless-stopped;objectstore-initandfeature-materializerare one-shot jobs. - Profiles.
ide(code-server),log-export(the exporter),log-export-esandlog-export-os(local test targets). - Trust boundary. The serving manager and the pipeline runner run as root with the
Docker socket mounted. That is root-equivalent on the host and is acceptable only in
this single-operator profile. The containers they start never receive the socket,
drop all capabilities, set
no-new-privileges, and have a PID limit. - Not included. The operator, integration worker and metrics collector, which exist only on Kubernetes. OpenFaaS is external in every deployment.
Single server¶
flowchart LR
internet(["Internet"]) -->|":80, :443"| caddy["caddy<br/>ACME TLS, HSTS"]
caddy -->|"gateway:8080<br/>health /api/v1/ready"| gw["gateway"]
gw --> idp["External OIDC issuer"]
gw --> db[("External PostgreSQL")]
gw --> eng["External engines<br/>(optional)"]
make -C deploy public-up DEPLOYMENT=/etc/kionga/deployment.json runs
deploy/vm/up.py, which refuses to start unless:
- the gateway and Caddy images are pinned by
sha256digest; - the domain is a real DNS hostname (not
example.com) and an ACME email is set; - the non-secret config file's
MLAIOPS_ALLOWED_ORIGINequalshttps://<domain>; - the required secrets (
DATABASE_URL,OIDC_CLIENT_SECRET,MLAIOPS_INTERNAL_TOKEN,KIONGA_CREDENTIAL_KEY) exist as files.
Only Caddy publishes ports (80 and 443). The gateway runs with
KIONGA_ENVIRONMENT=production, so its startup checks
apply. This package does not run the per-user workload plane; it is a gateway-only
pilot. A pull failure explains how to log in to ghcr.io with a read:packages
token, because the images are private.
deploy/compose.public.yaml (the local stack plus Caddy and Dex) still exists for
compatibility testing. deploy/public-up.sh runs it only when
KIONGA_LEGACY_PUBLIC_DEPLOY=1 is set and prints a warning that it is not the
supported production path.
Kubernetes through Helm¶
flowchart TB
users(["Users"]) --> ing["Ingress (TLS)<br/>console host"]
users --> wing["Ingress (wildcard TLS)<br/>*.workspaces host"]
subgraph sys["Release namespace (for example kionga-system)"]
gw["gateway x3<br/>HPA 3 to 10"]
op["operator x2"]
iw["integration-worker x2"]
fg["feature-gateway x2"]
sp["storage-proxy x2"]
tp["trace-proxy x2"]
end
subgraph wl["Workload namespace (kionga-workloads)"]
ws["Per-user workspace pods + PVCs"]
ag["Per-agent Deployments + Services"]
jobs["Pipeline Jobs (opt-in)"]
end
ing --> gw
wing --> gw
gw --> ws
gw --> ag
iw --> op
op --> ws
op --> ag
gw --> jobs
sys --> ext[("Provided by you: PostgreSQL, Redis,<br/>Kafka + REST proxy, S3 storage, OIDC,<br/>MLflow, Prefect or KFP, KServe")]
| Element | What the chart renders |
|---|---|
| Deployments | gateway (3 replicas, HPA from 3 to 10 at 70% CPU), operator, integration-worker, feature-gateway, storage-proxy, trace-proxy (2 replicas each); one PodDisruptionBudget and one ClusterIP Service each |
| Pod security | Non-root, read-only root file system, all capabilities dropped, topology spread constraints, images pinned by digest |
| Configuration | One ConfigMap mounted as KIONGA_CONFIG_FILE; each component's secrets mounted under /run/secrets and passed as <NAME>_FILE |
| Secrets | Created outside Helm, by hand or through External Secrets (externalSecrets.enabled); no Secret objects in the chart |
| Edge | Ingress with TLS for the console; an optional wildcard Ingress for workspace hosts (workspaceIngress) |
| Network | Default-deny NetworkPolicies with explicit egress CIDR ranges and ports (networkPolicy.externalEgress); DNS is allowed |
| Workload namespace | workspaceNamespace (default kionga-workloads) with a ResourceQuota (workspaceQuota) |
| Registry credentials | imagePullSecrets on platform pods, and passed to the operator as KIONGA_IMAGE_PULL_SECRETS for the workspace and agent pods it creates |
| Monitoring | Optional ServiceMonitors and a PrometheusRule (monitoring.enabled) |
| RBAC | Namespace-scoped Roles only; no ClusterRole or ClusterRoleBinding |
The CRDs are applied separately (kubectl apply -f deploy/config/crd/), with a
privileged bootstrap identity. Provider overlays and StorageClass examples for EKS, GKE
and AKS are under deploy/helm/providers/; deploy/scripts/cloud-preflight.py
checks a cluster before install and deploy/scripts/verify-production.py checks it
after.
The chart does not install RustFS, Kafka, the Kafka REST proxy, MLflow, Prefect, Langfuse or an OIDC provider. Each needs an owner, a security configuration and a backup plan.
Local Kubernetes¶
make -C deploy kind-up creates a kind cluster from deploy/kind/cluster.yaml,
applies the CRDs and the manifests in deploy/config/deploy/ (which also run the
metrics collector), and installs metrics-server for the agent autoscaler.
make -C deploy test-e2e-kind runs the end-to-end lane against it. Use it to test
operator and worker changes before a cloud staging run.
Agent routing per deployment¶
| Deployment | Endpoint the gateway calls |
|---|---|
| Compose | AGENT_RUNTIME_URL (http://agent-runtime:9000), one runtime for every agent, identity in request headers |
| Kubernetes | http://<agent-dns-name>.<MLAIOPS_AGENT_NAMESPACE>.svc, where the name is a hash-suffixed DNS label derived from the immutable agent ID; the operator uses the same name for the Deployment and Service |
The first-iteration deployment plan describes the proposed EKS rollout for a small invited team.