deployment/local/, and a single script brings the whole
system up, applies database migrations, and health-checks every service.
Prerequisites
The Dockerized flow needs only one thing:- Docker Engine 24+ with the Compose v2 plugin. Verify with
docker --versionanddocker compose version.
The local frontend image is a production build (
node server.js), not next dev, so there is
no hot reload: after editing backend or frontend source you must rebuild with
docker compose up -d --build (or re-run the deploy script).Quickstart
1
Get the code and enter the local deployment
2
Create the local config files
deploy.sh copies them from the examples on first run, or do it yourself:3
Set the essentials in backend/.env
Set a long random
JWT_SECRET, an ADMIN_DEFAULT_PASSWORD (this seeds the admin login on the
first migration), and at least one LLM key (e.g. OPENAI_API_KEY). Leave RECAPTCHA_SECRET_KEY
and SMTP_HOST empty so local dev skips captcha and email verification.4
Bring the stack up
.env files, runs docker compose up -d --build, then health-checks Postgres,
the backend, and the frontend. The equivalent manual command is docker compose up -d --build.5
Log in
Open http://localhost:3000, sign in at
/login with username admin
and the ADMIN_DEFAULT_PASSWORD you set (this account is pre-verified), then open the Studio at
/app.What comes up
deploy.sh runs the Compose file at deployment/local/docker-compose.yml, which starts four services:
On first start the backend’s
entrypoint.sh waits for Postgres, runs alembic upgrade head (creating the
schema and seeding the admin user), runs python -m app.db.init_checkpointer (creating the
LangGraph checkpointer tables), and then starts uvicorn.
Configuration
Settings resolve in the order env var →.env → YAML → defaults (see backend/app/core/config.py):
backend/.env(secrets):JWT_SECRET,ADMIN_DEFAULT_PASSWORD, LLM API keys, optionalSMTP_*,RECAPTCHA_SECRET_KEY,GOOGLE_OAUTH_CLIENT_ID. Compose assemblesDATABASE_URLfrom thePOSTGRES_*values, so you do not set it directly.frontend/.env:API_PROXY_TARGET(defaulthttp://backend:8000) and theNEXT_PUBLIC_*keys, baked into the image at build time (so changing them needs a rebuild).deployment/local/backend/config.local.yaml(non-secret, tracked and shared): the default LLMprovider/model, CORS origins, andobservability.provider(noneto disable Phoenix/LangSmith tracing). Secrets never come from YAML.deployment/local/backend/config.local.override.yaml(yours, gitignored): your own settings, merged overconfig.local.yamlkey by key, so it only needs the keys you change and they can never be committed by mistake.deploy.shcreates it from the trackedconfig.local.override.example.yaml, and Compose mounts it and names it inCONFIG_OVERRIDE_PATH. Restart the backend after an edit (docker compose restart backend).- A self-hosted default model: in your override, set
llm.provider: openai, the server’s model name inllm.model, and its OpenAI-compatible address (Ollama, vLLM) inllm.endpoint_url, such ashttp://host.docker.internal:11434/v1for Ollama on your machine. If the server needs a key, put it inbackend/.envasDEFAULT_LLM_API_KEY. That address and key serve only the default model, which is what an API request that names no model runs on. Requests for any other model never go there, the Studio always sends the model picked in its menu, and the OpenAI key is never sent there.
First login and accounts
- Seeded admin is the practical way in: the first migration creates user
admin(role admin,email_verified=true) using yourADMIN_DEFAULT_PASSWORD. - Self-registration (
/login→ register) enforces email verification before login, so withoutSMTP_*configured a self-registered local user cannot log in. Use the admin account, or configure SMTP. - Captcha is skipped automatically when
RECAPTCHA_SECRET_KEYis empty.