Configuration
Every environment variable, whether it is required, its default, and what it does
All settings live in .env next to docker-compose.yml; .env.example is the annotated
template. Change a value, then docker compose up -d to restart what depends on it.
Database
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL | yes | postgres://owly:owly@localhost:5432/owly | Postgres connection string. Inside the compose network this is overridden to reach the bundled postgres service; the value in .env is what host-run commands such as pnpm migrate use. Point it at an external Postgres to use that instead; add ?sslmode=require for managed providers. |
POSTGRES_USER | bundled | owly | User for the bundled Postgres container. |
POSTGRES_PASSWORD | bundled | none | Password for the bundled container. Must be set: compose refuses to start while it is empty. Generate one with openssl rand -base64 24. |
POSTGRES_DB | bundled | owly | Database name for the bundled container. |
DATABASE_POOL_MAX | no | 10 | Maximum connections in the app's pool. |
PGSSLMODE | no | unset | require or no-verify turns TLS on for the database connection (certificate not verified); disable forces it off. A sslmode= parameter in DATABASE_URL does the same. |
"bundled" means required only when you use the bundled Postgres container.
LLM
| Variable | Required | Default | Description |
|---|---|---|---|
LLM_PROVIDER | no | anthropic | anthropic or openai. The openai provider also talks to OpenAI-compatible servers such as Ollama via OPENAI_BASE_URL. |
ANTHROPIC_API_KEY | with anthropic | none | Claude API key. Needs API credits; a Claude subscription does not count. |
OPENAI_API_KEY | with openai | none | OpenAI API key. Ollama accepts any non-empty value, for example ollama. |
OPENAI_BASE_URL | no | unset | Base URL of an OpenAI-compatible server, for example http://host.docker.internal:11434/v1 for Ollama on the Docker host, or https://openrouter.ai/api/v1. Best effort: servers differ in JSON-mode support. |
LLM_MODEL_DIGEST | no | claude-sonnet-4-6 (anthropic) / gpt-5 (openai) | Model that writes the digest. Needs to handle structured JSON over up to 300 items. |
LLM_MODEL_SUMMARY | no | claude-haiku-4-5 (anthropic) / gpt-5-mini (openai) | Model that writes per-item summaries and the reader-profile rewrite. |
See LLM providers for a worked example per provider.
Digest
| Variable | Required | Default | Description |
|---|---|---|---|
DIGEST_LANGUAGE | no | en | Language of summaries and digests, as a language code such as en, nl or de. The UI stays English. |
DIGEST_TIMEZONE | no | UTC | IANA timezone, for example Europe/Amsterdam. Used for the digest week window and for all schedules. |
The digest schedule itself (weekly or daily, day and time) is not an environment variable. Set it in the app under Settings; see Scheduling.
Schedules
| Variable | Required | Default | Description |
|---|---|---|---|
INGEST_CRON | no | 0 */6 * * * | When sources are fetched, as a cron expression in DIGEST_TIMEZONE. Default: every six hours. |
CLEANUP_CRON | no | 15 4 * * * | When the retention cleanup runs. Default: daily at 04:15. |
ITEM_CONTENT_RETENTION_DAYS | no | 30 | Days after fetching before an item's full article text is cleared. Titles, links, summaries, digests and feedback are kept forever. 0 disables the cleanup. |
Email is optional. It stays disabled until the selected provider is fully configured: for Resend
that is RESEND_API_KEY, for SMTP SMTP_HOST, and for both DIGEST_EMAIL_FROM,
DIGEST_EMAIL_TO and SITE_URL.
| Variable | Required | Default | Description |
|---|---|---|---|
EMAIL_PROVIDER | no | resend | resend or smtp. |
RESEND_API_KEY | with resend | none | Resend API key. |
SMTP_HOST | with smtp | none | SMTP server hostname. A relay on the Docker host is reached as host.docker.internal, not localhost. |
SMTP_PORT | no | 587 | SMTP port. |
SMTP_USER | no | none | SMTP username. Leave empty for relays without authentication. |
SMTP_PASS | no | none | SMTP password. |
SMTP_SECURE | no | true on port 465, else false | true: implicit TLS from the first byte (the port 465 model). false: start plain and upgrade with STARTTLS when the server offers it. Over untrusted networks prefer port 465 with true; STARTTLS can be stripped by an active attacker. |
DIGEST_EMAIL_FROM | for email | none | From address. For Resend it must be on a domain verified in Resend. |
DIGEST_EMAIL_TO | for email | none | Recipient of the digest. |
Site and security
| Variable | Required | Default | Description |
|---|---|---|---|
SITE_URL | for email | none | Public URL of your deployment, for example https://owly.example.com. Used to build the links back to the app in the digest email. Its hostname is added to the Host allowlist automatically. |
ALLOWED_HOSTS | no | none | Comma-separated hostnames the API answers to, for example owly.example.com,owly.internal. Requests with another Host header get 403 Invalid host. (a DNS-rebinding defence). localhost and 127.0.0.1 are always allowed. Needed when you reach the app through a proxy or by LAN name and have not set SITE_URL. |
Image
| Variable | Required | Default | Description |
|---|---|---|---|
OWLYPOST_TAG | no | latest | Image tag for ghcr.io/esmeepeters/owlypost. Pin a release with 1.1.0 or 1.1; use dev for a locally built image. See Updating. |
Renamed variables
ANTHROPIC_MODEL_DIGEST and ANTHROPIC_MODEL_SUMMARY were renamed to LLM_MODEL_DIGEST and
LLM_MODEL_SUMMARY. The old names are no longer read: an install that still sets them silently
falls back to the default models until .env is updated.