config.yaml Reference

Full schema for the file you pass to definite init. See examples/ for working starting points.

Top-level shape

deployment:    { ... }   # required
broker:        { ... }   # optional, used by default brokered DNS/license setup
postgres:      { ... }   # required
object_store:  { ... }   # required
lakehouse:     { ... }   # optional, defaults to postgres_catalog mode
events:        { ... }   # optional, defaults to enabled
fi:            { ... }   # optional, defaults to disabled
auth:          { ... }   # optional, defaults to local auth
llm:           { ... }   # required
resources:     { ... }   # optional
license:       { ... }   # required for a usable deployment (see `license`)
telemetry:     { ... }   # optional, defaults to disabled

deployment

FieldTypeRequiredDefaultNotes
namestringyesKubernetes-safe name. Lowercase alphanumeric + hyphens, max 63 chars.
namespacestringnodefiniteCreated if missing.
dns_modeenumnodefinite-brokereddefinite-brokered or customer-owned.
hostnamestringconditionalRequired only for dns_mode: customer-owned; brokered installs receive this from Definite during definite init.
tlsenumnocert_managercert_manager, manual, or disabled.

Default installs use Definite-brokered DNS, so the customer does not need to create a DNS record before definite init. To manage DNS yourself, set dns_mode: customer-owned and provide hostname, or pass --dns-mode customer-owned --hostname analytics.customer.com to definite init.

tls: cert_manager uses the production Let's Encrypt issuer created by definite bootstrap. For customer-owned DNS, use a real DNS hostname that you control. Do not use *.nip.io with tls: cert_manager: Let's Encrypt applies a global registered-domain rate limit to nip.io, so new certs for any nip.io hostname are commonly rejected. The CLI rejects that combination before install; for local/no-trust testing use tls: disabled or bring your own cert with tls: manual.

broker

Used by the default deployment.dns_mode: definite-brokered flow. The CLI discovers the ingress controller's load-balancer target, calls the Definite broker, waits for brokered DNS readiness, then uses the returned FQDN and license key for the Helm install.

broker:
  api_url: https://api.definite.app
  setup_token: { env: DEFINITE_ONPREM_SETUP_TOKEN }
  requested_slug: analytics   # optional
FieldTypeRequiredDefaultNotes
api_urlstringnohttps://api.definite.appBroker API base URL. Can also be overridden with DEFINITE_BROKER_API_URL.
setup_tokenSecretRefnoOptional one-time setup token issued by Definite. If omitted, the CLI falls back to DEFINITE_ONPREM_SETUP_TOKEN, then DEFINITE_SETUP_TOKEN, then automatic cloud attestation. Never printed by the CLI.
requested_slugstringnoOptional preferred subdomain slug. Can also be passed with --requested-slug or DEFINITE_BROKER_REQUESTED_SLUG.

If no setup token is configured, brokered installs automatically acquire one from cloud credentials. On GCP, set DEFINITE_ONPREM_GCP_ATTESTATION_SERVICE_ACCOUNT or gcloud auth/impersonate_service_account when the active account must impersonate a service account to mint an audience-bound identity token with an email claim.

postgres

FieldTypeRequiredDefault
urlstringyes
pool_sizeuintno20
schemastringnodefinite

Backups are a prerequisite, not a config field. Definite does not back up the application database — the managed instance behind url must have automatic daily backups and point-in-time recovery enabled before you deploy. The bundled AWS Terraform and scripts/install-eks.sh do this for RDS by default; for Cloud SQL and Azure see prerequisites.md and the backup runbook backup-restore.md.

object_store

Tagged union by type:

S3

object_store:
  type: s3
  bucket: my-bucket
  region: us-east-1
  endpoint: null      # optional, for S3-compatible non-AWS
  credentials:
    mode: irsa
    role_arn: arn:aws:iam::123456789012:role/definite-lakehouse-irsa
  # Static fallback:
  # credentials:
  #   key_id: { env: S3_ACCESS_KEY_ID }
  #   secret: { env: S3_SECRET_ACCESS_KEY }

