--- description: The settings an operator supplies to a self-managed deployment of Actian AI Analyst, how to set them with the Compose bundle or the Helm chart, and how the server reports a missing or forbidden setting. --- # Configuration reference !!! info "Availability" - **Deployment:** Self-managed deployments only. On Actian SaaS, Actian manages this configuration. This page lists the deployment-wide settings you supply to a self-managed deployment. Members of an organization manage their own settings in **Settings** in the app. Most settings are environment variables that the AI Analyst server reads when it starts. With the Helm chart, a few settings are chart values instead. !!! tip This page lists the settings supported for self-managed deployments. Don't set other environment variables unless Actian asks you to. ## How you supply configuration === "Compose bundle" 1. Copy `.env.example` to `.env` next to `docker-compose.yml`, and fill in every value marked **REQUIRED**. 2. Start or update the stack: ```sh docker compose up -d --wait ``` After you edit `.env`, run `docker compose up -d --wait` again. Compose recreates the containers whose settings changed. A plain `docker compose restart` doesn't pick up changes to `.env`. **The Compose file passes only the keys it lists.** The server container doesn't read all of `.env`. It receives only the keys named in the `server` service's `environment` block of `docker-compose.yml`. A key that this page marks **Helm only** has no effect when you put it in `.env`. **Some settings are fixed by the Compose bundle.** You can't change them in `.env`: | Setting | Fixed value | Why | |---|---|---| | `NETWORK_POSTURE` | `air-gapped` | The Compose bundle always runs Air-gapped. See [Connected and Air-gapped deployments](network-posture.md). | | `ROLES` | `all` | The whole server runs as one process. | | `IDENTITY_BACKEND` | `keycloak` | The bundle includes Keycloak. | | `STORAGE_DRIVER` | `local` | Uploaded files live in the `storage_data` Docker volume. | | `ENTITLEMENT_BACKEND` | `licence` | The server reads your [Licence](licence.md) from `./licence`. | The Compose file also derives the database URL, the public origin and the identity settings from `PUBLIC_HOST`, `POSTGRES_PASSWORD` and related keys. See [Install with Docker Compose](install-docker-compose.md). === "Helm chart" The chart loads one Kubernetes Secret onto every server pod. Its name is set by `server.existingSecret` (default `ai-analyst-secrets`). You create this Secret yourself; the chart never creates it. Put environment variables in this Secret. The JDBC bridge pod, when enabled, loads the same Secret. The single-page app reads its public settings (the `VITE_*` keys) from a separate Secret named by `web.configSecretName` (default `ai-analyst-web-config`). See [Install on Kubernetes with Helm](install-kubernetes-helm.md). **Some settings are chart values.** The chart renders these as environment variables on the server pods, and they override any value for the same key in your Secret: | Chart value | Sets | Notes | |---|---|---| | `server.networkPosture` | `NETWORK_POSTURE` | `connected` (the chart default) or `air-gapped`. Set `air-gapped` explicitly for a deployment with no internet access. | | `server.apiPathPrefix` | `API_PATH_PREFIX` | Set to `api` so the server answers under `/api`, as your ingress forwards it. The `values-airgap.example.yaml` file sets it, together with `server.healthProbePath: /api/health`. | | `jdbcBridge.networkPolicy.allowPrivateEgressCidrs` | `WAREHOUSE_EGRESS_ALLOW_PRIVATE_CIDRS` | A list of CIDRs. Also opens egress from the JDBC bridge. See [Warehouse connectivity](warehouse-connectivity.md). | | `semqlLlmSqlFallback` | `SEMQL_LLM_SQL_FALLBACK` | Default `true`. See [Timeouts and limits](#timeouts-and-limits). | | `server.licence.existingSecret` | The Licence paths and the Licence entitlement backend | See [Licence](licence.md). | | `server.extraCaCerts.configMap` and `server.extraCaCerts.key` | The private CA bundle the server trusts | See [Private certificate authorities](#private-certificate-authorities). | | `embeddings.enabled` | `EMBEDDING_MODEL` and `EMBEDDING_BASE_URL` | Leave it `false` (the default). This section of the chart isn't for customer installs. See [Configure inference](configure-inference.md). | **Applying changes:** - After you change chart values, run `helm upgrade` with your values file. - After you change only the Secret, restart the server pods so they read it again. With a release named `ai-analyst` and the default single server Deployment: ```sh kubectl -n ai-analyst rollout restart deployment/ai-analyst-server-server ``` If you split the server into several roles, restart each server Deployment. If you changed a key that the JDBC bridge reads, restart the bridge Deployment too. ## Settings at a glance | Area | Main keys | Where it's described | |---|---|---| | Core and identity | `ENVIRONMENT`, `DATABASE_URL`, `FRONTEND_ORIGIN`, `IDENTITY_BACKEND`, `KEYCLOAK_*`, `BOOTSTRAP_*` | [Core settings](#core-settings) | | Licence | `LICENCE_DIR`, `LICENCE_FILE` (Compose), `server.licence.existingSecret` (Helm) | [Licence](licence.md) | | Chat | `LLM_*`, `AGENT_STREAM_TIMEOUT_MS` | [Chat and embeddings](#chat-and-embeddings) | | Guardrails | `AGENT_GUARDRAILS_*` | [Guardrails](#guardrails) | | Embeddings | `EMBEDDING_*`, `EMBEDDING_NEXT_*` | [Chat and embeddings](#chat-and-embeddings) | | Product email | `EMAIL_TRANSPORT`, `EMAIL_FROM`, `SMTP_*`, `UNSUBSCRIBE_SIGNING_KEY` | [Product email](#product-email) | | File storage | `STORAGE_DRIVER`, `S3_*` | [File storage](#file-storage) | | Encryption key | `WAREHOUSE_CREDENTIAL_ENCRYPTION_KEY` | [Credential encryption key](#credential-encryption-key) | | Warehouses | `JDBC_BRIDGE_*`, `WAREHOUSE_EGRESS_ALLOW_PRIVATE_CIDRS`, `CONNECTORS_ENABLED`, `WAREHOUSE_EGRESS_IPS` | [Warehouse connectivity](warehouse-connectivity.md) | | Timeouts and limits | `AGENT_STREAM_TIMEOUT_MS`, `FILE_UPLOAD_MAX_BYTES`, `SEMQL_LLM_SQL_FALLBACK` | [Timeouts and limits](#timeouts-and-limits) | | TLS trust | `SERVER_EXTRA_CA_CERTS` (Compose), `server.extraCaCerts` (Helm) | [Private certificate authorities](#private-certificate-authorities) | ## Core settings The install guides walk through these settings in order. This table is a summary. | Key | Required | Description | |---|---|---| | `ENVIRONMENT` | Yes | Set to `production`. The Compose bundle defaults it to `production`. | | `NETWORK_POSTURE` | Yes | `connected` or `air-gapped`. Fixed by the Compose bundle; set with `server.networkPosture` on Helm. | | `DATABASE_URL` | Yes | The Postgres connection URL for the product database. The Compose bundle builds it from `POSTGRES_USER` and `POSTGRES_PASSWORD`. | | `FRONTEND_ORIGIN` | Yes | The URL members open, for example `https://analyst.example.internal`. On Compose it comes from `PUBLIC_HOST`, or from `PUBLIC_ORIGIN` when you use a port other than 443. | | `API_PORT` | Yes | The port the server listens on: `8080`. The Compose bundle sets it for you. | | `PLG_CONFIG` | Yes | Set to `off`. The Compose bundle sets it for you. | | `IDENTITY_BACKEND` | Yes on Air-gapped | `keycloak`. If you leave it unset, the server assumes Auth0, which an Air-gapped deployment refuses. | | `KEYCLOAK_BASE_URL`, `KEYCLOAK_REALM`, `KEYCLOAK_ADMIN_CLIENT_ID`, `KEYCLOAK_ADMIN_CLIENT_SECRET` | With Keycloak | How the server reaches Keycloak's admin API. | | `AUTH0_DOMAIN`, `AUTH0_AUDIENCE` | Yes | Despite the names, with Keycloak these hold the token issuer (the realm URL) and the API audience. | | `BOOTSTRAP_ORG_NAME`, `BOOTSTRAP_ADMIN_EMAIL` | Yes, for the first start | The first organization and its administrator. `BOOTSTRAP_ADMIN_PASSWORD` and `BOOTSTRAP_SIGNUP_DOMAINS` are optional. See [Identity and first sign-in](identity-and-first-sign-in.md). | ## Chat and embeddings No model ships with AI Analyst. You point the deployment at your own OpenAI-compatible APIs. [Configure inference](configure-inference.md) explains each setting, with examples. | Key | Required | Default | Compose | Description | |---|---|---|---|---| | `LLM_PROVIDER` | No | Unset (no deployment default) | Yes | `openai-compatible` for your own API. | | `LLM_BASE_URL` | With `LLM_PROVIDER` | | Yes | The API's base URL, including `/v1`. | | `LLM_MODEL` | With `LLM_PROVIDER` | | Yes | The model id the API serves. | | `LLM_CAPABILITY_PRESET` | With `LLM_PROVIDER` | | Yes | `generic` or `reasoning`. | | `LLM_API_KEY` | No | | Yes | Sent as a bearer token. | | `LLM_FAST_MODEL` | No | `LLM_MODEL` | Yes | The model for quick calls, such as conversation titles. | | `AGENT_STREAM_TIMEOUT_MS` | No | `240000` | Yes | How long one agent response may take, in milliseconds. | | `EMBEDDING_MODEL` | No | Unset (keyword-only search) | Yes | A preset id or your own model id. | | `EMBEDDING_BASE_URL` | Yes on Air-gapped, and for `qwen3-embedding-0.6b` and custom models | | Yes | The API's base URL, without `/v1`. Always set it to your own endpoint. See [Configure inference](configure-inference.md#set-the-deployment-default-embedding-endpoint). | | `EMBEDDING_API_KEY` | No | | Yes | Sent as a bearer token. | | `EMBEDDING_DIMENSIONS` | For a custom model | | Yes | The length of the model's vectors. | | `EMBEDDING_MAX_DISTANCE`, `EMBEDDING_QUERY_PREFIX` | No | | Helm only | Tuning for a custom model. | | `EMBEDDING_NEXT_*` | No | | Helm only | The next model, while you change models. | ## Guardrails Input guardrails check each message a person types for prompt injection and for harmful content. Organizations turn [Guardrail blocking](../settings/data-protection.md#guardrail-blocking) on themselves. These settings control the check for the whole deployment. [PII masking](../settings/data-protection.md#pii-masking) has no deployment setting. The Compose bundle does not forward these keys, so a Compose deployment always uses the defaults. On Helm, set them in the server Secret. The Secret is loaded on every server pod, including when you split roles. Restart the server pods after a change. An invalid value stops the server from starting. | Key | Required | Default | Compose | Description | |---|---|---|---|---| | `AGENT_GUARDRAILS_ENABLED` | No | `true` | Helm only | `true` or `false`. `false` runs no check, and the organization's **Guardrail blocking** switch is disabled. | | `AGENT_GUARDRAILS_INJECTION_THRESHOLD` | No | `0.7` | Helm only | Score at or above which prompt injection is recorded as a flag. The message still runs. A number from 0 (exclusive) to 1. | | `AGENT_GUARDRAILS_INJECTION_BLOCK_THRESHOLD` | No | `0.9` | Helm only | Score at or above which prompt injection stops the message, when the organization has Guardrail blocking on. Must be at least `AGENT_GUARDRAILS_INJECTION_THRESHOLD`, or the server refuses to start. | | `AGENT_GUARDRAILS_MODERATION_THRESHOLD` | No | `0.5` | Helm only | Score at or above which content moderation is recorded as a flag. Content moderation never stops a message. | The default block threshold is the lowest cut that had no false stops on a synthetic set of ordinary analytics questions, including questions that mention age, gender or nationality. Lowering it stops more override attempts and can also stop a legitimate follow-up that uses words such as "disregard". Revisit the value if you change the chat model. What the check covers is described in [Input guardrails](../governance/how-ai-analyst-handles-your-data.md#input-guardrails). ## Product email AI Analyst sends product email, such as Scheduled Insights and notifications, through an SMTP relay that you run. | Key | Required | Default | Description | |---|---|---|---| | `EMAIL_TRANSPORT` | To send email | Unset | Set to `smtp`. | | `EMAIL_FROM` | With `smtp` | | The sender, for example `AI Analyst `. | | `SMTP_HOST` | With `smtp` | | Your relay's host name. | | `SMTP_PORT` | No | `587` | The relay's port. | | `SMTP_TLS` | No | `starttls` | How the connection is secured: `starttls`, `implicit` (typically port 465) or `none`. | | `SMTP_USER`, `SMTP_PASSWORD` | No | | Credentials, if the relay needs them. | | `UNSUBSCRIBE_SIGNING_KEY` | No | | Signs the unsubscribe links in notification emails. Generate one with `openssl rand -hex 32`. Without it, notification emails carry no unsubscribe link. The link is built from a separate public API base URL setting that the Compose bundle doesn't pass, so on Compose the links don't resolve. | - **If `EMAIL_TRANSPORT` is unset, nothing is sent.** The server writes each email to its log instead. - **Use only `smtp` on a self-managed deployment.** `ses` is vendor-hosted, and an Air-gapped deployment refuses `SES_*` keys. An unknown value stops the server at startup. With `smtp`, a missing `SMTP_HOST` or `EMAIL_FROM`, an invalid `SMTP_PORT` or an unknown `SMTP_TLS` value also stops it. - **Keycloak sends its own email.** Invitations, password resets and email verification for sign-up domains come from Keycloak, not from these settings. Configure Keycloak's relay in the Keycloak admin console, under the **aianalyst** realm → **Realm settings** → **Email**. See [Identity and first sign-in](identity-and-first-sign-in.md). - **On Compose, the server and Keycloak can't reach a relay on your network by default.** Run the relay as a container on the stack's network, or use the `compose.customer-network.yml` override. See [Warehouse connectivity](warehouse-connectivity.md#reach-warehouses-on-private-networks) for how the override works. ## File storage AI Analyst keeps uploaded files, their converted Parquet copies and attachments in file storage, separate from Postgres. === "Compose bundle" Files live in the `storage_data` Docker volume. You don't configure anything. Include the volume in your backups. See [Upgrade and backup](upgrade-and-backup.md). === "Helm chart" Use S3-compatible object storage, such as MinIO or Ceph RGW. Put these keys in the server Secret: | Key | Required | Default | Description | |---|---|---|---| | `STORAGE_DRIVER` | Yes | | Set to `s3`. | | `S3_BUCKET` | Yes | | The bucket name. | | `S3_ENDPOINT` | Yes on Air-gapped | Unset (AWS S3) | Your S3-compatible server, for example `https://minio.example.internal`. | | `S3_REGION` | No | `us-east-1` | The region your storage expects. | | `S3_FORCE_PATH_STYLE` | No | `true` when `S3_ENDPOINT` is set | Path-style addressing, which most S3-compatible servers need. | | `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` | No | | Static credentials. Without them, the server uses the default AWS credential chain. | !!! warning "Always set `STORAGE_DRIVER=s3` on Helm" If `STORAGE_DRIVER` is unset, the server keeps files on the pod's temporary disk. Files are lost when a pod restarts, and other pods can't see them. On an Air-gapped deployment, `STORAGE_DRIVER=s3` without `S3_ENDPOINT` stops the server at startup, because the files would go to AWS S3. ## Credential encryption key `WAREHOUSE_CREDENTIAL_ENCRYPTION_KEY` encrypts secrets that AI Analyst stores in its database: - Warehouse credentials. - Catalog connection secrets. - API keys of LLM and Embedding endpoints that an organization adds under **Settings** → **Inference**. The key is required. Generate it once: ```sh openssl rand -hex 32 ``` !!! warning "Keep this key safe and never change it" Store the key outside the deployment, with your other secrets, and keep it with your backups. If you lose or change it after the first start, AI Analyst can't decrypt any credential it stored before. A connection test then fails with **Stored credentials could not be read**. ## Timeouts and limits | Key | Default | Compose | Description | |---|---|---|---| | `AGENT_STREAM_TIMEOUT_MS` | `240000` (4 minutes) | Yes | The longest one agent response may take. Raise it for a self-hosted model that needs minutes to answer and call tools, for example `900000`. | | `FILE_UPLOAD_MAX_BYTES` | `262144000` (250 MiB) | Helm only | The largest CSV file members can upload. An invalid value falls back to the default. If you raise it, raise your ingress request-body limit and the server's memory in proportion. The 260 MB and 1 GiB figures below apply to the 250 MiB default. | | `SEMQL_LLM_SQL_FALLBACK` | On | Helm only (`semqlLlmSqlFallback`) | When the warehouse rejects generated SQL with a dialect or syntax error, AI Analyst asks the LLM to rewrite that SQL once and retries. The rewrite can only use tables and columns that the original SQL already used. Set `semqlLlmSqlFallback: false` to turn it off. | About file uploads: - **Request size.** The Compose bundle's reverse proxy accepts request bodies up to 260 MB. On Helm, configure your ingress to accept request bodies of at least 260 MB, with a read timeout of at least 600 seconds for large uploads. - **Memory.** Converting a 250 MiB CSV file peaks near 1 GiB on top of the running server, so keep headroom above that. On Compose, the `SERVER_MEMORY_LIMIT` default is `3g`. ## Private certificate authorities The server makes HTTPS calls to services you run: Keycloak (to fetch the token signing keys), your LLM and Embedding endpoints, and warehouses. If any of these presents a certificate from a private certificate authority (CA), the server must trust that CA. The server reads the CA bundle only when it starts, so restart it after you change the bundle. === "Compose bundle" By default, the server trusts the internal CA that the bundled Caddy proxy uses for its certificate. To trust your own CA instead: 1. Create one PEM file in `./certs` that contains **every** CA the server needs, including the CA that signed the proxy's certificate. If you still use Caddy's internal certificate, copy its root CA out of the proxy first: ```sh docker compose cp proxy:/data/caddy/pki/authorities/local/root.crt ./caddy-root.crt cat ./caddy-root.crt ./your-internal-ca.pem > ./certs/ca.crt ``` 2. In `.env`, set: ```sh SERVER_EXTRA_CA_CERTS=/etc/ai-analyst/certs/ca.crt ``` 3. Run `docker compose up -d --wait`. !!! warning `SERVER_EXTRA_CA_CERTS` replaces the default trust of Caddy's internal CA. If your file leaves out the CA that signed the proxy's certificate, the server can't verify sign-in tokens and every API call fails with `401`. === "Helm chart" 1. Put the PEM bundle in a ConfigMap: ```sh kubectl -n ai-analyst create configmap ai-analyst-ca --from-file=ca.crt=internal-ca.pem ``` 2. Set these values and run `helm upgrade`: ```yaml server: extraCaCerts: configMap: ai-analyst-ca key: ca.crt ``` ## Health endpoint The server answers `GET /api/health` with its status (with the Helm chart, this assumes `server.apiPathPrefix: api`). The Compose bundle uses it as the server's health check, and the Helm example values use it for the pods' probes. The response includes an `embeddings` field: `ok`, `degraded` (the Embedding endpoint didn't answer the last check), `unconfigured` (no `EMBEDDING_MODEL`) or `unknown` (not checked yet). This field is for information only. An Embedding endpoint that's down never makes the server unhealthy, because search falls back to keyword matching. ## How the server reports configuration problems The server checks its configuration when it starts. If something is wrong, it refuses to start and logs an error that names the setting. To read the error: - Compose: `docker compose logs server` - Helm: `kubectl -n ai-analyst logs deployment/ai-analyst-server-server` (adjust the name for your release and roles) | Problem | Example error | |---|---| | A required key is missing. `DATABASE_URL`, `ENVIRONMENT` and `NETWORK_POSTURE` are always required. | `Missing required env vars for roles [...]: NETWORK_POSTURE` | | `NETWORK_POSTURE` has an unknown value. | `Unknown NETWORK_POSTURE "...". Known postures: ...` | | `ROLES` isn't a known composition. | `ROLES=... is not a known deploy composition.` | | On an Air-gapped deployment, a vendor-hosted key is set. | `NETWORK_POSTURE=air-gapped forbids vendor-hosted env vars: ...` | | On an Air-gapped deployment, a setting would reach a vendor-hosted service. | `NETWORK_POSTURE=air-gapped: ...` | | The deployment default LLM endpoint is incomplete or unknown. | `LLM_PROVIDER=openai-compatible requires LLM_CAPABILITY_PRESET (one of generic, reasoning); ...` | | Product email, embedding or file storage settings are invalid. | For example `SMTP_HOST is required when EMAIL_TRANSPORT=smtp`, or `EMBEDDING_DIMENSIONS must be set to a positive integer for custom embedding model ...` | For the full list of Air-gapped checks, see [How Air-gapped is enforced](network-posture.md#how-air-gapped-is-enforced). !!! note "Split server roles on Helm" When `server.strictEnv` is `true` (the default), a server pod also refuses to start if the Secret contains a key that only a different server role uses. The error lists each key as `set but not declared by any loaded role`. If you split the server into several roles that share one Secret, set `server.strictEnv: false`. Required keys are still checked. See [Scaling and operations](scaling-and-operations.md).