Environment Variables
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.
WHISPER_LANGUAGE, WHISPER_COMPUTE_TYPE, and the VAD_GAP_* variables seed fresh installs only; runtime values live in Settings > Transcription (VAD at GET/PUT /api/v1/settings).
Standard
Section titled “Standard”| Variable | Default | Description |
|---|---|---|
ANTHROPIC_API_KEY |
(none) | Claude API key (required when LLM_PROVIDER=anthropic, not needed for Ollama) |
LLM_PROVIDER |
anthropic |
Default LLM provider: anthropic, openrouter, openai-compatible, ollama, typesafe, or systemone-compatible |
OPENROUTER_API_KEY |
(none) | OpenRouter API key (required when LLM_PROVIDER=openrouter) |
OPENAI_BASE_URL |
http://localhost:8000/v1 |
Base URL for OpenAI-compatible API (only used with non-anthropic providers) |
TYPESAFE_API_KEY |
(none) | Provider A TypeSafe API key. Provider B uses its saved slot key. |
SYSTEMONE_API_KEY |
(none) | Provider A System One-compatible API key. Provider B uses its saved slot key. |
SYSTEMONE_BASE_URL |
(none) | Provider A compatible base URL. Provider B uses its saved slot base URL. Requests use /systemone; model discovery uses /models. Does not inherit OPENAI_BASE_URL. |
OPENROUTER_BASE_URL |
https://openrouter.ai/api/v1 |
OpenRouter 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_KEY |
not-needed |
API 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_URL |
http://localhost:8000 |
Public 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_MODEL |
small (GPU), tiny (CPU image) |
faster-whisper backend only. Model size. tiny, base, small, medium, large-v3, turbo, plus .en variants |
WHISPER_DEVICE |
cuda (GPU image), cpu (CPU image/source) |
cuda or cpu; applies to both local backends. Set to cpu with the CPU image or remote backend. |
WHISPER_BACKEND |
local |
local (faster-whisper, in the container), parakeet (NVIDIA Parakeet TDT 0.6B v3, in the container), or openai-api (remote HTTP server). WHISPER_MODEL and WHISPER_COMPUTE_TYPE apply to local only. See Transcription. |
HF_HOME |
/app/data/.cache in both images |
Persistent model-cache root. Parakeet uses parakeet/nemo-parakeet-tdt-0.6b-v3 beneath it. See model storage and sizing. |
WHISPER_API_BASE_URL |
(none) | Base URL for OpenAI-compatible whisper API |
WHISPER_API_KEY |
(none) | API key for whisper API |
WHISPER_API_MODEL |
whisper-1 |
Model name sent to whisper API |
WHISPER_LANGUAGE |
en |
ISO 639-1 language code, or auto. |
WHISPER_COMPUTE_TYPE |
auto |
faster-whisper backend only. auto, float16, int8_float16, int8, or float32. auto picks float16 on CUDA and int8 on CPU. See GPU Compute Type for per-GPU recommendations. |
VAD_GAP_DETECTION_ENABLED |
true |
true 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. Advanced tuning, not surfaced in the UI. |
VAD_GAP_START_MIN_SECONDS |
3.0 |
Minimum pre-transcript gap (seconds) at episode start that the VAD detector will cut. Anything shorter is left alone. |
VAD_GAP_MID_MIN_SECONDS |
8.0 |
Minimum 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. |
VAD_GAP_TAIL_MIN_SECONDS |
3.0 |
Minimum post-transcript gap at episode end that the VAD detector will cut when no postroll marker already covers it. |
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_LEVEL |
INFO |
DEBUG, INFO, WARNING, or ERROR |
LOG_FORMAT |
text |
text or json. JSON output works with log aggregators (Loki, CloudWatch). |
DATA_DIR |
/app/data |
Data storage directory. Aliases DATA_PATH and MINUSPOD_DATA_DIR are also honored. |
TZ |
UTC |
Container 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
Section titled “Security”| Variable | Default | Description |
|---|---|---|
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_AUTH |
true in Compose |
Refuse 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_PROCESSING |
false in Compose |
Allow 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_SECURE |
auto in Compose |
Follow BASE_URL. Set true or false only when proxy termination makes that result incorrect. An unset value uses the same automatic behavior. |
SESSION_COOKIE_SAMESITE |
Strict |
Override to Lax only if a specific integration breaks. |
MINUSPOD_ENABLE_HSTS |
false |
Set to true once the deployment is HTTPS-only. HSTS traps browsers so don’t flip this on a dual-protocol setup. |
MINUSPOD_URL_PREFIX |
empty | Serve the app under a reverse-proxy sub-path such as /minuspod, so the UI is at /minuspod/ui/ and feeds at /minuspod/<slug>. BASE_URL must include the same prefix. The first segment cannot be ui, api, episodes, health, opml or login. See Sub-path hosting. |
MINUSPOD_TRUSTED_PROXY_COUNT |
0 |
Reverse-proxy hops to trust when reading X-Forwarded-For; 1 behind Cloudflare, cloudflared, nginx, or Traefik. Leaving it at 0 behind a proxy breaks login lockout and per-IP rate limits. See Client IP for login lockout. |
SSRF_IP_PINNING |
true |
Pin the resolved IP for outbound fetches so a DNS record that flips between check and connect cannot reach an internal target. Set false (or 0, no, off) only to isolate a fetch failure you suspect the pin causes. Provider SDK traffic is unaffected. See Outbound request safety. |
MINUSPOD_ALLOW_PRIVATE_FEED_HOSTS |
false |
Allow 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
Section titled “Advanced”Env-backed rows seed the default of the stored setting named in the row. The stored value is editable at runtime in Settings and wins over the env var.
| Variable | Default | Description |
|---|---|---|
PROCESSING_SOFT_TIMEOUT |
3600 |
Seconds before a stuck job is auto-cleared. Seeds fresh installs; runtime value lives in Settings > Transcription. |
PROCESSING_HARD_TIMEOUT |
7200 |
Seconds before the processing lock is force-released. Must exceed the soft timeout. |
AD_DETECTION_MAX_FAILED_WINDOW_RATIO |
0.25 |
Fraction 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 accepts 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_CHANGE |
true |
Run 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 (reviewer_calibration_on_change). |
LOW_AD_YIELD_ACTION |
nothing |
Action 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 (low_ad_yield_action); each feed can override it. |
EPISODE_LOG_RETENTION_DAYS |
30 |
Days 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 (episode_log_retention_days); each feed can opt in or out. |
EPISODE_LOG_LEVEL |
debug |
Minimum 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 (episode_log_level). |
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 (download_user_agent, Settings > Outbound Requests), so a host that starts refusing ours is fixed without a new image. |
FEED_USER_AGENT |
PodcastAdRemover/1.0 |
User-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 (feed_user_agent, Settings > Outbound Requests). |
LOG_DOWNLOAD_QUERY |
false |
Include 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 (log_download_query, Settings > Outbound Requests). |
MAX_AUDIO_DOWNLOAD_MB |
500 |
Per-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. |
MINUSPOD_MAX_ARTWORK_BYTES |
26214400 (25 MB) |
Cap on podcast artwork download size. Clamped to [65536, 52428800]. Env-backed. |
MINUSPOD_MAX_RSS_BYTES |
209715200 (200 MB) |
Cap on RSS response body size. Floor is 1 MB. Env-backed. |
RATE_LIMIT_STORAGE_URI |
memory-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_UID |
1000 |
UID gunicorn runs as inside the container. Override to match host volume ownership. |
APP_GID |
1000 |
GID counterpart to APP_UID. |
GUNICORN_BIND |
0.0.0.0:8000 |
Listen 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_PORT |
8000 |
Port 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_WORKERS |
2 |
Worker count. Episode processing always runs in the background leader worker, so extra workers only serve the API, UI and feeds. Lower means single-threaded UI blocking during RSS refresh; higher multiplies per-worker rate-limit counters (when using memory://). |
GUNICORN_TIMEOUT |
600 |
Per-request hard timeout. |
GUNICORN_GRACEFUL_TIMEOUT |
330 |
Seconds between SIGTERM and SIGKILL on shutdown. |
GUNICORN_ACCESS_LOG |
- (stdout) |
Where the access log goes. Set it empty to disable it, or to a file path to write there. |
GUNICORN_ERROR_LOG |
- (stderr) |
Where the server error log goes. Set it empty to disable it, or to a file path to write there. |
MINUSPOD_STOP_GRACE_PERIOD |
360s |
Compose container shutdown grace. Keep it longer than GUNICORN_GRACEFUL_TIMEOUT. |
MINUSPOD_BIND_ADDRESS |
0.0.0.0 |
Host 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_HOURS |
24 |
How 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
Section titled “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. An unset variable leaves the built-in default in place. 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:
| Suffix | Type | Range / values |
|---|---|---|
_TEMPERATURE |
float | 0.0 - 2.0 |
_MAX_TOKENS |
int | 128 - 32768 |
_REASONING_BUDGET |
int | 1024 - 65536 (Anthropic extended thinking) |
_REASONING_LEVEL |
enum | none, 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
Section titled “Optional”| Variable | Default | Description |
|---|---|---|
TUNNEL_TOKEN |
(none) | Cloudflare tunnel token. See 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_ENVIRONMENT |
production |
Environment tag forwarded to Sentry. |
Deprecated
Section titled “Deprecated”| Variable | Description |
|---|---|
RETENTION_PERIOD |
Legacy minutes-based retention. Auto-converted to days on first startup. Use Settings UI or PUT /api/v1/settings/retention instead. |