GCS

object_store:
  type: gcs
  bucket: my-bucket
  credentials: null   # optional; prefer Workload Identity

Azure

object_store:
  type: azure
  account: mystorageaccount
  container: definite
  credentials: null

MinIO

object_store:
  type: minio
  bucket: definite-lake
  endpoint: https://minio.internal:9000
  credentials:
    literal: "accesskey:secretkey"

lakehouse

Optional. The lakehouse is postgres_catalog: the DuckLake catalog lives in a dedicated Postgres database, query plans run client-side in embedded DuckDB (ATTACH 'ducklake:postgres:<DSN>'), and parquet is read from the object store over httpfs. There is no separate query server. This block only tunes the catalog connection and a few persistence knobs — omit it entirely to keep defaults.

lakehouse:
  data_inlining_row_limit: 1000
  # catalog_dsn: derived from postgres.url by default; set only to override.
FieldTypeDefaultNotes
catalog_dsnstringderived from postgres.urllibpq keyword/value connection string for the DuckLake catalog database, e.g. dbname=ducklake_catalog host=H port=5432 user=U password=P. When omitted, the CLI derives it from postgres.url by changing only the database name to ducklake_catalog. Reuse the deployment's existing Postgres server but keep this as a dedicated database (not the application database). Supports ${ENV_VAR} substitution when provided explicitly.
data_inlining_row_limituint1000DuckLake row threshold for inlining small inserts/deletes into the metadata catalog. Keep this at least as high as events.max_batch_rows if event batches should inline; 0 disables inlining.
prefixstringlake/Object-store prefix where lakehouse parquet data lives.

events

Small-batch event ingestion endpoint (POST /api/v1/events/{schema}/{table}). Defaults are tuned so event writes fit under DuckLake's inlining threshold.

events:
  enabled: true
  max_batch_rows: 1000
  max_payload_bytes: 1048576
FieldTypeDefaultNotes
enabledbooltrueEnables the event ingest API.
max_batch_rowsuint1000Maximum events accepted in one request. Keep this less than or equal to lakehouse.data_inlining_row_limit for inline writes.
max_payload_bytesuint1048576Maximum request body size.

fi

Fi is disabled by default. Enabling it requires kubernetes-sigs Agent Sandbox v0.4.5 CRDs/controller to already be installed in the cluster; definite doctor verifies that prerequisite.

fi:
  enabled: true
  web_access:
    enabled: true              # optional; default true
  web_search:
    provider: duckduckgo       # optional; default duckduckgo
    api_key: { ... }           # SecretRef; required for brave/tavily/serpapi
    url: https://searxng.internal   # searxng provider only
  sandbox:
    template_name: definite-fi
    image: ghcr.io/definite-app/fi-sandbox:0.0.4
    runtime_class_name: gvisor   # optional; omit for default runtime
    workspace_size: 10Gi
    storage_class_name: gp3      # optional
    max_active_threads: 5
    idle_ttl_seconds: 604800
    resources:
      request_cpu: "1"
      request_memory: 2Gi
      limit_cpu: "4"
      limit_memory: 8Gi
    egress:
      public_ports: []           # optional; [] = all ports allowed
      private_cidrs: []          # optional; exact private CIDRs to allow
      private_ports: [5432]      # optional; TCP ports for private_cidrs

The API creates one Agent Sandbox SandboxClaim per Fi thread and reuses its PVC-backed /workspace across runs. Interactive Fi chat sandboxes do not receive object-store or Postgres credentials; query/catalog access goes through the API and lakehouse. Compute-profile SQL workers use the same sandbox template, but receive the lakehouse/object-store query environment they need to run SQL inside the worker pod. For v1, sandbox pods do receive the configured LLM credentials so the Fi agent can call the customer's provider directly from inside the sandbox.

fi.sandbox.egress

