> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tesslate.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Environment variable reference for OpenSail, grouped by subsystem, with defaults and examples

<img src="https://mintcdn.com/tesslate/VT6tbZolrCfpx26M/images/opensail-banner.png?fit=max&auto=format&n=VT6tbZolrCfpx26M&q=85&s=66579c47537b3464fb65b229cc9ab0fd" alt="Tesslate OpenSail" width="4712" height="1612" data-path="images/opensail-banner.png" />

## Overview

All OpenSail configuration is environment-driven. The canonical reader is `orchestrator/app/config.py` (Pydantic settings). Feature flags live in `orchestrator/app/config_features.py`. Frontend runtime config reads `window._env_.*`; build-time config reads `VITE_*`.

Use this page together with:

* Repo-root `.env.example` and `.env.prod.example`
* `docs/guides/environment-variables.md` (the exhaustive reference)
* Kustomize overlays under `k8s/overlays/*/backend-patch.yaml`

<Info>
  After changing a variable, restart the affected service. In Docker: `docker compose restart orchestrator`. In Kubernetes: `kubectl rollout restart deploy/orchestrator -n tesslate`.
</Info>

## Required core

<ParamField path="SECRET_KEY" type="string" required>
  JWT signing and general crypto fallback. Never reuse across environments. Generate with `python3 -c "import secrets; print(secrets.token_urlsafe(32))"`.
</ParamField>

<ParamField path="DEPLOYMENT_MODE" type="string" default="docker">
  `docker`, `kubernetes`, or `desktop`. Selects orchestrator backend, DB driver, task queue, and pub/sub.
</ParamField>

<ParamField path="DATABASE_URL" type="string">
  SQLAlchemy async URL. Docker default points at the `postgres` service. Desktop mode resolves to SQLite automatically.
</ParamField>

<ParamField path="INTERNAL_API_SECRET" type="string">
  Shared secret for cluster-internal callers (Volume Hub GC, btrfs CSI) hitting `/api/internal/*`. Desktop mode ignores.
</ParamField>

## Database

<Tabs>
  <Tab title="Postgres (cloud)">
    | Variable            | Purpose                          |
    | ------------------- | -------------------------------- |
    | `POSTGRES_DB`       | Database name                    |
    | `POSTGRES_USER`     | DB user                          |
    | `POSTGRES_PASSWORD` | DB password                      |
    | `POSTGRES_PORT`     | Host port (compose default 5432) |

    ```env theme={null}
    DATABASE_URL=postgresql+asyncpg://tesslate:tesslate@postgres:5432/tesslate
    POSTGRES_PASSWORD=dev_password_change_me
    ```
  </Tab>

  <Tab title="SQLite (desktop)">
    Desktop resolves a SQLite file under `OPENSAIL_HOME`. No Postgres variables required.
  </Tab>
</Tabs>

## Redis, pub/sub, task queue

| Variable             | Purpose                                                                                    |
| -------------------- | ------------------------------------------------------------------------------------------ |
| `REDIS_URL`          | ARQ + RedisPubSub connection. Empty string falls back to in-memory for single-process dev. |
| `WORKER_MAX_JOBS`    | Concurrent agent tasks per ARQ worker pod (default 10)                                     |
| `WORKER_JOB_TIMEOUT` | Task timeout seconds (default 600)                                                         |

Desktop mode uses `LocalTaskQueue` (asyncio + apscheduler) and `LocalPubSub` (in-process). No Redis required.

## LiteLLM and agent

| Variable                   | Purpose                                               |
| -------------------------- | ----------------------------------------------------- |
| `LITELLM_API_BASE`         | Proxy URL, e.g. `https://your-litellm.example.com/v1` |
| `LITELLM_MASTER_KEY`       | Master key for minting per-user virtual keys          |
| `LITELLM_DEFAULT_MODELS`   | Comma-separated models granted to new users           |
| `LITELLM_TEAM_ID`          | LiteLLM team or access group                          |
| `LITELLM_EMAIL_DOMAIN`     | Internal email domain stamped into LiteLLM users      |
| `LITELLM_INITIAL_BUDGET`   | Starting budget USD per user                          |
| `COMPACTION_SUMMARY_MODEL` | Cheap model used for context compaction               |
| `DEFAULT_THINKING_EFFORT`  | Extended thinking effort for supported models         |
| `AGENT_MAX_COST`           | Global cost cap USD (default 20.0)                    |
| `AGENT_MAX_ITERATIONS`     | Global iteration cap, 0 disables                      |
| `AGENT_MAX_COST_PER_RUN`   | Per-run cost cap USD (default 5.0)                    |

