Skip to content

Installation

graal installs on your side, through a Helm chart, into two namespaces of a Kubernetes cluster you administer. This page states what you need before you start, what the platform expects from your platform team, and which objects the installation creates — or does not create — in your cluster.

Item Details
Kubernetes A distribution that provides an ingress controller, certificate management, secret synchronisation and a PostgreSQL operator (see below). Two namespaces, provided by your platform team. Pods follow the PSA restricted profile
PostgreSQL A dedicated application database. The reference deployment uses an operator that generates the role passwords itself
Keycloak 26 One realm per tenant, plus an administration realm. Federating your own directory is possible
S3 object storage MinIO, Ceph RGW or equivalent: data buckets and Apache Iceberg tables
An SMTP relay Invitations, notifications, password resets. Without it those features stay silent — the platform still starts
An ingress controller and a certificate A single hostname is enough: the console and the API share it
An image registry One you control: platform images and runtime images (Python, Spark, Jupyter, VS Code)

The SQL engine (Trino) and the experiment-tracking facade (compatible with the MLflow client, of which it implements a subset) are deployed by the platform; you do not need to provide them.

One constraint worth knowing about the registry: the runtime images repository must be pullable anonymously, because the execution namespace holds no Secret — therefore no pull secret. The platform images themselves may come from a private repository. Each runtime image is a chart value: changing registry means changing those values, without touching any code.

The installation occupies two namespaces, and the split is structural:

Namespace What lives there Why
application namespace console, API, low-code engine, platform services this is the platform’s trust zone
execution namespace users’ jobs and workspaces a workspace runs user code: nothing confidential is mounted there, and no secret is materialised in it

In the reference deployment they are called graal and graal-run. This separation is what lets us say that a user running arbitrary code cannot read the platform’s credentials: they are not in their namespace.

Runtime images are pulled anonymously from a public repository of your registry, with no pull secret, for the same reason. Corollary: nothing confidential, and never a customer’s image, in that repository.

This is deliberate, and it is checked when the chart is rendered: the installation neither creates nor requests the right to create:

  • no namespace — a namespace is a network boundary and a resource ceiling: it does not declare itself;
  • no Secret;
  • no cluster-scoped object: no ClusterRole, no ClusterRoleBinding, no StorageClass, no ClusterIssuer;
  • no CRD installed by graal — the reference deployment does, however, consume the custom resources of four families provided by your platform: ingress, certificates, secrets and database;
  • no Role, RoleBinding, NetworkPolicy, ResourceQuota or LimitRange: your guard rails stay yours, and a tenant that writes its own RBAC has no visible ceiling.

So what your platform team provides once, before the installation: the two namespaces with their quota and network policy, an execution ServiceAccount in the execution namespace, and a namespaced Role allowing the scheduler to create pods there. The scheduler no longer creates a ServiceAccount, a RoleBinding or a pull secret per job.

Practical consequence: graal cannot be installed with a cluster-admin account by accident, and an audit of your permissions does not have to trust our documentation — the rendered chart can be read.

The chart exposes many; six families decide whether the installation works.

GRAAL_SECRETS_SALT_KEY — mandatory, generated once

Section titled “GRAAL_SECRETS_SALT_KEY — mandatory, generated once”

This is the root key of the tenant vault: a project’s secrets (your credentials to your own systems) are encrypted at rest in the database, with AES-256-GCM, using a key derived from this salt through HKDF-SHA256 — a distinct key per tenant, so that a value copied from one tenant to another does not open.

  • Mandatory: missing, too short or empty, the API refuses to start, naming the variable. It is the only setting whose absence stops start-up, and that is deliberate — silently falling back to a default key would be worse than refusing.
  • Permanent: changing it makes everything already sealed unreadable. The value set at the first installation is the one for the whole life of the installation.
  • Generate it once, before the first installation, e.g. openssl rand -hex 16 (at least 32 characters), then back it up the way you back up a database encryption key. Losing it is losing the projects’ secrets.
Variable Role
GRAAL_S3_ENDPOINT_INTERNAL Required to create a tenant: the address through which the API and the pods reach the storage. Without it, creating a tenant fails with TENANT_CONFIGURATION_ERROR
GRAAL_S3_ENDPOINT The storage’s public URL, the one handed to users. Falls back to the internal address
GRAAL_S3_TYPE, GRAAL_S3_REGION, GRAAL_S3_PATH_STYLE_ACCESS, GRAAL_S3_SSL The defaults applied to every tenant

The two addresses are separate because in practice they almost always differ: a pod reaches the storage through an internal service name, a user through a public one.

The origin through which a pod in the execution namespace reaches the API back. It has no useful default in a real cluster, and its absence is invisible: runs fail with no explicit message in the interface. Set it explicitly, and verify it with a first real run rather than by reading health probes.

Variable Role
GRAAL_KEYCLOAK_URL Keycloak’s public origin. The expected token issuer is <origin>/realms/<realm>
GRAAL_KEYCLOAK_AUTH_ENDPOINT_INTERNAL The same, as seen from a pod
GRAAL_KEYCLOAK_JWKS_ENDPOINT Where to download the public keys. Does not change which issuer is accepted
GRAAL_KEYCLOAK_ISSUERS The list of origins accepted as issuer
GRAAL_KEYCLOAK_ADMIN_REALM, GRAAL_KEYCLOAK_ADMIN_CLIENT_ID, GRAAL_KEYCLOAK_ADMIN_USERNAME, GRAAL_KEYCLOAK_ADMIN_PASSWORD The account that provisions tenant realms
GRAAL_KEYCLOAK_UI_CLIENT_ID, GRAAL_KEYCLOAK_UI_REDIRECT_URIS The console’s public client and its allowed redirect URIs
GRAAL_KEYCLOAK_AGENT_CLIENT_ID A tenant’s “graal Application”: the identity of MCP agents

The part that costs the most time: the public origin freezes the tokens’ issuer (iss). A token obtained from outside is rejected inside — and the other way round — if the two views do not point at the same hostname. That is why the public origin and the internal origin are two separate settings, and why only the first decides what is accepted.

The host, the port and the database are not three independent settings but a single URL. The password, in the reference deployment, comes from the PostgreSQL operator and never from a vault: two sources of truth for the same secret, one of them silently ignored, is a rotation that breaks the application with nothing tying it to its cause.

Mind the TLS mode: some managed PostgreSQL distributions refuse any cleartext connection. Where the database is managed, require TLS explicitly.

GRAAL_SMTP_HOST, GRAAL_SMTP_PORT, GRAAL_SMTP_USERNAME, GRAAL_SMTP_PASSWORD, GRAAL_SMTP_AUTH, GRAAL_SMTP_STARTTLS.

Mail is optional: until it is configured the platform works and the mail health indicator stays off rather than declaring the platform unhealthy. An unconfigured feature must not turn the whole thing red.

In this order, because each step assumes the previous one:

  1. The API starts. If it refuses, it names the missing variable — read the pod log before looking anywhere else.
  2. A tenant can be created. This is what exercises the object storage and Keycloak provisioning.
  3. A user signs in to the console through Keycloak. This is what exercises the public origin and the redirect URIs.
  4. A job really runs, and its log comes back. This is the only check that exercises the execution namespace, the provided ServiceAccount, image pulling and GRAAL_API_INTERNAL_URL. A platform with green probes may well never execute a single run: do not stop before this step.