A Kubernetes NetworkPolicy governs what the Fi sandbox can reach. In-cluster traffic (the API pod, DNS, the LiteLLM proxy, the GCP metadata server) is fixed. The default tunable knob is which ports the sandbox may open to the public internet — any public IP outside the RFC1918 ranges, which stay blocked so the sandbox cannot dial internal cluster pods/services on arbitrary ports. Operators can also opt in to exact private CIDRs when a sandbox or compute-profile worker must reach a private dependency such as the DuckLake Postgres catalog.

FieldTypeRequiredDefaultNotes
egress.public_portslist[int]no[]Ports the sandbox may reach on public IPs. Empty list = all ports allowed, so the agent can connect directly to external databases (Postgres 5432, MySQL 3306, etc.) and arbitrary APIs. Set an explicit allowlist (e.g. [443]) to restrict it.
egress.private_cidrslist[string]no[]RFC1918 IPv4 or IPv6 ULA CIDRs the sandbox may reach. Empty keeps private ranges blocked. Set exact CIDRs such as ["10.224.0.3/32"] when compute-profile workers need a private catalog endpoint.
egress.private_portslist[int]no[5432]TCP ports allowed for private_cidrs. Defaults to Postgres.

The default is intentionally open: the Fi agent often needs a direct connection to a customer's source database. To lock the sandbox down to HTTPS only, set public_ports: [443].

Fi can search the public web and fetch URLs via two tools — definite_web_search and definite_fetch. Both run server-side in the API pod, not the sandbox, so the API owns egress, size caps, and timeouts.

FieldTypeRequiredDefaultNotes
web_access.enabledboolnotrueSet false for air-gapped installs; the tools are then not registered.
web_search.providerenumnoduckduckgoduckduckgo | searxng | brave | tavily | serpapi.
web_search.api_keySecretReffor keyed providersRequired for brave / tavily / serpapi.
web_search.urlstringfor searxngBase URL of a self-hosted SearXNG instance.

duckduckgo is the default because it is free and needs no API key — web search works on a fresh install with nothing to configure.

auth

Tagged union by mode:

Local

auth:
  mode: local
  initial_admin_email: admin@example.com   # optional
  initial_admin_password:                  # optional; enables first admin password login
    env: INITIAL_ADMIN_PASSWORD

OIDC

auth:
  mode: oidc
  issuer: https://issuer.example.com
  client_id: my-client-id
  client_secret:
    env: OIDC_CLIENT_SECRET
  scopes: [openid, email, profile]   # optional

email

Optional transactional email settings. When configured, /login shows a self-service password reset flow, and automation pipelines can send rendered data-app reports with attachments. When unset, /login shows the workspace-admin and operator recovery paths instead.

email:
  mode: smtp                         # disabled | smtp | definite_cloud
  enabled: true
  from: "Definite <no-reply@example.com>"
  reply_to: "support@example.com"     # optional
  smtp:
    host: smtp.example.com
    port: 587
    username: smtp-user               # optional for trusted relays
    password:
      env: EMAIL_SMTP_PASSWORD        # optional for trusted relays
    starttls: true
    ssl: false
  definiteCloud:
    centralApiUrl: https://api.definite.app
    deploymentId: ""                  # defaults to license.deploymentId
    from: hello@definite.app

definite_cloud is for Definite-managed hosted deployments. Self-managed installs should use smtp with a customer-owned relay.

See password-recovery.md for behavior and fallback recovery details.

llm

Tagged union by provider:

llm:
  provider: anthropic
  model: claude-sonnet-5
  api_key: { env: ANTHROPIC_API_KEY }

# or

llm:
  provider: bedrock
  region: us-east-1
  model: claude-sonnet-4-6
  inference_profile_prefix: us   # geo prefix of the cross-region profile;
                                 # us (default) | eu | apac | au | ...

# or

llm:
  provider: vertex
  project: my-project
  region: global
  model: claude-sonnet-5

# or

llm:
  provider: azure_openai
  endpoint: https://acme.openai.azure.com
  deployment: gpt-4o
  api_key: { env: AZURE_OPENAI_API_KEY }

serviceAccount

