You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Reference for the variables docker-compose.yml reads from .env (source of truth: .env.example + docker-compose.yml, checked 2026-10-04).
Quick Start
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)