Environment Variables

September 11, 2026 ยท View on GitHub

< Docs index | Project README


Contents

Grouped by how often you'll touch them. Standard is what a typical deployment sets; Security is the hardening surface; Advanced are tuning knobs for edge cases; Optional are opt-in features.

Standard

VariableDefaultDescription
ANTHROPIC_API_KEY(none)Claude API key (required when LLM_PROVIDER=anthropic, not needed for Ollama)
LLM_PROVIDERanthropicLLM backend: anthropic, openrouter, openai-compatible, or ollama
OPENROUTER_API_KEY(none)OpenRouter API key (required when LLM_PROVIDER=openrouter)
OPENAI_BASE_URLhttp://localhost:8000/v1Base URL for OpenAI-compatible API (only used with non-anthropic providers)
OPENROUTER_BASE_URLhttps://openrouter.ai/api/v1OpenRouter API base URL. Override for private proxies or regional endpoints.
ANTHROPIC_BASE_URL(anthropic default)Anthropic API base URL. Override for private proxies.
OPENAI_API_KEYnot-neededAPI key for OpenAI-compatible endpoint (not required for Ollama or local wrappers)
OPENAI_MODEL(none)Model id that seeds claude_model, verification_model, and chapters_model whenever one of those settings is unset. No shipped default exists: required for Ollama (e.g. qwen3:14b) and needed for OpenAI-compatible/OpenRouter unless you pick a model in Settings > AI models instead. Once a setting has a stored value the env var no longer touches it. Processing fails with a message pointing at Settings > AI models until every model setting is configured. There is no LLM_MODEL variable.
BASE_URLhttp://localhost:8000Public URL for generated feed links
UI_BASE_URL(falls back to BASE_URL)Public URL for UI links in webhooks (set if UI is on a different domain than feeds)
WHISPER_MODELsmallWhisper model size. tiny, base, small, medium, large-v3, turbo, plus .en variants
WHISPER_DEVICEcudacuda or cpu. Set to cpu when using API backend to skip GPU init.
WHISPER_BACKENDlocallocal (faster-whisper) or openai-api (remote HTTP)
WHISPER_API_BASE_URL(none)Base URL for OpenAI-compatible whisper API
WHISPER_API_KEY(none)API key for whisper API
WHISPER_API_MODELwhisper-1Model name sent to whisper API
WHISPER_LANGUAGEenISO 639-1 language code, or auto. Seeds fresh installs only; runtime value is in Settings > Transcription.
WHISPER_COMPUTE_TYPEautoauto, float16, int8_float16, int8, or float32. auto picks float16 on CUDA and int8 on CPU. Seeds fresh installs only; runtime value is in Settings > Transcription. See GPU Compute Type for per-GPU recommendations.
VAD_GAP_DETECTION_ENABLEDtruetrue or false. Toggles the VAD gap detector, which cuts audio regions Whisper's VAD dropped (sped-up disclaimers, distorted ad tails) that the transcript-based detectors never see. Seeds the DB row on fresh installs; runtime value is at GET/PUT /api/v1/settings. Advanced tuning, not surfaced in the UI.
VAD_GAP_START_MIN_SECONDS3.0Minimum pre-transcript gap (seconds) at episode start that the VAD detector will cut. Anything shorter is left alone. Seeds fresh installs only.
VAD_GAP_MID_MIN_SECONDS8.0Minimum mid-episode untranscribed gap. Standalone mid-gaps still require BOTH signoff-before AND resume-after context to emit; gaps adjacent to a detected ad extend that ad in place regardless of this threshold. Seeds fresh installs only.
VAD_GAP_TAIL_MIN_SECONDS3.0Minimum post-transcript gap at episode end that the VAD detector will cut when no postroll marker already covers it. Seeds fresh installs only.
OLLAMA_API_KEY(none)Ollama Cloud key. Leave unset for local.
PODCAST_INDEX_API_KEY(none)PodcastIndex.org API key for podcast search
PODCAST_INDEX_API_SECRET(none)PodcastIndex.org API secret
LOG_LEVELINFODEBUG, INFO, WARNING, or ERROR
LOG_FORMATtexttext or json. JSON output works with log aggregators (Loki, CloudWatch).
DATA_DIR/app/dataData storage directory. Aliases DATA_PATH and MINUSPOD_DATA_DIR are also honored.
TZUTCContainer timezone (e.g. America/New_York). When set to a valid IANA zone, seeds the notification_timezone setting that computes timestamp_local in webhook and email notifications. Override the setting at Settings > Notifications, or GET/PUT /api/v1/settings/notifications/timezone, without touching TZ.