## Auth and cookies

<ParamField path="ALGORITHM" type="string" default="HS256">JWT signing algorithm.</ParamField>
<ParamField path="ACCESS_TOKEN_EXPIRE_MINUTES" type="number" default="30">Access token TTL.</ParamField>
<ParamField path="REFRESH_TOKEN_EXPIRE_DAYS" type="number" default="14">Refresh token TTL.</ParamField>
<ParamField path="CSRF_SECRET_KEY" type="string">Separate key for CSRF tokens. Falls back to `SECRET_KEY`.</ParamField>
<ParamField path="CSRF_TOKEN_MAX_AGE" type="number" default="86400">CSRF token TTL seconds.</ParamField>
<ParamField path="COOKIE_SECURE" type="boolean" default="false">Set true in production.</ParamField>
<ParamField path="COOKIE_SAMESITE" type="string" default="lax">`lax`, `strict`, or `none`.</ParamField>
<ParamField path="COOKIE_DOMAIN" type="string">Scope cookies to apex, e.g. `.tesslate.com`, for subdomain access.</ParamField>
<ParamField path="CORS_ORIGINS" type="string">Comma-separated allowed origins.</ParamField>
<ParamField path="ALLOWED_HOSTS" type="string">Host header allowlist.</ParamField>

## OAuth providers

All optional. Absence of credentials disables the provider gracefully.

