Mojo UpDocs
Self-hosted AIOE

Install with Helm

Deploy the AIOE API and console to any Kubernetes cluster, with your own PostgreSQL and Redis.

The aioe Helm chart deploys the two AIOE services, the API and the console, with an ingress in front of them. It runs on any Kubernetes, in any cloud or on-premises, and contains nothing specific to one cloud. You bring PostgreSQL and Redis, managed or in the cluster.

Before you start

  • A Kubernetes cluster, kubectl and Helm 3.
  • An ingress controller. The chart defaults to ingress-nginx and sets the annotations that stop it buffering or timing out the relay's long-lived streams. With another controller, set the same behaviour yourself (see Proxies and load balancers).
  • Two host names with a TLS certificate covering both, for example aioe.example.com (API) and console.aioe.example.com (console), stored as a Kubernetes TLS secret.
  • A PostgreSQL database for AIOE, and a user that can create tables in it. The reference layouts use PostgreSQL 16.
  • A Redis instance. The chart runs two API replicas, and more than one replica needs Redis.
  • The AIOE chart and access to the aioe-api and aioe-console images, from your AIOE release. The console image has the API's public address built into it, so it must be built for your api.publicUrl. Ask Mojo Up if your release does not include one.
  • Your two app registrations, from Connect your identity provider.

Install

Create the namespace and the API's secret

The API reads every key of one Kubernetes secret as an environment variable. Put the connection strings and the platform's keys in it:

Terminal
kubectl create namespace aioe

kubectl -n aioe create secret generic aioe-api \
  --from-literal=DATABASE_URL='postgresql://aioe:<password>@<postgres-host>:5432/aioe?sslmode=require' \
  --from-literal=REDIS_URL='rediss://:<password>@<redis-host>:<port>' \
  --from-literal=AIOE_SIGNING_KEY="$(node -e "crypto.subtle.generateKey({name:'Ed25519'},true,['sign']).then(k=>crypto.subtle.exportKey('jwk',k.privateKey)).then(j=>console.log(JSON.stringify(j)))")" \
  --from-literal=AIOE_SECRETS_KEY="$(openssl rand -base64 32)"
  • AIOE_SIGNING_KEY signs requests the platform sends over the overlay, and every replica must share it. Without it, each replica makes its own key at start.
  • AIOE_SECRETS_KEY seals the secrets, linked accounts and workbench backups the platform keeps. Without it, the platform keeps none of them.

The signing key command needs Node.js 20 or later. Keep a copy of both keys in your secret store. Platform keys and rotation explains each one. Use redis:// for Redis without TLS.

Write your values

Create values.yaml with what differs from the defaults. Every value is listed in Helm values.

values.yaml
image:
  registry: <your registry>
  tag: <release tag>

api:
  publicUrl: https://aioe.example.com
  consoleUrl: https://console.aioe.example.com
  existingSecret: aioe-api
  env:
    AIOE_ENVIRONMENT: production
    # OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector:4318

bootstrapTenant:
  enabled: true
  slug: example
  organisation: Example Pty Ltd
  # Your email domains. Add the API's host name if it is not under one of them.
  domains: example.com
  oidcIssuer: https://login.microsoftonline.com/<tenant-id>/v2.0
  oidcAudience: <API registration client ID>
  oidcClientId: <client registration client ID>
  apiScope: api://aioe.example.com/access

ingress:
  apiHost: aioe.example.com
  consoleHost: console.aioe.example.com
  tls:
    - secretName: aioe-tls
      hosts: [aioe.example.com, console.aioe.example.com]

Replace the bootstrap values

The chart's defaults for bootstrapTenant are Mojo Up's own organisation and Entra registrations. Always set your own.

Install the chart

From the folder that holds the chart:

Terminal
helm install aioe ./aioe -n aioe -f values.yaml
kubectl -n aioe rollout status deployment/aioe-api

The release name (aioe here) prefixes every resource: the deployments and services are aioe-api and aioe-console. Each API pod applies any pending database migrations before it starts serving, then reports ready on /readyz.

Check it

Terminal
kubectl -n aioe logs deployment/aioe-api | grep -E 'postgres|bootstrap|broker|listening'
curl https://aioe.example.com/healthz
curl https://aioe.example.com/.well-known/aioe.json

The log should say connected to postgres, bootstrap tenant ready, relay broker: redis (multi-replica) and aioe api listening. /healthz answers with the API's version, and the discovery document names your organisation.

Open https://console.aioe.example.com and sign in.

Publish the discovery record

Add the aioe-discover record on each email domain so workbenches find AIOE from a work email. See Publish the discovery record.

What the chart deploys

ResourceNameNotes
Deployment and Service<release>-apiContainer port 3001, service port 80. Readiness probe /readyz every 10 s, liveness probe /healthz every 30 s. Runs as non-root with a read-only root file system and no added capabilities.
Deployment and Service<release>-consoleContainer port 8080, service port 80. Readiness probe /healthz every 10 s.
Ingress<release>Routes ingress.apiHost to the API and ingress.consoleHost to the console, with the TLS settings you give.

The chart does not deploy PostgreSQL, Redis, or the overlay's relay and gateway. To add the overlay, see Overlay and relays.

Proxies and load balancers

Workbenches keep a Server-Sent Events stream open to the API, and remote control travels down it. Anything between the internet and the API must:

  • not buffer responses (the chart sets nginx.ingress.kubernetes.io/proxy-buffering: "off");
  • allow long reads (the chart sets nginx.ingress.kubernetes.io/proxy-read-timeout: "3600", one hour; the stream reconnects after that).

Sticky sessions are not needed: with Redis, any replica can reach any workbench. See Redis for more than one replica.

Change a setting later

Edit values.yaml, or the secret, then:

Terminal
helm upgrade aioe ./aioe -n aioe -f values.yaml

A change to the secret alone does not restart the pods: run kubectl -n aioe rollout restart deployment/aioe-api after it. Upgrading to a new release is in Upgrades and backups.

On this page