KESTREL docs
v1.0.0
Get help
● Set up · .env reference

Configuration
.env for the infrastructure, the console for the rest.

Every environment variable the server reads, what it does, its default and where the value comes from. Most product settings can also be changed in the operator console without a restart.

.envserver/.env.exampleserver/.env.ai.example

01Where settings live

PlaceWhat goes thereHow a change applies
.env in the kestrel folder (Docker install)Everything below. install.sh writes it; docker-compose.yml passes it to the server (env_file: .env).Edit, then docker compose up -d. A change of PUBLIC_URL needs ./update.sh, because the web app is rebuilt with the new address.
server/.env (developer setup)The same variables, for a server you run with Node.jsRestart the server
Operator console /adminBrand, legal, access, tenancy, features, plans and limits, broker ids, AI, delivery, integrations, notificationsAt once, no restart. A value saved in the console wins over .env (Operator console).

The reference files in the package are .env.example (Docker), server/.env.example and server/.env.ai.example. In an .env file, KEY= with nothing after it means "not set".

02Core and infrastructure

These are read once at start and stay in .env (server/src/config.ts, docker-compose.yml).

VariableDefaultWhat it does, where the value comes from
KESTREL_MASTER_KEY Requirednone32 random bytes, base64. Encrypts every stored exchange key. Create it with openssl rand -base64 32; the installer does it for you. Losing it makes every stored key unreadable (Master key). The server refuses to start without it or when it does not decode to 32 bytes.
DATABASE_URLsqlite:./data/kestrel.db (Docker: sqlite:/data/kestrel.db)sqlite:<path> for one box, or postgres://user:pass@host:5432/kestrel. Migrations run at start.
PUBLIC_URLhttp://localhost:8080The address people open, e.g. https://trade.example.com. Webhook URLs of signal bots use it, and the Docker build compiles it into the web app.
CORS_ORIGINS*Comma-separated origins allowed to call the API from a browser. Set it to your PUBLIC_URL; the installer does.
WS_ORIGINSsame as CORS_ORIGINSOrigins allowed to open the WebSocket. Apps that send no Origin header (the mobile and desktop apps) always pass.
SERVER_PUBLIC_IPemptyThe server's public IP, shown to users at /api/server-ip and in the app so they can whitelist it on the exchange. The installer detects it.
TRUST_PROXY_HOPS0 (installer: 1)Number of reverse proxies in front of the server. Caddy in the Docker install is 1. Rate limits and the audit log use the client IP found this way.
PORT8080Port the server listens on. Docker fixes it to 8080 inside the container.
RUNNER_INTERVAL_MS2000How often the Smart Trade and bot runners make a pass, in milliseconds. The watchdog calls a runner stalled after 3 intervals without a pass.
NODE_ENVproductionUsually left alone.

Docker only

VariableWhat it does
DOMAINYour domain, written by the installer and reused when you run it again.
SITE_ADDRESSThe address Caddy serves: your domain (automatic HTTPS) or :80 (plain HTTP, no domain).
ACME_EMAILEmail given to the certificate authority for expiry notices.
COMPOSE_PROFILESpostgres starts the PostgreSQL container.
POSTGRES_PASSWORDPassword of the PostgreSQL container, random from the installer.

03Operator console

VariableDefaultWhat it does
ADMIN_SECRET_KEYderived from KESTREL_MASTER_KEYOptional. 32 bytes as 64 hex characters (openssl rand -hex 32). Encrypts the console's stored secrets (bot token, SMTP URL, API keys). Changing it later makes those stored secrets unreadable: enter them again.
ADMIN_DEMO_PASSWORDemptyOnly on a public demo host. Makes the console read-only with the account demo@kestrel.demo and this password, and the Users page refuses every change. Never set it on a real install. ADMIN_DEMO_EMAIL and ADMIN_DEMO_NOTICE change the demo account and its banner.

04Services: Telegram, email, push

Each can be set in the console (Integrations, owner only) instead. A value saved in the console wins; these are the fallback (server/.env.example).