Security

VariableDefaultDescription
MINUSPOD_MASTER_PASSPHRASE(unset)Unlocks encrypted provider-key store. Strongly recommended for production; first boot migrates any plaintext rows to enc:v1:. Losing it makes stored keys unrecoverable (env fallback still works).
MINUSPOD_REQUIRE_AUTHtrue in ComposeRefuse the API until an application password is set. Direct source runs default to false for development compatibility.
MINUSPOD_SETUP_TOKEN(unset)One-time header value for remote first-password setup when required auth is enabled. Send it as X-MinusPod-Setup-Token. Loopback setup does not need a token. Remove it after setup.
MINUSPOD_ALLOW_PUBLIC_PROCESSINGfalse in ComposeAllow an unauthenticated podcast request to start processing an unprocessed episode. Authenticated feed credentials can start processing regardless. Direct source runs default to true for compatibility.
SESSION_COOKIE_SECUREauto in ComposeFollow BASE_URL. Set true or false only when proxy termination makes that result incorrect. An unset value uses the same automatic behavior.
SESSION_COOKIE_SAMESITEStrictOverride to Lax only if a specific integration breaks.
MINUSPOD_ENABLE_HSTSfalseSet to true once the deployment is HTTPS-only. HSTS traps browsers so don't flip this on a dual-protocol setup.
MINUSPOD_TRUSTED_PROXY_COUNT0Reverse-proxy hops to trust when reading X-Forwarded-For. 1 behind Cloudflare / cloudflared / nginx / Traefik, higher for a multi-proxy chain. Leaving this at 0 behind a proxy breaks login lockout (the proxy IP is private/loopback, which the lockout excludes) and per-IP rate limits (they key on the proxy instead of the client); audit logs + auth-failure webhooks also carry the wrong IP. Startup logs a WARN when unset.
SSRF_IP_PINNINGtruePin the resolved IP for outbound fetches. Each request and redirect hop resolves the hostname once, checks every address against the SSRF rules, and connects to those addresses in order, so a DNS record that flips between the check and the connect cannot reach an internal target. If a connect fails, the next validated address is tried, each at most once. The URL, Host header, SNI, and certificate verification stay on the original hostname. Set false (or 0, no, off) to revert to checking the hostname and letting the HTTP client resolve it again; use that only to isolate a fetch failure you suspect the pin causes. Provider SDK traffic is unaffected, since those SDKs do their own HTTP.
MINUSPOD_ALLOW_PRIVATE_FEED_HOSTSfalseAllow RSS feed sources on private / loopback / LAN hosts. Off by default: feed URLs are untrusted and validated with the strict SSRF tier (DNS-resolved, private/metadata blocked) so a stored URL can't be rebound to an internal address on refresh. Set true only if you serve a feed from a private address (e.g. a LAN Audiobookshelf).

Advanced