Optional Helm ServiceAccount settings. The chart names the shared ServiceAccount after deployment.name; annotations here are rendered onto that shared ServiceAccount. On EKS Bedrock installs, set the IRSA role annotation from Terraform:

serviceAccount:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/definite-bedrock-irsa

When S3 uses object_store.credentials.mode: irsa, the chart also creates a dedicated <deployment.name>-lakehouse ServiceAccount for the API and job-runner pods and annotates it with object_store.credentials.role_arn.

resources

Per-component sizing. All fields optional.

resources:
  api:        { replicas: 2, cpu: "1",   memory: 4Gi }
  frontend:   { replicas: 2, cpu: 500m,  memory: 512Mi }
  job_runner: { replicas: 1, cpu: 500m,  memory: 2Gi }

There is no separate lakehouse component to size: lakehouse queries run as embedded DuckDB inside the API and job-runner pods. Size api.memory for interactive Query-page / data-app SQL and job_runner.memory for automation and maintenance SQL. For heavy bursty SQL, prefer a compute profile over permanently fattening these pods.

Sizing for heavy SQL

Lakehouse queries run as embedded DuckDB inside the API and job-runner pods (ATTACH 'ducklake:postgres:<DSN>', parquet over httpfs) — there is no separate lakehouse query pod to size or pin. For steady-state query memory, raise resources.api.memory (interactive Query-page / data-app SQL) and resources.job_runner.memory (automation + maintenance SQL).

For bursty heavy SQL — one-off migrations, big joins, full-table re-materializations — prefer a compute_profiles block over permanently fattening the always-on pods. A burst profile spawns a fresh sandbox pod per call (so the upstream DuckDB connection-scoped memory leak does not accumulate) and can pin that pod to a dedicated, scale-to-zero node pool. See compute-profiles.md for the per-cloud node-pool setup (GKE / EKS / Karpenter / AKS) and when to raise the always-on pods vs. add a burst profile.

compute_profiles

Named burst tiers a user, Fi thread, or pipeline Python step can pick at runtime to land on a bigger pod. Each entry renders its own SandboxTemplate and an optional SandboxWarmPool. Empty / omitted uses the default sandbox settings from fi.sandbox.*.

compute_profiles:
  large:
    resources:
      requests: { cpu: "4",  memory: "16Gi" }
      limits:   { cpu: "8",  memory: "32Gi" }
    node_selector:
      cloud.google.com/gke-nodepool: definite-burst   # EKS / AKS keys differ
    tolerations:
      - { key: definite-app/burst, operator: Equal, value: "true", effect: NoSchedule }
    warm_pool_size: 0      # cold-start; node pool can scale to zero
    max_concurrent: 2      # attached SQL worker slots
    worker_idle_ttl_seconds: 300
FieldTypeDefaultNotes
resourcesK8s resources mapinherit fi.sandbox.resourcesStandard {requests, limits}. GPUs etc. flow through.
node_selector{label: value}nonePins to a specific node pool. See compute-profiles.md for per-cloud label keys.
tolerationslistnoneRequired when the pool carries a taint.
warm_pool_sizeintfi.sandbox.warmPoolSize0 = cold-start; bigger numbers reserve idle pods at the profile's cost.
worker_idle_ttl_secondsintfi.workerTier.idleTtlSecondsHow long an attached SQL worker stays warm after the last query before the API reaper deletes its claim.
workspace_sizestringfi.sandbox.workspaceSizePer-pod ephemeral PVC.
storage_class_namestringfi.sandbox.storageClassNameStorageClass for the workspace PVC.
max_concurrentintnoneMaximum attached SQL workers for the profile when worker tier is enabled. Excess queries queue briefly, then return 503.

Profile names must be DNS-label safe (lowercase alphanumeric + hyphens, max 40 chars). The default profile is always present — declare it explicitly to override defaults, or omit it to fall back to fi.sandbox.*. See compute-profiles.md for the full guide including node-pool setup and a TPC-H recipe.

license

