Reference for the variables docker-compose.yml reads from .env (source of truth: .env.example + docker-compose.yml, checked 2026-10-04).
Quick Start
Terminal window
cp.env.example.env
# Edit .env with your values
Backend (API + Worker)
Database
Variable
Required
Default
Description
POSTGRES_USER
Yes
app
PostgreSQL username
POSTGRES_PASSWORD
Yes
—
PostgreSQL password
POSTGRES_DB
Yes
books
Database name
ConnectionStrings__Default
Auto
—
Full connection string (auto-built from above)
ASP.NET Core
Variable
Required
Default
Description
ASPNETCORE_ENVIRONMENT
No
Production
Development / Production
ASPNETCORE_URLS
No
http://+:8080
Listen URLs
Authentication
Variable
Required
Default
Description
JWT_SECRET
Yes
—
JWT signing key (min 32 chars) → Jwt__SecretKey
JWT_ISSUER
Yes
— (no compose default)
JWT issuer claim → Jwt__Issuer
JWT_AUDIENCE
Yes
— (no compose default)
JWT audience claim → Jwt__Audience
GOOGLE_CLIENT_ID
Yes
—
Google OAuth client ID → Google__ClientId
GOOGLE_LEGACY_CLIENT_IDS
No
empty
Comma-separated extra client IDs accepted during rotation
LLM / email / telemetry
Variable
Required
Default
Description
OPENAI_API_KEY
For translate/explain/tutor
empty
→ OpenAI__ApiKey (api and worker)
OPENAI_MODEL
No
gpt-4.1-mini (in OpenAiOptions)
Default model (translate) → OpenAI__Model. Empty = the code default
OPENAI_EXPLAIN_MODEL
No
gpt-4.1-mini (in OpenAiOptions)
Explain model → OpenAI__Explain__Model. Empty = the code default
RESEND_API_KEY
For password reset
empty
→ Resend__ApiKey
RESEND_FROM_EMAIL
No
noreply@textstack.app
→ Resend__FromEmail
ADMIN_ALERT_EMAIL
No
empty (alerts off)
SEO backfill / ops failure alerts → Resend__AdminAlertEmail
SENTRY_DSN
No
empty (Sentry fully off)
Keep ASPNETCORE_ENVIRONMENT=Development locally, or dev events land in prod Sentry
GIT_SHA
No
empty
Build arg → SENTRY_RELEASE in the image (set by CI)
Ollama (Ollama__BaseUrl, Ollama__Model=gemma4:e2b) and the cache paths (Tts__CachePath,
Explain__CachePath, Translate__CachePath) are hard-coded in docker-compose.yml, not read from .env.
SEO / SSG worker
Variable
Required
Default
Description
INDEXNOW_KEY
No
empty
IndexNow key. Bing binds the first key it saw — do not rotate
INDEXNOW_ENABLED
No
false
Turn IndexNow pings on
PDF content cleanup (quality-poll.sh, host script)
Variable
Required
Default
Description
CONTENT_CLEANUP_ENABLED
No
false
LLM cleanup pass for low-scoring PDF chapters (feat-0007)
CONTENT_QUALITY_THRESHOLD
No
60
Score below which cleanup runs
CLEANUP_TIMEOUT
No
1500
Per-chapter Claude budget, seconds
Test / CI knobs (never set in prod)
Variable
Default
Maps to
ENABLE_TEST_AUTH
false
Test auth endpoints for integration + E2E
GUEST_SESSION_PERMIT_LIMIT
3
RateLimits__GuestSessionPermitLimit (per IP / 5 min)
USER_LOGIN_PERMIT_LIMIT
10
RateLimits__UserLoginPermitLimit (per IP / min)
CLIP_PERMIT_LIMIT
20
RateLimits__ClipPermitLimit
ACCOUNT_DELETE_PERMIT_LIMIT
3
RateLimits__AccountDeletePermitLimit
DEVICE_CODE_PERMIT_LIMIT
5
RateLimits__DeviceCodePermitLimit (per IP / min)
GUEST_DAILY_ENRICHMENT_CAP
50
Entitlements__Tiers__Guest__DailyEnrichmentCap
Storage
Variable
Required
Default
Description
Storage__RootPath
No
/storage
Set in docker-compose.yml; host dir ./data/storage
Storage__LibraryRoot
No
/library
V2 library bytes; host dir ./data/library. Must not nest with Storage__RootPath (served publicly) either way, nor equal it: startup fails otherwise. V2 files left in library/ or uploads/ under the root only log a warning with the move command (the /storage denylist keeps them private) (#1110)
Migrations
Variable
Required
Default
Description
PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD
Yes (migrator)
—
libpq connection for database/migrate.sh; compose sets them from POSTGRES_*. The rollback target is an argument (docker compose run --rm migrator down <version>), not a variable (ADR-026)
MCP server (--profile mcp)
Set in docker-compose.yml: MCP_TRANSPORT=http, TEXTSTACK_API_URL=http://api:8080,
TEXTSTACK_SITE_HOST=textstack.app, ASPNETCORE_URLS=http://+:8090. Local stdio mode reads
TEXTSTACK_MCP_TOKEN (or uses the device flow). See mcp.md.
Frontend (Web)
All frontend variables must be prefixed with VITE_. They are build-time: prod values are
set in .github/workflows/deploy.yml, not in .env.
Variable
Required
Default
Description
VITE_API_URL
No
http://localhost:8080
API base URL (prod: /api)
VITE_STORAGE_URL
No
VITE_API_URL
Base for /storage files (prod: https://textstack.app)