<Tabs>
  <Tab title="Login">
    | Provider | Variables                                                               |
    | -------- | ----------------------------------------------------------------------- |
    | GitHub   | `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GITHUB_OAUTH_REDIRECT_URI` |
    | Google   | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_OAUTH_REDIRECT_URI` |
  </Tab>

  <Tab title="Deployment targets">
    | Provider     | Variables                                                                                 |
    | ------------ | ----------------------------------------------------------------------------------------- |
    | Vercel       | `VERCEL_CLIENT_ID`, `VERCEL_CLIENT_SECRET`, `VERCEL_OAUTH_REDIRECT_URI`                   |
    | Netlify      | `NETLIFY_CLIENT_ID`, `NETLIFY_CLIENT_SECRET`, `NETLIFY_OAUTH_REDIRECT_URI`                |
    | Heroku       | `HEROKU_CLIENT_ID`, `HEROKU_CLIENT_SECRET`, `HEROKU_OAUTH_REDIRECT_URI`                   |
    | DigitalOcean | `DIGITALOCEAN_CLIENT_ID`, `DIGITALOCEAN_CLIENT_SECRET`, `DIGITALOCEAN_OAUTH_REDIRECT_URI` |
  </Tab>

  <Tab title="MCP platform apps">
    Used when an MCP server does not advertise RFC 7591 Dynamic Client Registration.

    | Provider           | Variables                                                              |
    | ------------------ | ---------------------------------------------------------------------- |
    | GitHub Copilot MCP | `MCP_OAUTH_APP_GITHUB_CLIENT_ID`, `MCP_OAUTH_APP_GITHUB_CLIENT_SECRET` |
    | Slack MCP          | `MCP_OAUTH_APP_SLACK_CLIENT_ID`, `MCP_OAUTH_APP_SLACK_CLIENT_SECRET`   |
  </Tab>
</Tabs>

## SMTP and 2FA

| Variable                                                                                        | Purpose                                                         |
| ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| `SMTP_HOST`, `SMTP_PORT`, `SMTP_USERNAME`, `SMTP_PASSWORD`, `SMTP_USE_TLS`, `SMTP_SENDER_EMAIL` | Email transport for magic links, 2FA codes, and password resets |
| `TWO_FA_ENABLED`                                                                                | Require 6-digit email 2FA after password login (default false)  |

## S3 and object storage

| Variable                                   | Purpose                                                     |
| ------------------------------------------ | ----------------------------------------------------------- |
| `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` | Credentials. Omit on AWS with IRSA.                         |
| `S3_ENDPOINT_URL`                          | Custom endpoint for DO Spaces, MinIO, etc. Omit for AWS S3. |
| `S3_BUCKET_NAME`                           | Projects bucket (default `tesslate-projects`)               |
| `S3_REGION`                                | Region (default `us-east-1`)                                |
| `S3_PROJECTS_PREFIX`                       | Key prefix (default `projects`)                             |

## Kubernetes (user-project runtime)

| Variable                                                           | Purpose                                                                                     |
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| `K8S_DEVSERVER_IMAGE`                                              | Image for user containers. Minikube uses `tesslate-devserver:latest`; prod uses an ECR URL. |
| `K8S_IMAGE_PULL_SECRET`                                            | Registry pull secret (`ecr-credentials` in prod)                                            |
| `K8S_STORAGE_CLASS`                                                | StorageClass for PVCs                                                                       |
| `K8S_SNAPSHOT_CLASS`                                               | VolumeSnapshotClass                                                                         |
| `K8S_SNAPSHOT_RETENTION_DAYS`                                      | Days to keep soft-deleted snapshots (default 30)                                            |
| `K8S_MAX_SNAPSHOTS_PER_PROJECT`                                    | Max snapshots in timeline (default 5)                                                       |
| `K8S_SNAPSHOT_READY_TIMEOUT_SECONDS`                               | Snapshot readiness timeout (default 300)                                                    |
| `K8S_HIBERNATION_IDLE_MINUTES`                                     | Auto-hibernate after N idle minutes (default 10)                                            |
| `K8S_HYDRATION_TIMEOUT_SECONDS`, `K8S_DEHYDRATION_TIMEOUT_SECONDS` | Hibernation transitions                                                                     |
| `K8S_PVC_SIZE`                                                     | Default PVC size per project (default 5Gi)                                                  |
| `K8S_ENABLE_POD_AFFINITY`                                          | Keep multi-container projects on the same node                                              |
| `K8S_RWX_STORAGE_CLASS`                                            | ReadWriteMany class for shared source code                                                  |
| `K8S_INGRESS_CLASS`                                                | Ingress class name (default `nginx`)                                                        |
| `K8S_NAMESPACE_PER_PROJECT`                                        | Namespace-per-project isolation (default true)                                              |
| `K8S_ENABLE_NETWORK_POLICIES`                                      | Create NetworkPolicies for project isolation (default true)                                 |
| `K8S_WILDCARD_TLS_SECRET`                                          | TLS secret for wildcard cert (empty for HTTP)                                               |
| `K8S_INGRESS_DOMAIN`                                               | Apex domain for user-project ingress                                                        |
| `K8S_USE_S3_STORAGE`                                               | Use S3 hibernation instead of PVCs (default false)                                          |

## Volume Hub and btrfs CSI

| Variable                          | Purpose                                                                |
| --------------------------------- | ---------------------------------------------------------------------- |
| `VOLUME_HUB_ADDRESS`              | Hub gRPC endpoint (default `tesslate-volume-hub.kube-system.svc:9750`) |
| `TEMPLATE_BUILD_STORAGE_CLASS`    | StorageClass for template builds (`tesslate-btrfs`)                    |
| `TEMPLATE_BUILD_NODEOPS_ADDRESS`  | NodeOps gRPC endpoint for template builds                              |
| `FILEOPS_ENABLED`                 | Use FileOps gRPC service for v2 file operations (default true)         |
| `FILEOPS_TIMEOUT`                 | FileOps RPC timeout seconds (default 30)                               |
| `COMPUTE_MAX_CONCURRENT_PODS`     | Max concurrent compute pods (default 5)                                |
| `COMPUTE_POD_TIMEOUT`             | Pod readiness timeout (default 600)                                    |
| `COMPUTE_REAPER_INTERVAL_SECONDS` | Orphan pod reaper interval (default 60)                                |
| `COMPUTE_REAPER_MAX_AGE_SECONDS`  | Max pod age before reaping (default 900)                               |

## Web search

| Variable               | Purpose                                             |
| ---------------------- | --------------------------------------------------- |
| `WEB_SEARCH_PROVIDER`  | `tavily`, `brave`, or `duckduckgo` (default tavily) |
| `TAVILY_API_KEY`       | Tavily API key                                      |
| `BRAVE_SEARCH_API_KEY` | Brave API key                                       |

## Messaging channels and gateway

| Variable                       | Purpose                                                |
| ------------------------------ | ------------------------------------------------------ |
| `AGENT_DISCORD_WEBHOOK_URL`    | Webhook for the agent `send_message` tool              |
| `CHANNEL_ENCRYPTION_KEY`       | Fernet key for encrypting per-user channel credentials |
| `GATEWAY_ENABLED`              | Enable gateway runner (default false)                  |
| `GATEWAY_SHARD`                | Shard identifier for multi-instance gateway            |
| `GATEWAY_TICK_INTERVAL`        | Scheduler tick interval seconds                        |
| `GATEWAY_SESSION_IDLE_MINUTES` | Idle timeout for gateway sessions                      |
| `GATEWAY_VOICE_TRANSCRIPTION`  | Enable voice message transcription                     |

## MCP (Model Context Protocol)

| Variable                   | Purpose                                            |
| -------------------------- | -------------------------------------------------- |
| `MCP_TOOL_CACHE_TTL`       | Tool schema cache TTL seconds (default 300)        |
| `MCP_TOOL_TIMEOUT`         | Tool call timeout seconds (default 30)             |
| `MCP_MAX_SERVERS_PER_USER` | Cap on installed MCP servers per user (default 20) |

## Stripe and billing

| Variable                                                                                       | Purpose                                         |
| ---------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| `STRIPE_SECRET_KEY`, `STRIPE_PUBLISHABLE_KEY`, `STRIPE_WEBHOOK_SECRET`                         | Stripe core keys                                |
| `STRIPE_CONNECT_CLIENT_ID`                                                                     | Stripe Connect (creator payouts)                |
| `STRIPE_BASIC_PRICE_ID`, `STRIPE_PRO_PRICE_ID`, `STRIPE_ULTRA_PRICE_ID`                        | Monthly subscription prices                     |
| `STRIPE_BASIC_ANNUAL_PRICE_ID`, `STRIPE_PRO_ANNUAL_PRICE_ID`, `STRIPE_ULTRA_ANNUAL_PRICE_ID`   | Annual prices                                   |
| `CREDIT_PACKAGE_SMALL`, `CREDIT_PACKAGE_MEDIUM`, `CREDIT_PACKAGE_LARGE`, `CREDIT_PACKAGE_TEAM` | One-time credit prices (cents)                  |
| `SIGNUP_BONUS_CREDITS`, `SIGNUP_BONUS_EXPIRY_DAYS`                                             | Signup bonus                                    |
| `CREATOR_REVENUE_SHARE`, `PLATFORM_REVENUE_SHARE`                                              | Revenue split decimals (sum to 1)               |
| `USAGE_INVOICE_DAY`                                                                            | Day of month to generate usage invoices (1..28) |

## Frontend

| Variable                                                | Mode    | Purpose                                                         |
| ------------------------------------------------------- | ------- | --------------------------------------------------------------- |
| `VITE_API_URL`                                          | build   | API base URL (empty for Vite proxy, or `http://localhost:8000`) |
| `VITE_ALLOWED_HOSTS`                                    | build   | Comma-separated host allowlist for Vite dev server              |
| `VITE_PUBLIC_POSTHOG_KEY`, `VITE_PUBLIC_POSTHOG_HOST`   | build   | PostHog project                                                 |
| `window._env_.API_URL`                                  | runtime | Base URL without `/api` (frontend code prepends it)             |
| `window._env_.POSTHOG_KEY`, `window._env_.POSTHOG_HOST` | runtime | PostHog runtime config                                          |