VariableWhat it does, where to get it
TELEGRAM_BOT_TOKENYour own bot's token, from BotFather in Telegram. Turns on Telegram notifications, channel signals and the control commands (Create the bot).
SMTP_URLsmtps://user:pass@host:465 or smtp://user:pass@host:587 (STARTTLS is required on 587). From your email provider's SMTP settings. Unset: no email.
MAIL_FROMThe sender address, e.g. KESTREL <no-reply@example.com>. Required with SMTP; the domain must be allowed to send through your provider.
FCM_SERVICE_ACCOUNT_FILEPath to the Firebase service account JSON file, for push. In Docker the path must be inside the container, so paste the JSON in the console (Integrations) instead (Push).
Cost

A Telegram bot is free. Firebase Cloud Messaging is free. SMTP providers usually have a free tier with a sending limit and charge above it.

05Branding and legal

The starting values of the console's Brand and Legal sections, published at GET /api/config.

VariableDefaultWhat it does
BRAND_NAMEKESTRELProduct name, up to 60 characters.
BRAND_ACCENT#FF6B35Accent colour, #RRGGBB.
BRAND_LOGO_URLemptyLogo URL.
LEGAL_TERMS_URL, LEGAL_PRIVACY_URLemptyYour terms of service and privacy policy.

The compiled apps take their own name, colours and icon from the app source; see White label.

06Feature switches

Everything is on by default. KESTREL_FEATURE_<NAME>=false (also 0, off, no) is the starting value of the matching switch in the console's Features section (server/src/features.ts).

Names: JOURNAL, ANALYTICS, EXPORT, ALGO, COPY, SCANNER, AI, TELEGRAM, PUSH, EMAIL, PANIC, RISK. Example: KESTREL_FEATURE_COPY=false.

A switched-off feature disappears from the app's navigation and its API routes answer 404 feature_disabled. Paper trading, Smart Trade, bots and alerts cannot be switched off. Switching off EMAIL only hides the flag: sign-in and email verification keep working.

07Copy trading, scanner, AI assistant

From server/.env.ai.example. Every one has a safe default.

VariableDefaultWhat it does
KESTREL_TENANCYself_hostedself_hosted or saas. Starting value of the console's Tenancy section (Tenancy).
COPY_INTERVAL_MS3000How often the copy runner reads masters and converges followers. Minimum 500.
SCANNER_DEFAULT_TAKER_FEE0.001Taker fee rate the spread scanner uses when a market reports none (shown as "default").
SCANNER_RATE_PER_MIN30Scanner requests per user per minute.
KESTREL_AI_PROVIDERempty (off)anthropic or openai-compatible (OpenAI, OpenRouter, Ollama, vLLM and others).
KESTREL_AI_BASE_URLthe provider's ownOptional. Defaults to https://api.anthropic.com or https://api.openai.com/v1. For a local Ollama: http://localhost:11434/v1 (from inside Docker, the address of the machine running Ollama).
KESTREL_AI_MODELemptyThe model name, as your provider writes it.
KESTREL_AI_API_KEYemptyYour provider's API key. Never logged, never returned by any endpoint. Can be set in the console (Integrations, LLM API key) instead.
KESTREL_AI_RATE_PER_HOUR20AI requests per user per hour.
KESTREL_AI_TIMEOUT_MS30000How long the server waits for the provider. Minimum 1000.

The assistant turns on only when provider, model and key are all set. Without them every AI route answers 501 ai_not_configured and the app hides the assistant.

Cost: LLM provider

Hosted models are paid per use (per token) to the provider, on your account. Limit the cost with KESTREL_AI_RATE_PER_HOUR and the per-plan AI limit. A local model through Ollama has no per-use fee.

08Changing a value

cd kestrel
nano .env                 # edit, save with Ctrl+O, exit with Ctrl+X
docker compose up -d      # recreates the server with the new values

Changed your domain? Edit PUBLIC_URL, SITE_ADDRESS, CORS_ORIGINS, DOMAIN (and ACME_EMAIL), then run ./update.sh so the web app is rebuilt for the new address.