Self-Hosting Configuration

Complete reference for Docker environment variables, Convex backend variables, service topology, and volume persistence.

This page is the complete configuration reference for a self-hosted Owlat instance. For step-by-step setup, see Self-Hosting.

Two Configuration Layers

Docker .env vs Convex env vars

Owlat uses two separate configuration layers. Mixing them up is the most common setup mistake.

  • Docker .env — read by Docker Compose at container startup. Controls ports, secrets shared between containers, and browser-facing URLs.
  • Convex environment variables — read by serverless functions inside the Convex backend. Set after deployment via npx convex env set. Controls application behavior: email provider, auth secrets, sender identity, integrations.

Docker Environment Variables

These go in the .env file at the project root. Docker Compose reads them when starting containers.

Convex Backend

VariableRequiredDefaultDescription
INSTANCE_SECRETYesConvex backend instance identity. Generate with openssl rand -hex 32.
CONVEX_ADMIN_KEYYesAdmin key for deploying functions. Generated after first boot via docker compose exec convex ./generate_admin_key.sh.

Public URLs

These must be reachable from the user's browser — do not use Docker-internal hostnames.

VariableRequiredDefaultDescription
NUXT_PUBLIC_CONVEX_URLYeshttp://localhost:3210Convex backend URL (browser → Convex).
NUXT_PUBLIC_CONVEX_SITE_URLYeshttp://localhost:3211Convex site proxy URL (browser → HTTP actions).
NUXT_PUBLIC_SITE_URLYeshttp://localhost:3000Web application URL.

MTA Configuration

The MTA is an opt-in service

The built-in MTA runs only when it is the delivery provider (EMAIL_PROVIDER=mta) or when postbox/inbox need it for inbound mail — gated by the mta Docker Compose profile. The default self-host ships COMPOSE_PROFILES=mta; a Resend/SES, receive-only, or IMAP-only deployment drops mta and doesn't run it. These variables apply only when the MTA is active. See Operating Modes.

VariableRequiredDefaultDescription
MTA_API_KEYYesShared secret for Convex → MTA authentication. Generate with openssl rand -base64 32.
MTA_WEBHOOK_SECRETYesHMAC secret for MTA → Convex webhook callbacks. Generate with openssl rand -base64 32.
EHLO_HOSTNAMEFor deliverymail.localhostSMTP EHLO/HELO hostname. The mail.localhost default boots fine; required for production sending, where it must match your server's rDNS PTR record.
RETURN_PATH_DOMAINFor deliverybounces.localhostDomain for VERP bounce return-path addresses. The bounces.localhost default boots fine; required for production sending, where it needs an MX record pointing to your server.
IP_POOLS_TRANSACTIONALNo127.0.0.1Comma-separated IPs for transactional email delivery.
IP_POOLS_CAMPAIGNNo127.0.0.1Comma-separated IPs for campaign/marketing email delivery.
DKIM_KEYSNo{}DKIM signing keys as JSON. See DNS & Email Setup.
WORKER_CONCURRENCYNo50Number of parallel MTA worker threads.
MTA_LOG_LEVELNoinfoMTA log verbosity: debug, info, warn, error.

Port Overrides

All ports can be changed if the defaults conflict with existing services.

VariableDefaultService
CONVEX_PORT3210Convex backend API
CONVEX_SITE_PORT3211Convex HTTP actions
DASHBOARD_PORT6791Convex dashboard (requires --profile dashboard; bound to 127.0.0.1 only — reach via SSH tunnel)
WEB_PORT3000Web application
MTA_HTTP_PORT3100MTA HTTP API
MTA_SMTP_PORT25MTA SMTP (bounce processing)

The Convex, MTA HTTP, and dashboard ports default to localhost-only binding (127.0.0.1). CONVEX_BIND (default 127.0.0.1) controls the interface the Convex backend binds to, and REDIS_PASSWORD sets the Redis auth password.

Redis and ClamAV publish no host port — they are reached only over the internal Docker network (redis:6379, clamav:3310), so there is no REDIS_PORT/CLAMAV_PORT host-override knob. (CLAMAV_PORT is set on the MTA container as the internal address its scan client dials; it does not map a host port.)

Analytics (Optional)

VariableDefaultDescription
NUXT_PUBLIC_POSTHOG_API_KEYPostHog project API key for client-side tracking.
NUXT_PUBLIC_POSTHOG_HOSThttps://eu.i.posthog.comPostHog instance URL.

Deployment Mode

These control which UI the web app shows and whether in-app updates can run. The defaults match a self-hosted install — you rarely need to change them.

