Kumoss

Environment variables and secrets

Every environment variable Kumoss reads: the core, the four sidecars, the IaC engine, and the LLM providers.

Kumoss keeps every secret in environment variables. config.yaml holds only non-secret settings and, where a secret is needed, the name of the variable that carries it (fields ending in _env). This page documents every variable in core/env.sample and in the four sidecar env.sample files, plus the variables that the IaC engine and the LLM providers read on their own.

Each env.sample is a template: copy it to a .env file next to it (core/.env, services/<name>/.env). Docker Compose loads those .env files per container. core/.env is required for the stack to start; the sidecar files are optional and the containers fall back to their built-in defaults when a file is missing.

All example values in this page are fictitious. Never commit a .env file; they are gitignored.

Which files you need depends on the deployment model: Quickstart needs core/.env and services/iac/.env (plus the notifications file when enabled); Deploy to production replaces the files with a secret store but keeps the same variable names.

Core (core/env.sample)

The core reads variables through the names configured in config.yaml. The table lists the default names; if you rename a *_env field, rename the variable accordingly.

Variable Requirement Default Meaning Checked

LLM provider credentials (ANTHROPIC_API_KEY for the shipped anthropic/ models; for example AZURE_AI_API_KEY and AZURE_AI_API_BASE, or the VERTEXAI_PROJECT / VERTEXAI_LOCATION / VERTEXAI_CREDENTIALS trio, for other providers)

Mandatory for the provider selected by llm.model, llm.small_model or llm.model_list

none