VariableDefaultDescription
PROCESSING_SOFT_TIMEOUT3600Seconds before a stuck job is auto-cleared. Seeds fresh installs; runtime value lives in Settings > Transcription.
PROCESSING_HARD_TIMEOUT7200Seconds before the processing lock is force-released. Must exceed the soft timeout.
AD_DETECTION_MAX_FAILED_WINDOW_RATIO0.25Fraction of a detection or verification pass's windows that may fail (LLM error, timeout) before the whole pass is treated as failed, instead of accepting a result with those windows unexamined. A failed first pass fails the episode, which the retry ladder then picks up. A failed verification pass leaves the first-pass result and its markers alone and records the second scan as incomplete rather than clean; the episode is not reprocessed on its own. 1.0 restores the legacy behavior of accepting the pass unless every window failed. This variable seeds the ad_detection_max_failed_window_ratio setting, which is read on every pass, so editing the stored value retunes the threshold without a restart.
REVIEWER_CALIBRATION_ON_CHANGEtrueRun the reviewer calibration self-test in a background thread when the reviewer model setting changes. Each run costs 8 LLM calls. Set to false to switch models without paying for the check. Env-backed: seeds the default; the stored reviewer_calibration_on_change setting is editable at runtime.
LOW_AD_YIELD_ACTIONnothingAction taken automatically when a pipeline-initiated run removes far less ad time than the feed's recent average: nothing, redetect (rerun detection from the stored transcript), reprocess, or full. Fires at most once per episode (a failed rerun still counts) and never for a manual reprocess. Env-backed: seeds the default; the stored low_ad_yield_action setting is editable at runtime in Settings, and each feed can override it.
EPISODE_LOG_RETENTION_DAYS30Days to keep per-run pipeline logs, clamped to [0, 365]. 0 turns run log storage off and lets the cleanup sweep delete what is already stored. Env-backed: seeds the default; the stored episode_log_retention_days setting is editable at runtime in Settings, and each feed can opt in or out.
EPISODE_LOG_LEVELdebugMinimum level kept in a run log: debug or info. A run log can only keep what the server already writes, so there are no debug lines to keep unless LOG_LEVEL is DEBUG. Env-backed: seeds the default; the stored episode_log_level setting is editable at runtime in Settings.
DOWNLOAD_USER_AGENT(a current Chrome string)User-Agent sent when fetching audio, artwork, and chapters. Some hosts run bot mitigation that refuses browser identifiers below a rolling version floor, which surfaces as a 403 on download even though the file is there. Env-backed: seeds the default; the stored download_user_agent setting is editable at runtime in Settings > Outbound Requests, so a host that starts refusing ours is fixed without a new image.
FEED_USER_AGENTPodcastAdRemover/1.0User-Agent sent when fetching RSS. Kept separate from the download UA because some feed hosts do the opposite and answer only a declared podcast client. Env-backed: seeds the default; the stored feed_user_agent setting is editable at runtime in Settings > Outbound Requests.
LOG_DOWNLOAD_QUERYfalseInclude URL query strings in download logs. The requested path and the full redirect chain are logged either way. This adds the query, which on a podcast enclosure regularly carries a signed CDN token or a per-listener tracking id. Turn it on while debugging a refusal, not permanently, since those values then sit in your logs for as long as you keep them. Env-backed: seeds the default; the stored log_download_query setting is editable at runtime in Settings > Outbound Requests.
MAX_AUDIO_DOWNLOAD_MB500Per-episode download size cap in MB. Raise it for feeds with very long or high-bitrate episodes (a 260-minute episode at 256kbps is about 500MB). Guards against a broken or malicious enclosure filling the disk, so keep it finite. Env-backed: seeds the default; editable at runtime in Settings, and a saved UI value wins over the env var.
MINUSPOD_MAX_ARTWORK_BYTES26214400 (25 MB)Cap on podcast artwork download size. Clamped to [65536, 52428800]. Env-backed: seeds the default; editable at runtime in Settings.
MINUSPOD_MAX_RSS_BYTES209715200 (200 MB)Cap on RSS response body size. Floor is 1 MB. Env-backed: seeds the default; editable at runtime in Settings.
RATE_LIMIT_STORAGE_URImemory-threadsafe://Flask-limiter storage backend. Default is per-worker; set to redis://host:6379 + run a Redis sidecar for exact declared limits across workers.
APP_UID1000UID gunicorn runs as inside the container. Override to match host volume ownership.
APP_GID1000GID counterpart to APP_UID.
GUNICORN_BIND0.0.0.0:8000Listen address. Accepts a comma-separated list for multiple sockets. For dual-stack on rootless Podman, use [::]:8000: one IPv6 wildcard also accepts IPv4 when the kernel keeps bindv6only=0 (the default). Do not list both 0.0.0.0:8000 and [::]:8000 on such a kernel; the second bind fails with EADDRINUSE and gunicorn exits.
MINUSPOD_PORT8000Port for the default listen address (0.0.0.0:$MINUSPOD_PORT). Handy for network_mode: host or running several instances on one host without a port-mapping conflict. Ignored when GUNICORN_BIND is set, which takes precedence. The container EXPOSE stays at 8000 (build-time metadata only); the actual listen port follows this var.
GUNICORN_WORKERS2Worker count. Lower means single-threaded UI blocking during RSS refresh; higher multiplies per-worker rate-limit counters (when using memory://).
GUNICORN_TIMEOUT600Per-request hard timeout.
GUNICORN_GRACEFUL_TIMEOUT330Seconds between SIGTERM and SIGKILL on shutdown.
MINUSPOD_STOP_GRACE_PERIOD360sCompose container shutdown grace. Keep it longer than GUNICORN_GRACEFUL_TIMEOUT.
MINUSPOD_BIND_ADDRESS0.0.0.0Host address used by the Compose port mapping. Set 127.0.0.1 when a local reverse proxy, VPN, or wrapper is the only intended entry point.
SECRET_KEY(auto-generated)Flask session signing key. If unset, a random value is generated and stored in the SQLite settings table. Set it explicitly only when required by a custom deployment. Rotating it invalidates every existing session.
SESSION_LIFETIME_HOURS24How long authenticated sessions stay valid, in hours.
OMP_NUM_THREADS(library default)Caps OpenMP threads for local faster-whisper CPU transcription. On hybrid Intel CPUs the default can push work onto the slow E-cores and thrash the cache; set it to your performance-core count (more threads is not faster). No effect with a remote Whisper API or on GPU. See Installation.

LLM stage tunables

Every per-stage LLM control in Settings > Ad Detection has a matching env var: the setting key in uppercase. Set one to pin the control (the UI renders it read-only with a note); unset it to hand control back to the stored value. Defaults match the pre-tunable behavior, so an unset variable changes nothing. See the annotated list in .env.example and the configuration guide.

The stage prefixes are DETECTION_, VERIFICATION_, REVIEWER_, CHAPTER_BOUNDARY_, and CHAPTER_TITLE_. Each takes the same four suffixes:

SuffixTypeRange / values
_TEMPERATUREfloat0.0 - 2.0
_MAX_TOKENSint128 - 32768
_REASONING_BUDGETint1024 - 65536 (Anthropic extended thinking)
_REASONING_LEVELenumnone, low, medium, high (non-Anthropic providers)

So DETECTION_TEMPERATURE, VERIFICATION_MAX_TOKENS, REVIEWER_REASONING_LEVEL, and so on. Two legacy names still resolve: AD_DETECTION_MAX_TOKENS (alias of DETECTION_MAX_TOKENS) and REVIEW_MAX_TOKENS (alias of REVIEWER_MAX_TOKENS).

Optional

VariableDefaultDescription
TUNNEL_TOKEN(none)Cloudflare tunnel token. See Remote Access / Security > Before enabling the tunnel profile.
SENTRY_DSN(none)Opt-in Sentry. Requires sentry-sdk installed. Headers, cookies, CSRF tokens, and credential-like query params are scrubbed before send; no performance tracing.
MINUSPOD_RELEASE(none)Optional release tag forwarded to Sentry.
SENTRY_ENVIRONMENTproductionEnvironment tag forwarded to Sentry.

Deprecated

VariableDescription
RETENTION_PERIODLegacy minutes-based retention. Auto-converted to days on first startup. Use Settings UI or PUT /api/v1/settings/retention instead.

< Docs index | Project README