VariableDefaultDescription
OWLAT_DEPLOYMENT_MODEselfhostRead by the web app (config.public.deploymentMode). Controls the self-host onboarding banner. Leave as selfhost for an OSS deploy.
OWLAT_INSTALL_DIR./Absolute path to this repo on the host. The updater sidecar mounts it to write compose templates and run docker compose pull && up -d. Required for in-app updates.
OWLAT_HOSTED_MODE is OSS-inert

The owlat setup CLI writes an OWLAT_HOSTED_MODE value into .env, but no service in this OSS repo reads it — the hosted-cloud control plane (and its billing) was extracted to a separate private repo. Setting it has no effect on a self-hosted instance.

Image Pinning (Optional)

The compose file pins upstream images to minor versions via dedicated *_VERSION vars, each with a hard-coded default. Override only to pin a different version; production release compose files further lock these to @sha256 digests.

VariableDefaultImage
CONVEX_BACKEND_VERSIONlatestghcr.io/get-convex/convex-backend
CONVEX_DASHBOARD_VERSIONlatestghcr.io/get-convex/convex-dashboard
REDIS_VERSION7.4-alpineredis
CLAMAV_VERSIONstableclamav/clamav
CADDY_VERSION2.8-alpinecaddy (only used with --profile tls)

Owlat's own images (web, mta, updater, convex-deploy, code-worker) are tagged with OWLAT_VERSION — the image pull tag defaults to latest, while the in-container build arg defaults to the dev local-build sentinel. The release pipeline writes the pinned tag, so do not edit OWLAT_VERSION, OWLAT_GIT_SHA, or OWLAT_BUILD_DATE by hand.

Convex Environment Variables

These are set after deploying functions. They control application-level behavior inside the Convex backend.

npx convex env set VAR_NAME "value" \
  --url http://localhost:3210 \
  --admin-key <your-admin-key>

Set These for Normal Operation

Set all of these on any real deployment. In code, most are read with sensible fallbacks and won't crash a function if missing — but auth redirects, unsubscribe links, and CORS will be wrong without them.