Credentials that LiteLLM reads for the configured provider. Names follow LiteLLM conventions; see LLM providers and models. VERTEXAI_CREDENTIALS holds the service-account key itself, as single-line JSON content (see the Vertex AI example). For anthropic/* models LiteLLM accepts either ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN.

At boot for providers LiteLLM can validate; otherwise on the first LLM call

KUMOSS_IAC_TOKEN

Mandatory (the IaC sidecar is always called; it has no enabled flag)

sample value dev-iac-token

Bearer token the core sends to the IaC sidecar. Must equal KUMOSS_IAC_TOKEN in services/iac/.env.

At boot: an empty token aborts startup

KUMOSS_MAPPING_TOKEN

Conditional

sample value dev-mapping-token

Bearer token for the mapping sidecar, sent when services.mapping.enabled is true. Must equal KUMOSS_MAPPING_TOKEN in services/mapping/.env.

At boot when enabled

KUMOSS_NOTIFICATIONS_TOKEN

Conditional

sample value dev-notifications-token

Bearer token for the notifications sidecar, sent when services.notifications.enabled is true. Must equal KUMOSS_NOTIFICATIONS_TOKEN in services/notifications/.env.

At boot when enabled

KUMOSS_AUTHZ_TOKEN

Conditional

sample value dev-authz-token

Bearer token for the authorization sidecar, sent when services.authz.enabled is true. Must equal KUMOSS_AUTHZ_TOKEN in services/authz/.env.

At boot when enabled

GIT_USER

Optional but needed for pushes

empty

Account username at the Git provider selected by git.provider (default GITHUB).

At boot the core logs a warning when empty; pushes fail later unless credentials come from elsewhere

GIT_TOKEN

Optional but needed for pushes

empty

Personal access token for that account. Written with GIT_USER into ~/.git-credentials inside the core container at boot.

Same as GIT_USER

RUSTFS_ACCESS_KEY

Conditional

rustfsadmin when storage.provider is RUSTFS and the variable is unset; empty otherwise

Access key for the S3-compatible store, read when storage.provider is RUSTFS (or S3 with static keys). Used for the artifacts bucket and — where Kumoss-managed state is enabled (storage.terraform_state_bucket non-blank; that is the shipped default) — for the state bucket too, in which case it is also embedded into the backend block the IaC sidecar executes. Must match the access key of the object-storage service. The bundled docker-compose.yml sets none, so RustFS uses its built-in rustfsadmin.

At boot: the core creates or checks every configured bucket and aborts if the store is unusable

RUSTFS_SECRET_KEY

Conditional

rustfsadmin when storage.provider is RUSTFS and the variable is unset; empty otherwise

Secret key for the artifact store, matching the secret key of object-storage (RustFS’s built-in rustfsadmin with the bundled compose file). For S3, leave both keys unset to use the AWS SDK default credential chain (an instance role or workload identity), or set static keys under these same variable names unless you rename storage.access_key_env / storage.secret_key_env.

At boot

STORAGE_ACCOUNT_KEY

Conditional

empty

Shared key of the Azure storage account, read when storage.provider is STORAGE_ACCOUNT; also signs download URLs. The account name is derived from storage.endpoint_url.

At boot: configuration validation fails if empty

KUMOSS_SQL_DATABASE_URL

Mandatory

sample value points at the bundled core-db container

Connection URL of Kumoss’s PostgreSQL database. Must carry the credentials of the core-db service in docker-compose.yml.

At boot: configuration validation fails if empty, and database initialisation fails if unreachable

KUMOSS_REDIS_URL

Optional; not present in core/env.sample

redis://redis:6379/0 from redis.default_url

Redis URL for the session cache. Add it only to point at an external Redis — for example when the URL embeds a password or sits outside the compose network. Cache operations fail open at runtime, but the boot-time ping must succeed.

At boot: Redis initialisation fails if unreachable

KUMOSS_CONFIG

Optional; not present in env.sample

/etc/kumoss/config.yaml

Path, inside the container, of the configuration file to load. The file must exist at that path (for example through a mounted volume); a missing file silently falls back to built-in defaults.

At boot

APP_VERSION

Optional; not present in env.sample

The release version of the source the core was built from

Version string reported by the API’s OpenAPI document. Leave it unset to report the built release; set it only to override that, for example on a patched build.

never

The rustfsadmin fallback applies only when the variable is absent from the environment. A variable that is present but blank (RUSTFS_ACCESS_KEY= in core/.env) resolves to the empty string, no fallback is applied, and under storage.provider: RUSTFS the core embeds no static credentials at all: boto3 falls back to the AWS default credential chain, which has nothing to find in the container, and boot fails at the bucket check. Blank the two keys only for storage.provider: S3 with an instance profile or IRSA, where an empty value is what tells the core to omit them and let the AWS default chain resolve.

What the boot check actually covers. The core validates the LLM credentials with litellm.validate_environment for every entry in the effective model list. Coverage depends on the provider. For the shipped anthropic/…​ models a missing key fails the boot. For azure_ai/…​ models the check verifies nothing: LiteLLM has no validation mapping for that provider, so a missing AZURE_AI_API_KEY or AZURE_AI_API_BASE boots cleanly and fails on the first LLM call. For vertex_ai/…​ models the check requires VERTEXAI_PROJECT and VERTEXAI_LOCATION only; VERTEXAI_CREDENTIALS is not verified at boot, and when it is unset LiteLLM falls back to Application Default Credentials. Coverage is per provider (for anthropic/*, either ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN satisfies it), and no provider’s check validates that a credential actually works.

litellm loads core/.env by itself. The library calls load_dotenv() at import time unless LITELLM_MODE is PRODUCTION, so running the core or its test suite outside Docker silently picks up core/.env even when you meant to run with a clean environment. Running from another directory does not avoid it: python-dotenv searches upwards from the calling package’s directory, not from the working directory, so a virtual environment at core/.venv finds core/.env from anywhere. Variables already set in the environment are left alone. To reproduce a missing-credential failure, set LITELLM_MODE=PRODUCTION, move core/.env aside, or use a virtual environment outside core/.

Two related facts about the sample file:

  • The pre-filled dev-*-token placeholders are accepted because the bundled sidecars ship with an empty expected token, which disables their bearer check. That is acceptable only on an isolated workstation.

  • Nothing OIDC-related goes in core/.env. The single-page application is a public client using PKCE (Proof Key for Code Exchange), and the core validates tokens with the issuer’s public keys. All OIDC settings live in config.yaml.

Read by libraries, not by Kumoss

Two more variables can matter to the core even though no Kumoss code reads them. Neither is in core/env.sample, and neither is needed for the bundled Phoenix.

Variable Read by Meaning

OTEL_EXPORTER_OTLP_HEADERS

the OpenTelemetry SDK

Comma-separated key=value headers added to OTLP exports — the usual way to authenticate against a hosted collector. The core builds its OTLPSpanExporter with the endpoint only and passes no headers of its own.

PHOENIX_API_KEY

the phoenix.client library

API key for an authenticated Phoenix instance, used when fetching and seeding prompt templates. The core constructs AsyncClient(base_url=…​) and supplies no credential.

Because the core passes the endpoint and base URL programmatically, whether each library still picks these variables up from the environment in that configuration is not verified in this repository — consult the OpenTelemetry Python and Phoenix client documentation for the version you run before relying on them.

IaC sidecar (services/iac/env.sample)

Variable Requirement Default Meaning Checked

KUMOSS_IAC_TOKEN

Recommended; mandatory outside an isolated workstation

empty (accepts any bearer)

Token the sidecar requires on every /v1/* call. Must equal the core’s KUMOSS_IAC_TOKEN.

Per request: a mismatch returns 401 to the core

IAC_BINARY

Optional

tofu

Name or absolute path of the IaC engine CLI. tofu runs the bundled OpenTofu; terraform runs the bundled HashiCorp Terraform (BUSL-1.1 licensed; your use is subject to its terms). Any Terraform-compatible engine on PATH works.

At boot: the service refuses to start if the binary cannot be found

IAC_BACKEND_CONFIG

Optional

empty (unset)

Path to a .hcl or .tfbackend file of state-backend values, passed to every init as -backend-config=<path>. Absolute paths resolve inside the sidecar container, so mount the file there yourself; relative paths resolve against the workspace, so the target repository can carry the file. It supplies values only — the backend type still comes from the workspace’s own terraform { backend "…​" } block. Its values take precedence over the core’s backend_override.tf, which in turn beats the repository’s own block; keys the file does not declare fall through, so setting it together with Kumoss-managed state (storage.terraform_state_bucket set) merges the two backends key by key instead of picking one — a misconfiguration to avoid, not a layering feature. Blank or whitespace counts as unset. See Sidecar-supplied backend.

No: unlike IAC_BINARY this path is not checked at startup, so a wrong one fails on init, in that job’s stderr

The job retention period (how long a finished job stays pollable at GET /v1/jobs/{job_id}, one hour) is a constant in the sidecar’s own configuration, not an environment variable. Jobs live in memory, so a restart also forgets them.

Cloud credentials for the IaC engine

The IaC sidecar does not interpret cloud credentials. It launches the engine with its entire container environment inherited and no allowlist, so every variable in services/iac/.env (including KUMOSS_IAC_TOKEN) is visible to the Terraform and OpenTofu providers and to any external or local-exec code in the repositories you run. The providers read their credentials directly during init, plan, and apply. Missing or invalid credentials never stop the container; the command runs and the engine’s own authentication error appears in the job’s stderr, which the session shows to the user. Keep only the variables the engine needs in that file, and treat the sidecar’s environment as exposed to the IaC code it executes.

The sidecar’s /v1/import/scope-resource-ids endpoint is the one exception to "does not interpret": it launches no engine and instead reads the same ARM_*, GOOGLE_*, and AWS_* credential variables itself through the clouds' auth libraries to query Azure Resource Graph, GCP Cloud Asset Inventory, or AWS Resource Explorer. Two extra requirements apply to it only: on AWS AWS_REGION or AWS_DEFAULT_REGION must be set and Resource Explorer must be enabled for the account; on GCP the cloudasset.googleapis.com and cloudresourcemanager.googleapis.com APIs must be enabled. The core does not call this endpoint today. See Import discovery in the IaC sidecar reference.

The sample file lists no cloud variables; add the ones your modules need. Terraform providers gives the minimum set per cloud and authentication method (service principal, OIDC federation, managed identity, named profile, and so on) plus the state-backend minimums, and its exhaustive section lists every variable each provider and backend reads. The table below is a short orientation; the sidecar imposes nothing beyond what the provider supports.

Cloud Typical variables Provider chain (examples, not a complete list)

Azure (azurerm, azuread)

ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_TENANT_ID, ARM_SUBSCRIPTION_ID

Service principal with client secret. Certificate, OIDC federation, and managed identity are other documented provider options. ARM_SUBSCRIPTION_ID set here is overwritten on every init, plan, apply, and import with the request’s scope id, so it only takes effect for validate, show, and state pull.

Google Cloud (google)

GOOGLE_APPLICATION_CREDENTIALS or GOOGLE_CREDENTIALS

GOOGLE_CREDENTIALS holds the service-account key as JSON content; GOOGLE_APPLICATION_CREDENTIALS holds a path to a key file readable inside the container. Set one of them. GOOGLE_PROJECT set here is overwritten on every init, plan, apply, and import with the request’s scope id, so it only takes effect for validate, show, and state pull.

AWS (aws)

AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, or AWS_PROFILE

Static keys, or a profile from a mounted credentials file. Instance roles and web-identity federation are other documented provider options.

Oracle Cloud (oci)

OCI_* or TF_VAR_*

The provider’s documented mechanisms, for example an OCI configuration file mounted into the container, or instance principals.

Kubernetes (kubernetes, helm)

KUBE_CONFIG_PATH

A mounted kubeconfig (KUBE_CONFIG_PATHS for several), KUBE_HOST
KUBE_TOKEN, or in-cluster service-account credentials. See Terraform providers.

These are the provider credentials, the ones plan and apply use on the resources themselves. init needs a second, separate grant on the state store, which may be a different account or even a different cloud, and which some variables serve exclusively — ARM_ACCESS_KEY, ARM_SAS_TOKEN, ARM_USE_AZUREAD, GOOGLE_BACKEND_CREDENTIALS, GOOGLE_IMPERSONATE_SERVICE_ACCOUNT. Both grants live in the same services/iac/.env; which variables you need for which is in Two sets of credentials on the sidecar.

Credential files you mount must be readable by the unprivileged user the image runs as (kumoss, uid and gid 10001 by default).

Credentials for the state backend

The sidecar is the process that reads and writes Terraform state, so state-backend credentials are always a sidecar concern.

  • By default (storage.terraform_state_bucket non-blank — that is the shipped value), Kumoss manages state. When the core’s rendered backend block contains static keys (RUSTFS, or S3/STORAGE_ACCOUNT with keys set in core/.env), the sidecar needs nothing extra for state.

  • When Kumoss-managed state is on and the block omits those keys (provider: S3 with RUSTFS_ACCESS_KEY/RUSTFS_SECRET_KEY blank), the engine resolves credentials from the sidecar’s own environment: AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, AWS_PROFILE, or an attached instance role or IRSA token. Granting that role to the core container only is the common mistake — the core creates the bucket at boot.

  • With Kumoss-managed state on, the sidecar must also reach storage.endpoint_url. In the Compose stack core and iac share bridge-network, so http://object-storage:9000 resolves; elsewhere, allow that egress.

  • If a deployment sets storage.terraform_state_bucket to "", the backend comes from the target repository instead, and the sidecar must hold whatever that backend needs — S3 keys or a role, an Azure identity, a GCS service account. Nothing on the core side helps here.

Where state lives, and how each provider is configured, is covered in Configure state backends.

Mapping sidecar (services/mapping/env.sample)

Variable Requirement Default Meaning Checked

KUMOSS_MAPPING_TOKEN

Recommended when the sidecar is enabled

empty (accepts any bearer)

Token required on POST /v1/resolve. Must equal the core’s KUMOSS_MAPPING_TOKEN.

Per request

The bundled mapping service is an identity passthrough: it returns the identifier it receives as both repo_url and identifier, echoes back any terraform_provider it was sent, and always answers null for scope_id — it knows nothing it was not told, and null means "ask the user". While services.mapping.enabled is false (the default) the core performs that same mapping itself and never calls the service. Once enabled, a sidecar that times out or is unreachable makes the wizard’s resolve step fail with 504 or 502, and so does a response the core cannot parse or one naming a provider outside the contract’s enum; there is no silent fallback.

Notifications sidecar (services/notifications/env.sample)

Variable Requirement Default Meaning Checked

KUMOSS_NOTIFICATIONS_TOKEN

Recommended when the sidecar is enabled

empty (accepts any bearer)

Token required on POST /v1/notify. Must equal the core’s KUMOSS_NOTIFICATIONS_TOKEN.

Per request

SLACK_WEBHOOK_URL

Mandatory for the bundled container to start

empty

Slack incoming-webhook URL the service posts to. Treat it as a secret: anyone holding it can post to the channel. The service never logs it.

At boot: the container exits with ConfigError when empty

LOG_LEVEL

Optional

INFO

Root log level (DEBUG, INFO, WARNING, ERROR).

At boot: an unknown level name aborts startup

The core sends four kinds of notification on its own: iac.compliance.failed and iac.impact.high at the end of a generate round, iac.apply.failure when an apply fails, and system.exception.failure when a background run raises. Users can also send free-form support requests through POST /api/v1/notifications (see Admin portal). The audience of every notification is the session owner (or the caller) plus every user with a panel role of editor or higher. Delivery is best-effort: the core logs Failed to send '<kind>' notification and continues. The bundled sidecar delivers synchronously to Slack with a 10-second budget and answers 502 when Slack rejects the message.

The bundled notifications container refuses to start without SLACK_WEBHOOK_URL, even when the core integration is disabled. Because Compose starts every sidecar regardless of config.yaml, a notifications container that exits immediately is expected and harmless while services.notifications.enabled is false. No restart policy is set for it (only proxy carries restart: on-failure), so it exits once and then shows as Exited in docker compose ps — it is not restarted in a loop. Remove the service from your Compose file or provide a webhook URL if you would rather not see it.

Authorization sidecar (services/authz/env.sample)

Variable Requirement Default Meaning Checked

KUMOSS_AUTHZ_TOKEN

Recommended when the sidecar is enabled

empty (accepts any bearer)

Token required on /v1/*. Must equal the core’s KUMOSS_AUTHZ_TOKEN.

Per request

KUMOSS_AUTHZ_ROOT_ADMIN_EMAIL

Optional

empty

When set, the sidecar grants its own admin role to a user record keyed by this email at startup.

At boot

KUMOSS_AUTHZ_PERMISSIVE

Optional

true

true (case-insensitive): POST /v1/check answers authorized: true for every request. Any other value: it answers authorized: false for every request. Neither value implements a real policy.

At boot

KUMOSS_AUTHZ_ROLE_STORE

Optional

/data/roles.json

Path of the JSON file holding the sidecar’s user-to-roles map. The path is container-local in the shipped Compose file (no volume is mounted), so assignments are lost when the container is recreated; point it at a mounted path if you need them to survive.

On first write

Two things to keep apart:

  • The bundled authorization sidecar is a permissive reference implementation. It exists so that the contract in contracts/openapi/authz.v1.yaml has a runnable example. The core consults it only from POST /v1/auth/authorize (a cloud-project access check) and only when services.authz.enabled is true. Enabling it without replacing the logic changes nothing about who is allowed to do what. While disabled, the core answers "authorized" without any call; once enabled, an unreachable or slow sidecar makes the preflight fail with 502 or 504 rather than allowing the request.

  • The sidecar’s admin and user roles, its root-admin bootstrap, and its /v1/users endpoints belong to the sidecar alone. Kumoss’s own operation roles (developer, devops) and admin-panel roles (viewer, editor, admin) live in the core database and are documented in the Admin portal guide.

Cloud-provider credentials at a glance

Consumer Where Purpose

Terraform or OpenTofu providers

services/iac/.env (see Terraform providers)

init, validate, plan, show, apply against your cloud

AWS SDK (boto3) in the core

core/.env (RUSTFS_* or the default chain)

Artifact storage when storage.provider is RUSTFS or S3

Azure Storage shared key in the core

core/.env (STORAGE_ACCOUNT_KEY)

Artifact storage when storage.provider is STORAGE_ACCOUNT

LiteLLM in the core

core/.env

Cloud-hosted LLM providers (Vertex AI, Bedrock, Azure OpenAI, and so on)

Cloud credentials for the IaC engine and for the LLM provider are independent. A stack that generates AWS infrastructure with an Azure OpenAI model needs AWS credentials in services/iac/.env and Azure OpenAI credentials in core/.env.

LLM-provider credentials

Variable names follow LiteLLM’s provider conventions and are selected by the llm.model and llm.small_model strings. The complete per-provider table, the os.environ/VARIABLE_NAME syntax for custom names, and the limits of boot-time validation are documented in LLM providers and models.

Credential handling and production recommendations

  • Generate bearer tokens with a cryptographic generator. For example:

    openssl rand -hex 32

    Paste the output into both core/.env and the matching sidecar .env. Generate a distinct value per sidecar. Do not reuse the dev-*-token placeholders or an empty sidecar token outside an isolated workstation: an empty sidecar token disables its bearer check entirely.

  • Never put secrets in config.yaml. It is baked into the core image and is meant to be shareable. Use the *_env fields to rename variables if your platform imposes naming conventions.

  • Keep sidecars private. The bundled sidecars compare bearer tokens in constant time, but they are still simple services: keep them on a private network that only the core can reach, and rotate tokens on a schedule.

  • All five Kumoss-built images run unprivileged. The core, IaC, mapping, notifications, and authorization Dockerfiles each create and switch to the kumoss user (uid/gid 10001 by default, from the KUMOSS_UID / KUMOSS_GID build args). The proxy service is the upstream nginx image, which starts as root and drops privileges for its worker processes. Apply your platform’s pod or container security defaults on top regardless — the Compose file adds no cap_drop, read_only, or resource limits.

  • Prefer a secret manager or orchestrator secrets (Docker secrets, Kubernetes Secrets, a vault) over plaintext .env files in shared and production deployments. Mount files with permissions readable only by uid 10001.

  • Scope Git tokens narrowly. GIT_TOKEN is single-tenant: one token pushes every session’s branch and opens every pull request. Give it the minimum repository permissions your provider offers.

  • Rotate the RustFS keys and database password before exposing the stack beyond a workstation. docker-compose.yml sets no RustFS keys, so RustFS uses its built-in rustfsadmin / rustfsadmin. Set RUSTFS_ACCESS_KEY / RUSTFS_SECRET_KEY on object-storage and the same values in core/.env. The compose file also hard-codes the postgres database password. The same file hard-codes PHOENIX_SQL_DATABASE_URL with the phoenix-db password in it — change it outside a workstation. The RustFS web console is disabled (RUSTFS_CONSOLE_ENABLE=false); enabling it is up to you, see RustFS console.

  • Restrict who can reach the stack while OIDC is disabled. With a blank oidc.issuer_url, every request is treated as a fully privileged local developer. See Enable authentication.

Example .env sets

All values below are fictitious. Replace every token and URL.

Core plus IaC sidecar only (Anthropic models)

Only the IaC sidecar is enabled, and the model strings are the shipped ones — anthropic/* is both what the checked-in config.yaml selects and what the core falls back to when no config.yaml is loaded at all. The Vertex AI set is shown in the next example.

config.yaml excerpt for this example:

llm:
  model: "anthropic/claude-sonnet-5"
  small_model: "anthropic/claude-haiku-4-5"

core/.env:

# LLM provider
ANTHROPIC_API_KEY=sk-example-not-a-real-key

# Sidecar bearer tokens (only iac is enabled in the shipped config.yaml)
KUMOSS_IAC_TOKEN=0000000000000000000000000000000000000000000000000000000000000001
KUMOSS_MAPPING_TOKEN=unused-while-disabled
KUMOSS_NOTIFICATIONS_TOKEN=unused-while-disabled
KUMOSS_AUTHZ_TOKEN=unused-while-disabled

# Git push credentials
GIT_USER=kumoss-bot
GIT_TOKEN=ghp_example_not_a_real_token

# Bundled RustFS and PostgreSQL defaults
RUSTFS_ACCESS_KEY=rustfsadmin
RUSTFS_SECRET_KEY=rustfsadmin
KUMOSS_SQL_DATABASE_URL=postgresql://postgres:postgres@core-db:5432/kumoss

services/iac/.env (AWS example):

KUMOSS_IAC_TOKEN=0000000000000000000000000000000000000000000000000000000000000001
IAC_BINARY=tofu

# Unset: the backend comes from the workspace — the target repository's
# own block, or the override the core writes when Kumoss-managed state
# is on (the shipped default). If set, this file's values win over that
# override, key by key (see Terraform/OpenTofu state backends), so set
# it only with storage.terraform_state_bucket explicitly blanked to "",
# and mount the file in this container.
# IAC_BACKEND_CONFIG=/etc/kumoss/backend.hcl

# These credentials serve both the providers and, when the rendered
# backend block omits static keys, the S3 state backend.
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=example-not-a-real-secret-access-key
AWS_REGION=eu-west-1

All sidecars enabled (Vertex AI models, Azure infrastructure)

config.yaml excerpt:

llm:
  model: "vertex_ai/claude-sonnet-4-5"
  small_model: "vertex_ai/gemini-3.7-flash"
services:
  notifications:
    enabled: true
  mapping:
    enabled: true
  authz:
    enabled: true
  iac:
    # No enabled flag: the IaC sidecar is mandatory and always called.
    endpoint: "http://iac:8082"

core/.env:

# LLM provider (Google Vertex AI). The service-account key JSON, on one line.
VERTEXAI_PROJECT=demo-platform-project
VERTEXAI_LOCATION=europe-west1
VERTEXAI_CREDENTIALS={"type":"service_account","project_id":"demo-platform-project","private_key":"<service-account-private-key-pem>","client_email":"kumoss@demo-platform-project.iam.gserviceaccount.example.invalid"}

# One distinct token per sidecar
KUMOSS_IAC_TOKEN=0000000000000000000000000000000000000000000000000000000000000001
KUMOSS_MAPPING_TOKEN=0000000000000000000000000000000000000000000000000000000000000002
KUMOSS_NOTIFICATIONS_TOKEN=0000000000000000000000000000000000000000000000000000000000000003
KUMOSS_AUTHZ_TOKEN=0000000000000000000000000000000000000000000000000000000000000004

GIT_USER=kumoss-bot
GIT_TOKEN=example-not-a-real-gitlab-token

RUSTFS_ACCESS_KEY=rustfsadmin
RUSTFS_SECRET_KEY=rustfsadmin
KUMOSS_SQL_DATABASE_URL=postgresql://postgres:postgres@core-db:5432/kumoss

Paste the downloaded key file’s content as the value, collapsed onto one line and unquoted. Keep the \n escapes inside private_key as they are: turning them into real line breaks makes the JSON invalid.

VERTEXAI_CREDENTIALS also accepts a path to the key file instead of its content. Kumoss does not provide that file: create a secret or file mount into the core container yourself (the Compose file mounts nothing there), readable by uid 10001. A path that does not exist inside the container is not caught at boot. The first LLM call then fails with a JSON-parsing error rather than a missing-file error.

services/iac/.env (Azure example):

KUMOSS_IAC_TOKEN=0000000000000000000000000000000000000000000000000000000000000001
IAC_BINARY=tofu

# Provider grant: the resources plan and apply create.
ARM_CLIENT_ID=00000000-0000-0000-0000-000000000000
ARM_CLIENT_SECRET=example-not-a-real-client-secret
ARM_TENANT_ID=00000000-0000-0000-0000-000000000001
ARM_SUBSCRIPTION_ID=00000000-0000-0000-0000-000000000002

# State-backend grant: the storage account the target repository's own
# azurerm backend block names. Backend-only, and enough on its own.
# Drop it and add ARM_USE_AZUREAD=true to reuse the principal above.
ARM_ACCESS_KEY=<storage-account-access-key>

services/mapping/.env:

KUMOSS_MAPPING_TOKEN=0000000000000000000000000000000000000000000000000000000000000002

services/notifications/.env:

KUMOSS_NOTIFICATIONS_TOKEN=0000000000000000000000000000000000000000000000000000000000000003
SLACK_WEBHOOK_URL=https://hooks.slack.example.invalid/services/T000/B000/example
LOG_LEVEL=INFO

services/authz/.env:

KUMOSS_AUTHZ_TOKEN=0000000000000000000000000000000000000000000000000000000000000004
KUMOSS_AUTHZ_ROOT_ADMIN_EMAIL=platform-admin@example.invalid
KUMOSS_AUTHZ_PERMISSIVE=true
KUMOSS_AUTHZ_ROLE_STORE=/data/roles.json

Other LLM providers (drop-in replacements for the LLM block)

AZURE_API_KEY=sk-example-not-a-real-key
AZURE_API_BASE=https://demo-platform.openai.azure.example.invalid
AZURE_API_VERSION=2025-01-01-preview

Model strings openai/<model>.

OPENAI_API_KEY=none
OPENAI_API_BASE=https://llm.example.invalid/v1

Other cloud providers (drop-in replacements for the IaC block)

# Provider grant. Also serves the gcs backend, unless you override it below.
GOOGLE_CREDENTIALS={"type":"service_account","project_id":"demo-platform-project","private_key":"<service-account-private-key-pem>","client_email":"kumoss@demo-platform-project.iam.gserviceaccount.example.invalid"}

# State-backend grant: backend-only, and needed only when the state bucket
# lives outside the project above. Omit it and `init` reuses GOOGLE_CREDENTIALS.
# GOOGLE_BACKEND_CREDENTIALS={"type":"service_account", ...}

Kubeconfig mounted into the container.

KUBE_CONFIG_PATH=/home/kumoss/.kube/config