Configuration
Look up a setting here. The environment variable tables are built from the code on every site build, so each row links to where the variable is read.
League, team and season
Three settings identify your team. Set them in each place that reads them.
| Setting | Locally | On Kubernetes | In the UI |
|---|---|---|---|
| League ID | ESPN_LEAGUE_ID | fantasy/deployment in the secret store, as ESPN_LEAGUE_ID | Settings page |
| Team ID | ESPN_TEAM_ID | fantasy/deployment, as ESPN_TEAM_ID | Settings page |
| Season | CURRENT_YEAR | fantasy-league-config ConfigMap, plus the --season arguments in two CronJobs | Settings page |
Both IDs are in your team's ESPN URL: …/team?leagueId=<league>&teamId=<team>. The deployment treats them as private and keeps them out of Git.
Write safety
Which bots may write to ESPN is set by DRY_RUN, TRADE_DRY_RUN, TRADE_SUBMIT_ENABLED, TRADE_RESPONSE_ENABLED and AUTO_PUBLISH_IR_MOVES. Their defaults differ by bot. See Run with every write disabled before you change any of them.
Environment variables
| Column | Meaning |
|---|---|
| Default | What applies when the variable is unset. A dash means nothing does. |
| Required | Whether you must set it, or the condition that makes it required. Many defaults are in-cluster addresses, so outside the cluster you set them anyway. |
| Labels under the name | Secret: the value is a credential. Set in production: fantasy-infra sets it, possibly from a Secret. |
Shared by several services
| Variable | Required | Default | Description |
|---|---|---|---|
DB_HOST | yes, outside the cluster | core: fantasy-db.default.svc.cluster.local; worker: none (node-postgres falls back to PGHOST or localhost) | Postgres host. |
DB_NAME | yes, outside the cluster | core: fantasy_football; worker: none | Postgres database name. |
DB_USER | yes, outside the cluster | core: bot_admin; worker: none | Postgres user. |
DB_PASSWORD | yes | — | Postgres password. Core logs an error and every query fails without it. |
REDIS_HOST | yes, outside the cluster | fantasy-cache.default.svc.cluster.local | Redis host for daily locks, the draft feed and notification de-duplication. |
REDIS_PORT | no | 6379 | Redis port. |
AMQP_URL | yes, outside the cluster | amqp://guest:[email protected]:5672/%2f (the worker's default ends in // instead) | RabbitMQ connection URL, including the credentials. Encode the default vhost as %2f. More |
TRIGGER_API_KEY | yes, for every trigger and job write | — | Shared key for the API's write endpoints. The API returns 500 on those routes without it; core's jobs send it, and the UI's nginx (or the Vite dev proxy) adds it as X-API-Key. Never expose it as a VITE_ variable. More |
ESPN_LEAGUE_ID | yes | — | Your ESPN league ID. Production keeps it in OpenBao with the session cookies. More |
ESPN_TEAM_ID | yes | — | Your team's ID within the league. More |
CURRENT_YEAR | yes, for the first refresh (draft_prep_refresh exits without it) | core: the current calendar year; worker: 2026 | The ESPN season ID, such as 2026. More |
ESPN_S2 | yes | null (the worker falls back to OpenBao) | The espn_s2 session cookie from a logged-in browser. More |
OTEL_EXPORTER_OTLP_ENDPOINT | no | — | OTLP endpoint for traces; tracing is off when unset. Core takes a bare http://host:4318 and appends /v1/traces; the worker exports over gRPC to an http:// URL. |
RUN_ID | no | — | Run log row the job adopts, so a launched run and its log entry match. |
RUN_TRIGGER | no | — | What started the run, such as schedule, manual or downstream. |
RUN_PARENT_ID | no | — | Parent run, for chained jobs. |
RUN_CRONJOB | no | — | Name of the CronJob that started the run. |
RUN_K8S_JOB | no | falls back to HOSTNAME | Name of the Kubernetes Job that started the run. |
DRAFT_MODE | no | core: live; worker: none | live or practice. A practice draft lets the clicker submit even when DRY_RUN is unset; mock makes the worker join a practice lobby. |
LLM_PROVIDER_PRIORITY | no | groq:openai/gpt-oss-120b, then gemini, then ollama:llama3.1:8b (the Gemini model differs between core and worker) | Ordered provider:model list to try. More |
{PROVIDER}_API_KEY | yes, for each hosted provider you use | null (falls back to OpenBao) | API key for a hosted LLM provider. More |
OPENBAO_{PROVIDER}_SECRET_PATH | no | secret/fantasy/<provider> | OpenBao path read for the provider's key when the variable is unset. |
{PROVIDER}_RPM_LIMIT | no | groq 30, gemini 15 | Requests per minute allowed per provider. |
OLLAMA_URL | no | http://keda-add-ons-http-interceptor-proxy.keda.svc.cluster.local:8080 | Ollama URL. The default goes through the KEDA HTTP add-on, which scales Ollama from zero. |
OLLAMA_HOST_HEADER | no | ollama.default.svc.cluster.local (only when OLLAMA_URL is unset) | Host header the KEDA interceptor routes on. |
LLM_HISTORY_CAPTURE_CONTENT | no | false (metadata only) | Set to true to store prompt and response text with each LLM call. More |
Core pipeline
| Variable | Required | Default | Description |
|---|---|---|---|
CORE_API_BASE_URL | yes, for jobs that call the API: the defaults name a namespace the manifests don't use | http://fantasy-core-pipeline.core.svc.cluster.local:8000 (trigger client) or http://fantasy-api.core.svc.cluster.local:8000 (stale draft sweeper) | Base URL core's jobs use to call the core API's trigger endpoints. |
ESPN_SWID | yes, for ESPN reads | — | The SWID cookie, under the name core reads. The worker reads the same value as SWID. More |
AUTO_PUBLISH_IR_MOVES | no | off | Lets the waiver evaluator publish IR stash moves without a human (1, true or yes). More |
OTEL_CONSOLE_EXPORT | no | — | Set to 1 to print core's spans to the console. |
WORKER_OTEL_EXPORTER_OTLP_ENDPOINT | no | — | OTLP endpoint the job launcher passes to the worker pods it starts. |
RUN_ATTEMPT | no | — | Attempt number of a launcher run. |
JOB_NAME | no | — | Scopes idempotency keys for the lineup optimizer and rumor action jobs. |
JOB_CATALOG_PATH | no | /etc/fantasy-job-catalog/catalog.yaml | Job catalog the launcher schedules and the /v1/jobs API reads. |
{PROFILE}_IMAGE | yes, for each image placeholder the job catalog uses | — | Image reference the launcher substitutes for a catalog profile's image placeholder. |
K8S_NAMESPACE | no | default | Namespace the API and launcher create Jobs in. |
DRAFT_SYNC_IMAGE | yes, for drafts (POST /v1/draft/start returns 500 without it) | — | Core image the API runs draft listener Jobs from. |
BROWSER_WORKER_IMAGE | yes, for drafts | — | Worker image the API runs draft clicker Jobs from. |
DRAFT_SYNC_ENV_SECRET | yes, for drafts | — | Name of the Secret holding the ESPN cookies and IDs that draft Jobs load. |
DRAFT_SYNC_ENV_CONFIGMAP | yes, for drafts | — | Name of the ConfigMap holding league settings that draft Jobs load. |
DRAFT_SYNC_DB_SECRET_NAME | no | database-credentials | Secret holding the Postgres password for draft Jobs. |
DRAFT_SYNC_DB_SECRET_KEY | no | postgres-password | Key within that Secret. |
DRAFT_PICK_CLOCK_SECONDS_LIVE | no | 90 | Pick clock the API assumes for a live draft. |
DRAFT_PICK_CLOCK_SECONDS_PRACTICE | no | 30 | Pick clock the API assumes for a practice draft. |
AUTO_DRAFT_DELAY_SECONDS_LIVE | no | 45 | How long into a live pick the bot waits before auto-publishing its choice. |
AUTO_DRAFT_DELAY_SECONDS_PRACTICE | no | 15 | Auto-publish delay for a practice draft. |
DRAFT_STATUS_STALENESS_SECONDS | no | 15 | Age after which the API reports draft status as stale. |
DRAFT_PICK_CLOCK_SECONDS | no | 90 (set per Job by the API) | Pick clock the draft listener uses. |
AUTO_DRAFT_DELAY_SECONDS | no | 8 (set per Job by the API) | Auto-publish delay the draft listener uses. |
MIN_AUTO_PUBLISH_WINDOW_SECONDS | no | 10 | Smallest time left on the clock in which the listener still auto-publishes. |
MAX_CONSECUTIVE_PUBLISH_FAILURES | no | 3 | Publish failures in a row before the listener stops auto-publishing. |
PICK_REJECTION_GRACE_SECONDS | no | 10 | How long the listener waits for a rejected pick to clear before acting again. |
MATCHUP_WEIGHTING_ENABLED | no | false | Weights VORP by opponent defense strength. Only the exact string true enables it. More |
RUMOR_ACTION_CONFIDENCE_THRESHOLD | no | 0.75 | Minimum rumor confidence before the rumor action job acts. |
OUTGOING_COMMIT_HOURS | no | 72 | How long an approved outgoing trade stays committed. |
ACTIVITY_DEPENDENCY_HOLD_SECONDS | no | 3600 | How long a dependent activity waits for the activity it depends on. |
LLM_CONTENT_RETENTION_DAYS | no | 90 | Days to keep captured LLM prompt and response text. |
RUN_LOG_RETENTION_DAYS | no | 90 | Days to keep run log rows. |
MCP_GOOGLE_OAUTH_CLIENT_ID | yes, for /mcp | null (the MCP endpoint rejects every token) | Google OAuth client ID that MCP access tokens must be issued for. |
MCP_GOOGLE_ALLOWED_EMAIL | yes, for /mcp | — | The one Google account allowed to use the MCP endpoint. |
MCP_RESOURCE_URL | no | http://fantasy-core-pipeline.default.svc.cluster.local:8000/mcp | Public URL of the MCP endpoint, used as the OAuth resource. |
MCP_ALLOWED_HOSTS | no | empty | Comma-separated Host headers the MCP endpoint accepts. |
CHAT_LLM_PROVIDER_PRIORITY | no | falls back to LLM_PROVIDER_PRIORITY | Provider order for the chat endpoint. Ollama is always excluded. More |
LLM_REQUEST_TIMEOUT_SECONDS | no | 15 | Timeout for a hosted provider request from core. |
OLLAMA_REQUEST_TIMEOUT_SECONDS | no | 100 | Timeout for an Ollama request from core, which allows for a cold start. |
REDDIT_CLIENT_ID | no | null (Reddit is skipped and only RSS feeds are read) | Reddit API app ID. More |
REDDIT_CLIENT_SECRET | no | — | Reddit API app secret. More |
REDDIT_USER_AGENT | no | — | User agent sent to Reddit. More |
REDDIT_MIN_SCORE | no | 20 | Minimum post score to ingest. |
REDDIT_MAX_POST_AGE_HOURS | no | 72 | Oldest post to ingest. |
Browser worker
| Variable | Required | Default | Description |
|---|---|---|---|
CORE_PIPELINE_URL | yes, outside the cluster, for the trade bots | http://fantasy-core-pipeline.default.svc.cluster.local:8000 | Base URL the worker uses to call the core API for trade scoring, context and candidates. |
SWID | yes | null (falls back to OpenBao) | The SWID cookie, under the name the worker reads. Core reads the same value as ESPN_SWID. More |
ESPN_SESSION_STATE_FILE | yes, for the draft room, trade builder and other rendered pages | null (falls back to OpenBao) | Path to a saved Playwright storage state (cookies plus the Disney login's localStorage). More |
OPENBAO_ESPN_SECRET_PATH | no | secret/fantasy/espn | OpenBao path the worker reads espn_s2 and swid from when the cookie variables are unset. More |
OPENBAO_ESPN_SESSION_STATE_PATH | no | secret/fantasy/espn-session-state | OpenBao path for the saved browser session, read by the bots and written by the remote login Job. More |
OPENBAO_K8S_ROLE | no | fantasy-browser-worker | OpenBao Kubernetes auth role the remote login Job uses to save the session. |
CDP_DEBUG_PORT | no | — | Opens a Chrome DevTools port on the bot's browser for live debugging. |
NODE_ENV | no | — | When production, the bots block images, CSS and fonts to speed up page loads. |
FORCE_MANUAL_LOGIN | no | — | Makes the recon tools ignore the saved session and prompt for a fresh login. More |
TRADE_RECON_TO_TEAM_ID | yes, for trade_recon | — | Opposing team the trade recon tool opens a trade with. |
LOGIN_WAIT_TIMEOUT_MS | no | 1800000 | How long the remote login Job waits for you to log in over VNC. |
LOGIN_POLL_INTERVAL_MS | no | 5000 | How often the remote login Job checks for a completed login. |
VNC_DISPLAY | no | :99 | X display the remote login image starts Xvfb on. |
VNC_RESOLUTION | no | 1280x800x24 | Virtual screen size for the remote login browser. |
VNC_PORT | no | 5900 | x11vnc port in the remote login image. |
NOVNC_PORT | no | 6080 | noVNC web client port in the remote login image. |
VNC_PASSWORD | no | null (no password) | Password for the remote login VNC session. Without it, only a port-forward protects the session. |
DRY_RUN | no | per bot: lineup is live, every other bot dry-runs | Only 0, false or no make a bot write to ESPN; any other non-blank value forces a dry run. Set DRY_RUN=true while you evaluate the stack, because the lineup bot is live by default. More |
TRADE_DRY_RUN | no | falls back to DRY_RUN | Dry-run switch for trade submissions only, parsed like DRY_RUN. It lets trades go live without arming every other bot. More |
TRADE_SUBMIT_ENABLED | no | off | Arms outgoing trade submissions (1, true or yes). An approval and verified selectors are still required. More |
TRADE_RESPONSE_ENABLED | no | off | Arms accept, decline and counter responses to incoming trades (1, true or yes). More |
IDLE_EXIT_SECONDS | no | 0 (never exit) | The worker exits after this many idle seconds, so a KEDA-scaled pod frees its slot. Production uses 120. |
DEBUG_CAPTURE | no | on | Set to 0, false or no to stop saving page snapshots and the ESPN write log. More |
DEBUG_OUTPUT_DIR | no | <repo>/recon-output | Where debug captures and recon output are written. |
POD_NAME | no | falls back to HOSTNAME | Identifies the pod in the draft session registry. |
LOG_LEVEL | no | info | pino log level. More |
OTEL_SERVICE_NAME | no | fantasy-browser-worker | Service name on worker traces. Production sets a name per workload for core as well. |
DRAFT_ROOM_URL | no | — | Opens this draft room URL instead of navigating from the league. |
DRAFT_HEALTH_INTERVAL_MS | no | 10000 | How often the draft sync checks its own health. |
DRAFT_PICK_SILENCE_WARN_MS | no | 180000 | Warns when no pick has been seen for this long. |
DRAFT_STALL_TERMINAL_CYCLES | no | 3 | Failed health cycles before the draft sync gives up. |
DRAFT_COMMAND_POLL_INTERVAL_MS | no | 1000 | How often the draft sync polls Redis for pick commands. |
DRAFT_COMMAND_CONFIRM_TIMEOUT_MS | no | 5000 | How long to wait for ESPN to confirm a pick command. |
DRAFT_DOM_POLL_INTERVAL_MS | no | 5000 | How often the draft room page is re-read. |
DRAFT_WATCH_STALL_MS | no | 45000 | Stall threshold for the draft-watch script. |
LLM_REQUEST_TIMEOUT_MS | no | 15000 | Timeout for a hosted provider request from the worker. |
OLLAMA_REQUEST_TIMEOUT_MS | no | 100000 | Timeout for an Ollama request from the worker. |
OLLAMA_DIRECT_URL | no | http://ollama.default.svc.cluster.local:11434 | Ollama URL the worker uses for calls that bypass the interceptor. |
TRADE_EVALUATE_TIMEOUT_MS | no | 10000 | Timeout for the core trade evaluation call. |
TRADE_SCORE_TIMEOUT_MS | no | 30000 for scans, 20000 in the trade inbox | Timeout for the core trade score call. |
TRADE_CONTEXT_TIMEOUT_MS | no | 30000 | Timeout for the core trade context and bye outlook calls. |
TRADE_INBOX_SHADOW_ACCEPT_MIN_SCORE | no | — | Score at or above which the inbox's advisory shadow decision says accept. |
TRADE_INBOX_SHADOW_DECLINE_MAX_SCORE | no | — | Score at or below which the inbox's advisory shadow decision says decline. |
RUMOR_DIGEST_CHUNK_SIZE | no | 20 | Rumors per LLM request in the daily digest. |
RUMOR_DIGEST_TOKEN_BUDGET | no | 6000 | Token budget per digest request. |
RUMOR_DIGEST_CHUNK_DELAY_MS | no | 60000 | Pause between digest requests, to stay under provider rate limits. |
NTFY_URL | no | null (nothing is sent) | ntfy server URL for phone notifications. More |
NTFY_TOKEN | no | null (nothing is sent) | ntfy access token with publish rights to the topic. More |
NTFY_TOPIC | no | fantasy | ntfy topic. More |
WEB_UI_BASE_URL | no | null (pushes carry no link) | Public UI URL that notification taps open. More |
Web UI
| Variable | Required | Default | Description |
|---|---|---|---|
CORE_API_UPSTREAM | yes, outside the cluster | fantasy-core-pipeline.default.svc.cluster.local:8000 (set in the image) | host:port that the UI container's nginx proxies /v1/ to. |
VITE_API_PROXY_TARGET | no | http://localhost:8000 | Core API URL the Vite dev server proxies /v1 to. Set it in the shell, not in .env.local. More |
VITE_DEV_LEAGUE_ID | no | empty | League ID prefilled in dev builds only. Put it in .env.local. |
VITE_DEV_TEAM_ID | no | empty | Team ID prefilled in dev builds only. Put it in .env.local. |
VITEST_MAX_WORKERS | no | 25% | Vitest worker limit. |
Platform images and libraries
| Variable | Required | Default | Description |
|---|---|---|---|
OTEL_EXPORTER_OTLP_PROTOCOL | no | SDK default | Read by the OpenTelemetry SDKs; production sets http/protobuf for core and grpc for the worker. |
POSTGRES_DB | yes | — | Database the Postgres image creates on first start (fantasy_football). |
POSTGRES_USER | yes | — | Owner the Postgres image creates (bot_admin). |
POSTGRES_PASSWORD | yes | — | That user's password; the same value as DB_PASSWORD. |
PGDATA | no | — | Postgres data directory inside the volume. |
PGHOST | yes | — | psql connection for the read-only role Job. |
PGDATABASE | yes | — | psql connection for the read-only role Job. |
PGUSER | yes | — | psql connection for the read-only role Job. |
PGPASSWORD | yes | — | psql connection for the read-only role Job. |
READONLY_PASSWORD | yes | — | Password the Job sets on the grafana_reader role. More |
RABBITMQ_DEFAULT_USER | yes | — | Broker user that the definitions init container creates and the probes check. More |
RABBITMQ_DEFAULT_PASS | yes | — | That user's password. |
RABBITMQ_VM_MEMORY_HIGH_WATERMARK_RELATIVE | no | — | Share of container memory at which RabbitMQ blocks publishers (0.4). |
OAUTH2_PROXY_CLIENT_ID | yes | — | Google OAuth client ID for the sign-in proxy. More |
OAUTH2_PROXY_CLIENT_SECRET | yes | — | Google OAuth client secret. |
OAUTH2_PROXY_COOKIE_SECRET | yes | — | Key that signs the session cookie. |
OAUTH2_PROXY_REDIRECT_URL | yes | — | Your public callback URL, ending in /oauth2/callback. Production keeps it in OpenBao. |
OAUTH2_PROXY_COOKIE_DOMAINS | yes | — | Domain the session cookie is set for. Production keeps it in OpenBao. |
ARGOCD_IMAGE_UPDATER_KUSTOMIZE_V5 | no | — | Image Updater setting; see the Argo CD Image Updater docs. |
ARGOCD_IMAGE_UPDATER_GIT_COMMIT_USER | no | — | Author of the Image Updater's digest commits. |
ARGOCD_IMAGE_UPDATER_GIT_COMMIT_EMAIL | no | — | Email on the Image Updater's digest commits. |
ARGOCD_IMAGE_UPDATER_WRITE_BACK_METHOD | no | — | git, so new digests are committed to fantasy-infra. |
ARGOCD_IMAGE_UPDATER_WRITE_BACK_TARGET | no | — | kustomization, so digests land in the overlay's images list. |
ARGOCD_IMAGE_UPDATER_LOG_LEVEL | no | — | Image Updater log level. |
ARGOCD_IMAGE_UPDATER_HEALTH_PORT | no | — | Image Updater health port. |
How is this page kept current?
The descriptions live in docs/installation/env-vars.yaml. Each full site build scans core, the worker, the UI and fantasy-infra, and fails when a variable is read or set but not described, described but no longer read, or set from a Secret but not marked secret. The nightly build catches a new variable within a day. To check before you push, run npm run check:env from this repository with the other checkouts next to it, or with FANTASY_DOCS_ROOT pointing at them.