Configuration
Baleia (the API and the worker) is configured with environment variables. The defaults work for local development with the provided docker-compose.yaml. In Kubernetes, the Helm chart sets them from config and its Secret (deploy/helm/baleia/values.yaml).
| Variable | Default | Description |
|---|---|---|
PORT | 8080 | HTTP listen port |
DATABASE_URL | postgresql://baleia:baleia@localhost:5432/baleia?sslmode=disable | PostgreSQL connection string |
JWT_SECRET | change-me-in-production | Key for signing JWTs |
BALEIA_ENCRYPTION_KEY | (unset) | Key that encrypts stored connection credentials (≥ 32 characters, must differ from JWT_SECRET). Required once any credential is stored; the API refuses to start without it. Derived with HKDF-SHA256; each value is bound to its connection row |
BALEIA_ENCRYPTION_KEY_PREVIOUS | (unset) | Previous encryption key, set only during rotation: values are read with the current or previous key and always written with the current one |
BALEIA_ACCESS_TOKEN_TTL | 15m | Lifetime of an access token |
BALEIA_REFRESH_TOKEN_TTL | 720h (30 days) | Lifetime of a refresh token; a password change or reset, a role change, a suspension or a removal revokes them earlier |
BALEIA_RESET_TOKEN_TTL | 30m | Lifetime of a password-reset link |
BALEIA_BCRYPT_COST | 12 | Password hashing cost, between 10 and 31; higher is slower to sign in and to brute-force |
BALEIA_ALLOW_AMBIENT_AWS_CREDENTIALS | false | Lets an AWS Glue connection with no keys or role of its own use the host's default AWS credential chain. Off by default: otherwise any connection owner could use the platform's IAM role |
BALEIA_AUDIT_EXPORT_URL, BALEIA_AUDIT_EXPORT_INDEX, BALEIA_AUDIT_EXPORT_AUTH, BALEIA_AUDIT_EXPORT_USER, BALEIA_AUDIT_EXPORT_PASSWORD, BALEIA_AUDIT_EXPORT_TOKEN, BALEIA_AUDIT_EXPORT_BATCH, BALEIA_AUDIT_EXPORT_INTERVAL, BALEIA_AUDIT_EXPORT_CA_FILE | export off | Shipping the audit trail to OpenSearch/Elasticsearch: defaults, authentication modes and behavior in Audit export |
TEMPORAL_HOST | localhost:7233 | Temporal server address |
TRINO_URL | http://localhost:8088 | Trino coordinator REST endpoint. At API boot it also seeds the default Trino engine (with the service and admin identities below) and keeps it in step while that engine's connection is system-owned; engine endpoints live in the database (engines, ADR 0002). Trino is dialed through the connections dialer like every engine, so a loopback TRINO_URL needs BALEIA_CONNECTIONS_ALLOW_LOOPBACK=true (set by make dev) |
POLARIS_URL | http://localhost:8181 | Polaris REST API endpoint |
POLARIS_CLIENT_ID / POLARIS_CLIENT_SECRET | (unset) | Credentials stored for a polaris connection created without any; both must be set |
PUBLIC_URL | http://localhost:8080 | Externally reachable URL (used for presigned links) |
S3_ENDPOINT | localhost:9000 | Endpoint of any S3-compatible store (AWS S3, RustFS, MinIO, Ceph RGW, Google Cloud Storage, Oracle Object Storage), as host[:port] |
S3_ACCESS_KEY | rustfsadmin | S3 access key |
S3_SECRET_KEY | rustfsadmin | S3 secret key |
S3_BUCKET | baleia | S3 bucket for query results |
S3_REGION | (empty) | SigV4 signing region. Empty discovers it (AWS, RustFS, MinIO); set it for stores that require it, e.g. sa-saopaulo-1 on Oracle Object Storage |
S3_USE_SSL | false | Enable TLS for S3 connections |
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASSWORD / SMTP_FROM | — | Outbound email (password reset, invites) |
BALEIA_DEFAULT_LOCALE | en | Fallback language for transactional e-mails when the recipient has no users.locale (en or pt-BR) |
RATE_LIMIT_RPS | 60 | Max requests per second (rate limiter) |
RATE_LIMIT_BURST | 10 | Rate limiter burst size |
BALEIA_STATEMENT_TIMEOUT | 30m | Per-statement execution timeout; enforced by the worker and by the engine (Trino: query_max_execution_time) |
BALEIA_RECONCILE_INTERVAL | unset | Apply binding reconciliation on a schedule (at least 5m); unset: no schedule, only POST /engines/{id}/reconcile |
BALEIA_RECONCILE_MAX_ACTIONS | 20 | Changes one reconcile run may apply; the rest are reported |
BALEIA_METADATA_SYNC_INTERVAL | 15m | How often every catalog's metadata snapshot is refreshed (at least 5m) |
BALEIA_MAINTENANCE_MAX_CONCURRENT_RUNS | 4 | Automatic Iceberg maintenance runs in progress at once, across tables (#500) |
BALEIA_ICEBERG_MAINTENANCE_MIN_RETENTION | 1h | Minimum retention projected into managed Iceberg catalogs for expire_snapshots and remove_orphan_files; table parameters below the floor a catalog has loaded are refused (at least 1m). Changing it, including the first deploy that introduces it, changes the catalog version: every managed Iceberg catalog is re-created on its engine once, and until that reconcile the old floor (Trino's 7d) still applies |
BALEIA_EXECUTION_TIMEOUT | 2h | Cap for a whole execution: all statements run in one engine session, so a script may take statements × statement timeout, capped by this |
BALEIA_RESULT_MAX_ROWS | 1000000 | Max rows buffered per statement result before truncation |
BALEIA_LOB_MAX_BYTES | 1048576 | Max bytes an Oracle engine reads from one CLOB/NCLOB/BLOB value into a result (1 MiB) |
BALEIA_RESULT_MAX_BYTES | 536870912 | Max Arrow bytes per statement result before truncation |
BALEIA_SCHEDULES_ENABLED | true | Ensure recurring Temporal Schedules (Lens pollers, table health, retriever) at worker boot |
BALEIA_CONNECTIONS_DENY_CIDRS | (unset) | Comma-separated CIDRs that connections may never reach, added to the built-in deny list (loopback, link-local incl. cloud metadata 169.254.169.254, 0.0.0.0/8) — see ADR 0011 |
BALEIA_CONNECTIONS_ALLOW_CIDRS | (unset) | If set, connections may only reach these comma-separated CIDRs (the deny list still wins) |
BALEIA_CONNECTIONS_CREATE | everyone | Who may create and edit connections: everyone, or admins. The CIDR lists bind admins too, so on a deployment with self-service or shared accounts use admins to keep other users from aiming connections at internal services |
BALEIA_LOG_LEVEL | info | Lowest level the API, the worker and cmd/migrate log: debug, info, warn or error. They log JSON lines to stdout; a line logged during a traced request or activity carries its trace_id and span_id (#379) |
BALEIA_TRINO_EVENTS_TOKEN | (unset) | Bearer token Trino's HTTP event listener sends to POST /internal/trino/query-events (at least 32 characters). Set, the API records each completed query as it ends; unset, the endpoint does not exist. The chart wires both sides with trino.queryEvents.enabled (#406) |
BALEIA_PPROF_ADDR | (unset) | Where the API and the worker serve the Go runtime profiles (/debug/pprof/), e.g. 127.0.0.1:6060; unset serves none. The listener has no authentication: bind it to loopback or a port only reachable from the pod (kubectl port-forward), never to a public interface (#380) |
BALEIA_METRICS_ENABLED | true | Serve Prometheus metrics at the API's /metrics (the catalog is internal/metrics, #87) |
BALEIA_METRICS_TOKEN | (unset) | When set, /metrics (API and worker) needs Authorization: Bearer <token>; it is never behind the user JWT. A credential: in the chart it is the optional Secret key BALEIA_METRICS_TOKEN (secrets.values or existingSecret), never config |
WORKER_HEALTH_PORT | 8081 | The worker's liveness listener (/healthz) |
BALEIA_WORKER_METRICS_ADDR | :9090 | The worker's listener for /metrics (Temporal SDK, pool, build) and /healthz; empty disables it |
BALEIA_DB_MAX_CONNS | api 20, worker 10 | Size of Baleia's own database pool (observable as baleia_db_pool_*) |
BALEIA_DB_MIN_CONNS | 2 | Connections the pool keeps open |
BALEIA_DB_MAX_CONN_LIFETIME | 1h | Recycle pool connections after this long |
BALEIA_DB_MAX_CONN_IDLE_TIME | 30m | Close pool connections idle for this long |
BALEIA_DB_HEALTH_CHECK_PERIOD | 1m | How often the pool checks idle connections |
BALEIA_DB_STATEMENT_TIMEOUT | 1m | statement_timeout on Baleia's own sessions (not user queries, which use BALEIA_STATEMENT_TIMEOUT); 0 disables it |
BALEIA_DB_IDLE_IN_TX_TIMEOUT | 1m | idle_in_transaction_session_timeout: a leaked transaction is ended and its locks released; 0 disables it |
BALEIA_DB_LOCK_TIMEOUT | 30s | lock_timeout on Baleia's own sessions; 0 disables it |
BALEIA_HTTP_READ_HEADER_TIMEOUT | 10s | Time a client has to send the request headers to the API |
BALEIA_HTTP_IDLE_TIMEOUT | 2m | How long an idle keep-alive connection to the API stays open |
BALEIA_HTTP_SHUTDOWN_TIMEOUT | 20s | How long the API drains in-flight requests on shutdown (keep it below the pod's termination grace period); 0 waits without a deadline |
BALEIA_SECRET_FILE_BASE_DIR | (unset) | Directory @baleia-secret[file:name] references resolve under (secrets.md); unset refuses file: references. Read once at startup |
BALEIA_SQLITE_ROOT | (unset) | Directory SQLite connections are confined to (symlinks resolved); unset disables the SQLite provider. The API and the worker must see the same files (shared volume) |
BALEIA_SQLITE_ALLOW_WRITE | false | Let a SQLite connection that sets read_write open its file read-write; otherwise files open read-only |
BALEIA_CONNECTIONS_ALLOW_LOOPBACK | false | Let connections reach loopback addresses. For local development only (make dev / make worker set it); never in a shared deployment |
BALEIA_TRINO_ADMIN_USER | baleia_admin | Trino identity for catalog DDL/reconcile/maintenance (never user SQL) |
BALEIA_TRINO_SERVICE_USER | baleia | Worker fallback identity for executions |
BALEIA_TRINO_USER_IDENTITY | uuid | Map Baleia users to Trino users: uuid→u_<uuid> or email |
BALEIA_AGENTS_ENABLED | false | Mount the agent management API and /mcp (mcp.md). Off by default |
BALEIA_AGENT_TOKEN_DEFAULT_TTL | 720h | Default lifetime of an agent token when expires_in_days is omitted |
BALEIA_AGENT_TOKEN_MAX_TTL | 2160h | Maximum accepted agent-token lifetime (90 days) |
BALEIA_AGENT_MAX_ROWS | 5000 | Ceiling for an agent policy's max_rows_returned |
BALEIA_AGENT_MAX_RESULT_BYTES | 1048576 | Ceiling for max_result_bytes (1 MiB) |
BALEIA_AGENT_MAX_QUERY_RUNTIME_SECONDS | 900 | Ceiling for max_query_runtime_seconds |
BALEIA_AGENT_MAX_CONCURRENT_QUERIES | 10 | Ceiling for max_concurrent_queries |
BALEIA_AGENT_MAX_QUERIES_PER_HOUR | 2000 | Ceiling for max_queries_per_hour |
BALEIA_AGENT_MAX_TOOL_CALLS_PER_MINUTE | 600 | Ceiling for max_tool_calls_per_minute |
BALEIA_MCP_ALLOWED_ORIGINS | (unset) | Comma-separated Origin allow-list for /mcp; empty rejects any request that carries one |
Credential encryption
Connection credentials are stored as v1:<key-id>:<base64> envelopes: AES-256-GCM under a key derived (HKDF-SHA256) from BALEIA_ENCRYPTION_KEY, with the connection id as additional authenticated data, so a ciphertext copied to another connection does not open. A value that cannot be opened is reported as credentials_unreadable; it is never used as if it were the secret.
- Fresh install: set
BALEIA_ENCRYPTION_KEYbefore saving the first credential. Without it the API starts with a warning and refuses to store credentials. - Upgrading from
JWT_SECRET-encrypted credentials: setBALEIA_ENCRYPTION_KEY, keep the existingJWT_SECRET, then run the one-shot, idempotent migration (make migrate-credentials, or/migrate -cmd=credentialsin the image). It requires a validJWT_SECRETto open the legacy values. Rows that cannot be decrypted are listed by id and left untouched; re-enter those credentials. - Rotating the key: move the old value to
BALEIA_ENCRYPTION_KEY_PREVIOUSand set the newBALEIA_ENCRYPTION_KEY. Values are read with either key, but reading never re-seals them. Runmake migrate-credentials(or/migrate -cmd=credentials) to move every envelope to the new key; only after it reports no failures is it safe to dropBALEIA_ENCRYPTION_KEY_PREVIOUS. Envelopes left on the old key becomecredentials_unreadableonce it is removed.
See also secrets.md for external secret references and the catalog registry at rest.