## Domain, ports, Traefik

| Variable                      | Purpose                                      |
| ----------------------------- | -------------------------------------------- |
| `APP_DOMAIN`                  | Apex domain (no protocol)                    |
| `APP_PROTOCOL`                | `http` or `https`                            |
| `APP_PORT`, `APP_SECURE_PORT` | Public HTTP and HTTPS ports                  |
| `BACKEND_PORT`                | Uvicorn port                                 |
| `FRONTEND_PORT`               | Vite dev port                                |
| `APP_BASE_URL`                | Full base URL; defaults to protocol + domain |
| `TRAEFIK_DASHBOARD_PORT`      | Dashboard port (dev)                         |
| `TRAEFIK_BASIC_AUTH`          | htpasswd admin credential                    |
| `TRAEFIK_CERT_RESOLVER`       | `letsencrypt` or `cloudflare`                |
| `CF_DNS_API_TOKEN`            | Cloudflare DNS token for wildcard certs      |

## Desktop

| Variable               | Purpose                                                                                    |
| ---------------------- | ------------------------------------------------------------------------------------------ |
| `OPENSAIL_HOME`        | Desktop data directory override. Resolved by `orchestrator/app/services/desktop_paths.py`. |
| `TEST_HELPERS_ENABLED` | Expose test-only routes (CI)                                                               |
| `BUILD_SHA`            | Deployment identifier reported by `/api/version`                                           |