license:
  key: { env: DEFINITE_LICENSE_KEY }
  # Optional. Leave unset unless Definite support tells you to preserve a
  # known deployment identity during restore or test automation.
  deploymentId: "00000000-0000-4000-8000-000000000000"
  # Optional restore/test hook. Must be signed by a key trusted through
  # telemetry.publicKey or the image-bundled Definite public keys.
  initialEntitlementJwt: { env: DEFINITE_INITIAL_ENTITLEMENT_JWT }

Required for customer-owned/manual licensing. In the default brokered install flow, definite init receives a license key from the broker and uses it for the install without printing it. Otherwise, the CLI renders license.key into the definite-secrets Kubernetes Secret; the definite-api pod reads the Secret's license-key entry as LICENSE_KEY and activates against the central Definite API. Without a license block or externally managed license-key Secret entry, the deployment still boots, but it comes up unlicensed and every product API route returns HTTP 403. definite init will print a warning, and definite doctor flags the missing block.

Advanced installs that use an external secret store may leave license.key unset and sync license-key into the chart-managed definite-secrets Secret instead. The chart wires LICENSE_KEY and LICENSE_INITIAL_ENTITLEMENT_JWT from that Secret with optional secretKeyRefs, so an unlicensed install still starts. Kubernetes reads env vars only at container startup; make sure your external secret controller or a reloader rolls the API pod when the synced license changes.

The key is issued by Definite (internally, via pybe onprem issue-license). key is a SecretRef — supply it as an env var, a file, or (not recommended) an inline literal. The k8s_secret SecretRef form is not supported for license fields; external secret store installs should sync license-key into definite-secrets instead. To license or re-license a running deployment, add or update this block and run definite upgrade. Check the live entitlement with definite license status.

deploymentId is an advanced restore/test hook. On a fresh application database, the API adopts it as the stable deployment UUID before activation. Once the deployment has activated, changing deploymentId is ignored so the central license binding is not silently moved.

initialEntitlementJwt is also advanced. On a fresh database, the API verifies the JWT and persists it before trying central activation. This is for restore or controlled test automation, not the normal customer licensing path.

telemetry

telemetry:
  enabled: false   # opt-in

image

Override the container image source for all Definite components. Useful when you've mirrored images into your own registry (Artifact Registry, ECR, ACR, Harbor, etc.). All fields are optional; omit the block entirely to use the release defaults.

FieldTypeDefaultNotes
registrystringrelease defaultRegistry + repo prefix; the chart appends /<component>:<tag>.
tagstringCLI versionImage tag. Defaults to the version of the definite CLI itself, so chart and CLI ship in lockstep.
pull_policystringIfNotPresentKubernetes imagePullPolicy.
pull_secretsstring[][]Names of imagePullSecrets in the deployment namespace.
image:
  registry: us-central1-docker.pkg.dev/acme-prod/definite
  tag: 0.0.4
  pull_policy: IfNotPresent
  pull_secrets:
    - artifact-registry-pull

SecretRef (used by many fields)

A SecretRef is a tagged union telling the CLI where to find a sensitive value:

# Read from an environment variable at deploy time
field: { env: SOME_ENV_VAR }

# Read from a file at deploy time
field: { file: /etc/secrets/postgres-password }

# Reference an existing Kubernetes Secret
field:
  k8s_secret:
    name: my-existing-secret
    key: password

# Inline literal (NOT RECOMMENDED — value ends up in Helm release)
field: { literal: "plaintext-value" }

For env, file, and literal refs the CLI resolves the value to plaintext at deploy time and the chart writes it to a Kubernetes Secret it manages. k8s_secret refs point the chart at an existing Secret you already manage with your own tooling (External Secrets Operator, Vault sync, etc.) — note that end-to-end passthrough for k8s_secret is partial today; some fields still require env / file / literal.

Environment variable substitution

Anywhere in the config, ${VAR} is replaced by the value of VAR at config load time. Missing variables fail loud — they don't silently produce empty values.

postgres:
  url: postgres://definite:${POSTGRES_PASSWORD}@db.internal:5432/definite