VariableDescription
SITE_URLPublic site URL for auth redirects (e.g., http://localhost:3000). Falls back to http://localhost:3000.
BETTER_AUTH_SECRETSecret for signing auth sessions. Generate with openssl rand -base64 32.
UNSUBSCRIBE_SECRETHMAC secret for signed unsubscribe tokens. Generate with openssl rand -base64 32.
EMAIL_PROVIDEREmail provider: mta (default), ses, or resend.
Do not set CONVEX_SITE_URL via `convex env set`

CONVEX_SITE_URL (used for tracking pixels and unsubscribe links) is a Convex built-inconvex env set rejects it with EnvVarNameForbidden. The backend derives it from CONVEX_SITE_ORIGIN on the convex container, which docker-compose interpolates from NUXT_PUBLIC_CONVEX_SITE_URL in your .env. Set the public URL there, not as a Convex function env var.

The only hard-required pair

MTA_API_URL and MTA_API_KEY are the only variables that hard-throw — and only when an email is actually sent through the MTA provider (auth invites, campaigns, transactional). Sending fails with No system email transport configured if either is unset (apps/api/convex/systemMail.ts; the MTA provider itself lives in apps/api/convex/lib/sendProviders/mta/index.ts). Set both whenever EMAIL_PROVIDER=mta.

VariableDescription
MTA_API_URLMTA service URL. Use http://mta:3100 for Docker networking.
MTA_API_KEYMust match the MTA_API_KEY in Docker .env.
MTA_WEBHOOK_SECRETMust match the MTA_WEBHOOK_SECRET in Docker .env.

Sender Identity

VariableDefaultDescription
DEFAULT_FROM_EMAILnoreply@example.comDefault sender email address.
DEFAULT_FROM_NAMEOwlatDefault sender display name.
DEFAULT_FROM_DOMAINmail.owlat.appDomain for system emails (invitations, etc.).
ALLOWED_ORIGINSComma-separated CORS origins (e.g., http://localhost:3000,http://localhost:3001).

LLM Provider (Optional)

Owlat talks to a single pluggable LLM provider (ADR-007). The same config powers AI agent replies, translations, knowledge-graph embeddings, and the visualization agent. Anything that speaks the OpenAI Chat Completions / Embeddings shape works (OpenAI, OpenRouter, Ollama, local vLLM/LM Studio). Only set these if you turn on an AI feature flag.

VariableDefaultDescription
LLM_PROVIDERopenaiopenai, openrouter, or ollama. Selects the client and a default base URL.
LLM_API_KEYProvider API key. Not required for ollama.
LLM_BASE_URLprovider defaultOverride the endpoint (for self-hosted/local servers). OpenRouter and Ollama get sensible defaults automatically.
LLM_MODEL_FASTgpt-4o-miniModel for fast tasks (classify/extract/guard/summarize).
LLM_MODEL_CAPABLEgpt-4oModel for capable tasks (draft).
LLM_EMBEDDING_MODELtext-embedding-3-smallEmbedding model for the knowledge graph.
Back-compat key aliases

OPENAI_API_KEY and OPENROUTER_API_KEY are accepted as fall-back aliases for LLM_API_KEY (precedence: LLM_API_KEYOPENROUTER_API_KEYOPENAI_API_KEY). Prefer LLM_API_KEY on new installs (apps/api/convex/lib/llmProvider.ts).

Running Claude models

LLM_PROVIDER accepts only openai, openrouter, and ollama. To run Claude models, use the OpenAI-compatible path: set LLM_PROVIDER=openai (or leave the default), point LLM_BASE_URL at an OpenAI-compatible endpoint, and set LLM_MODEL_CAPABLE / LLM_MODEL_FAST to the Claude model IDs.

Other Integrations (Optional)

VariableDescription
GOOGLE_SAFE_BROWSING_API_KEYGoogle Safe Browsing API v4 key for URL reputation checking.
MTA_INTERNAL_URLPreferred in-cluster MTA base URL used for all Convex→MTA HTTP calls (outbound send, cache push, delivery hooks, attachment scan); overrides the public MTA_API_URL. e.g. http://mta:3100.
MAIL_SYNC_API_URLMail-sync worker URL for external IMAP/SMTP accounts. Without it, an outbound external send is marked failed (errorCode EXTERNAL_NOT_CONFIGURED) on every recipient rather than dispatched.
MAIL_SYNC_API_KEYShared secret for the mail-sync worker.
POSTHOG_API_KEYPostHog API key for server-side analytics.
POSTHOG_HOSTPostHog instance URL (default: https://eu.i.posthog.com).

For the complete variable reference including AWS SES, Resend, and inbound-channel webhooks, see Environment Variables.

Service Topology

ServicePortsDepends OnHealthcheck
convex3210 (API), 3211 (site proxy)curl -f http://localhost:3210/version every 15s
convex-dashboard6791 (127.0.0.1 only)convex (healthy)— (dashboard profile; reach via SSH tunnel ssh -L 6791:127.0.0.1:6791 host)
web3000convex (healthy)
mta3100 (HTTP), 25 (SMTP)redis (healthy), convex (healthy), clamav (started, optional)
redis6379redis-cli ping every 10s
clamav3310clamdcheck every 60s (600s start delay) (clamav profile; off unless file scanning is enabled)
updater3200 (internal only)docker-socket-proxy
convex-deployconvex (healthy)One-shot (deploy profile)
caddy80, 443web, convex, mta— (tls profile)
code-worker— (internal only)convex (healthy)— (inbox-codetasks profile; also dev)
imap993convex (healthy), imap-cert-init (completed)— (personal-mail profile)
mail-sync— (internal only)convex (healthy)— (external-mail profile)
ollama— (internal only)— (ai / dev profiles)

The updater sidecar has no public port — the web app reaches it at http://updater:3200 over the Docker network. It mounts only OWLAT_INSTALL_DIR read-write so it can write a new compose template, and reaches the Docker API through the least-privilege docker-socket-proxy sidecar (which mounts the socket read-only) via DOCKER_HOST=tcp://docker-socket-proxy:2375 — it no longer mounts the Docker socket directly.

The optional caddy service (--profile tls) is a reverse proxy that terminates TLS via Let's Encrypt and fronts web, convex, and mta on subdomains. Edit Caddyfile to match your domains before bringing it up, or front the stack with your own reverse proxy instead.

The code-worker sidecar (--profile inbox-codetasks, also available via --profile dev) is the AI coding agent that picks up code tasks. It reads the LLM_* and GITHUB_* variables and is off by default.

Volumes

VolumePersistsBackup Priority
convex-dataDatabase, file storage, vector indexesCritical — all application data
redis-dataMTA job queue (AOF)Medium — in-flight email jobs
clamav-dataVirus definition signaturesLow — re-downloads automatically
caddy-data / caddy-configTLS certificates and Caddy state (only with --profile tls)Low — certificates re-issue automatically
code-workspaceCode-worker checkout (only with --profile inbox-codetasks or --profile dev)Low — ephemeral working tree
mail-certsIMAP server TLS certificates (only with --profile personal-mail)Low — certificates re-issue automatically
ollama-dataOllama model files (only with --profile ai / --profile dev)Low — models re-download automatically