## Observability

| Variable                  | Purpose                                                          |
| ------------------------- | ---------------------------------------------------------------- |
| `LOG_LEVEL`               | `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` (default `INFO`) |
| `VITE_PUBLIC_POSTHOG_KEY` | PostHog project key (empty to disable)                           |

## Feature flags (Tesslate Apps)

Apps features are gated by `TSL_FEATURE_*` booleans. Default is off except governance policies. The env variable is the dotted flag name upper-cased with dots replaced by underscores: `apps.publish` becomes `TSL_FEATURE_APPS_PUBLISH`.

| Flag                                                                                                                | Default                  |
| ------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| `apps.manifest_schema_v1`, `apps.publish`, `apps.install`                                                           | false                    |
| `apps.runtime.ui`, `apps.runtime.chat`, `apps.runtime.scheduled`, `apps.runtime.triggered`, `apps.runtime.mcp_tool` | false                    |
| `apps.hosted_agent`, `apps.source_view`, `apps.fork`, `apps.bundles`                                                | false                    |
| `apps.review.stage1`, `apps.review.stage2`, `apps.review.stage3`, `apps.yank`                                       | false                    |
| `apps.yank.critical_two_admin`                                                                                      | true (governance policy) |
| `apps.billing.dispatcher`, `apps.billing.revenue_split`                                                             | false                    |
| `apps.triggers.webhook`, `apps.triggers.mcp_event`, `apps.triggers.app_invocation`                                  | false                    |
| `apps.canvas.hosted_agent_node`, `apps.embedding.postmessage`                                                       | false                    |

## Testing

| Variable               | Purpose                       |
| ---------------------- | ----------------------------- |
| `TEST_HELPERS_ENABLED` | Expose test-only routes in CI |
| `PLAYWRIGHT_BASE_URL`  | Base URL for Playwright E2E   |

CI sets `SECRET_KEY=test-secret-key-*`, `DEPLOYMENT_MODE=docker`, `LITELLM_API_BASE=http://localhost:4000/v1`, `LITELLM_MASTER_KEY=test-key`, and points `DATABASE_URL` at Postgres on port 5433.

## Environment comparison

| Setting                   | Docker (Local) | Minikube                    | AWS EKS                               |
| ------------------------- | -------------- | --------------------------- | ------------------------------------- |
| `DEPLOYMENT_MODE`         | `docker`       | `kubernetes`                | `kubernetes`                          |
| `K8S_DEVSERVER_IMAGE`     | N/A            | `tesslate-devserver:latest` | `<ECR_URL>/tesslate-devserver:latest` |
| `K8S_IMAGE_PULL_SECRET`   | N/A            | (empty)                     | `ecr-credentials`                     |
| `K8S_STORAGE_CLASS`       | N/A            | `tesslate-btrfs`            | `tesslate-block-storage`              |
| `K8S_WILDCARD_TLS_SECRET` | N/A            | (empty, HTTP)               | `tesslate-wildcard-tls`               |
| `APP_DOMAIN`              | `localhost`    | `localhost`                 | e.g. `opensail.example.com`           |
| `COOKIE_SECURE`           | `false`        | `false`                     | `true`                                |

## Next steps

<CardGroup cols={2}>
  <Card title="Deployment" icon="server" href="/self-hosting/deployment">
    Production paths: Docker, Kubernetes, AWS EKS.
  </Card>

  <Card title="Architecture" icon="diagram-project" href="/self-hosting/architecture">
    How the orchestrator, agent, and storage fit together.
  </Card>

  <Card title="Authentication" icon="key" href="/authentication">
    JWT, OAuth, email 2FA, API keys, and desktop pairing.
  </Card>

  <Card title="API reference" icon="book" href="/developer/api-reference">
    REST surface overview and OpenAPI spec pointer.
  </Card>
</CardGroup>
