Development Setup¶
Set up a local development environment where the app processes run directly (with hot reload) while Docker provides the shared infrastructure.
For the all-Docker experience instead, see Self-Hosting.
Prerequisites¶
- Git, Docker and Docker Compose
- Python 3.10 or 3.11 for the backend and ingest worker.
- Node.js 20.19+ or 22.12+ for the frontend.
apps/frontend/.nvmrcpins v22.12.0 (nvm usepicks it up); CI runs Node 20. - An OpenAI-compatible embedding endpoint, for example Ollama serving
Qwen/Qwen3-Embedding-8B; ingest and retrieval need one, chat models do not have to be configured up front.
Quick start¶
1. Clone the repository and configure the environment¶
git clone https://github.com/Center-for-AI-Innovation/Illinois-Chat.git
cd Illinois-Chat
cp .env.template .env
One value in .env must be set before anything starts:
SIM_APPROVAL_ADMIN_EMAIL— your email. It names the account that becomes the Sim platform admin, and the start script refuses to run while it is empty. Leave it empty only if you will start with--no-sim.
Everything else in the root .env can stay as shipped for local work: the start script falls back to the dev defaults (postgres/password, minioadmin, admin) for anything unset, and generates ENCRYPTION_MASTER_KEY and the SIM_* secrets on the first run. The model and embedding values are set in step 4, in the app env files, not here. The full variable list is in the Configuration reference.
2. Install packages¶
cd apps/backend
python3.11 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
pip install -r ai_ta_backend/rabbitmq/requirements.txt
The first file is the Flask backend's dependencies, the second the ingest worker's. Activate the virtual environment (source venv/bin/activate) in every terminal that runs the backend or the worker.
3. Start the infrastructure¶
On the first run (or whenever the database volume is empty), pass --create-schema:
Without the flag, an empty database stops the script with Database is empty. Re-run with --create-schema. The script exits before starting anything if SIM_APPROVAL_ADMIN_EMAIL is unset (unless --no-sim is given) or if SIM_SSO_DOMAIN is not a single domain.
This script:
- generates
ENCRYPTION_MASTER_KEYand theSIM_*secrets into.envwhen they are missing; - creates or updates the app-local env files (
apps/backend/.env,apps/frontend/.env,apps/crawlee/.env) without overwriting existing values; - starts the shared dev infrastructure from
infra/docker/docker-compose.dev.yaml, plus the Sim stack frominfra/docker/docker-compose.sim.yamlunless--no-simis given; - with
--create-schema, applies the Postgres schema frominfra/db/init-schema.sql; on every run it replays Drizzle migrations 0016 and 0017 (both are no-ops once applied); - ensures the configured Qdrant collection exists with 4096-dimensional cosine vectors;
- creates the object-storage
uiuc-chatbucket.
| Flag | Effect |
|---|---|
--create-schema |
Apply infra/db/init-schema.sql to an empty database. |
--clean |
Remove the dev containers and volumes first, then recreate the schema. Cannot be combined with --create-schema. |
--no-sim |
Start without the Sim AI tool stack. |
-h, --help |
Show usage. |
4. Set the model values in the app env files¶
In development the compose file only runs infrastructure; each app reads its own env file, which the script has just written:
apps/backend/.env— Flask backend and ingest workerapps/frontend/.env— Next.js frontend (npm run local)apps/crawlee/.env— Crawlee, if you run it
They already hold the connection values for Postgres (app and Keycloak databases), Redis, RabbitMQ, Qdrant and object storage, the Keycloak realm and client, ENCRYPTION_MASTER_KEY and ALLOWED_EMBEDDING_PROVIDERS. The model keys are written empty, and the script does not copy the root .env values into them, so set these in both apps/backend/.env and apps/frontend/.env, with the same values:
| Key | Set it to |
|---|---|
EMBEDDING_MODEL |
Qwen/Qwen3-Embedding-8B, or another model that produces 4096-dimensional vectors (the embeddings column is vector(4096)). |
EMBEDDING_API_BASE |
The OpenAI-compatible endpoint serving it, e.g. http://localhost:11434/v1 for Ollama on the host. |
NCSA_HOSTED_API_KEY or OPENAI_API_KEY |
Only if that endpoint needs a bearer token; either is sent. |
Left empty, both apps fall back to OpenAI's text-embedding-ada-002 at https://api.openai.com/v1, whose 1536-dimensional vectors do not fit the column, so every ingest fails. Re-running the script later only appends keys that are missing, so your values survive.
Optional but worth setting now: NEXT_PUBLIC_SIGNING_KEY in apps/frontend/.env (provider keys that chatbot owners paste on the LLMs page are stored in plaintext without it) and SUPER_ADMIN_EMAILS (your email, to see every chatbot). Chat models themselves are configured per chatbot on its LLMs page; OLLAMA_SERVER_URL and NCSA_HOSTED_API_KEY only make the Ollama and NCSA providers available there. The apps/backend/.env.template and apps/frontend/.env.template files are dev reference files that no script reads.
For uploads and ingest, always use the object-storage API port, not the console port (the dev stack runs Silo, a MinIO-compatible server, as the compose service minio):
# apps/frontend/.env
MINIO_ENDPOINT=http://localhost:10000
MINIO_PUBLIC_ENDPOINT=http://localhost:10000
NEXT_PUBLIC_S3_ENDPOINT=http://localhost:10000
# apps/backend/.env
MINIO_URL=http://localhost:10000
MINIO_ENDPOINT=http://localhost:10000
MINIO_PUBLIC_ENDPOINT=http://localhost:10000
http://localhost:9001 is the object-storage console and must not be used for S3 uploads.
5. Run the apps¶
Run each in its own terminal:
Open http://localhost:3000 and sign in. The local Keycloak realm (illinois_chat_realm, admin console at http://localhost:8080) ships with no users and allows self-registration, so register an account on the sign-in page, then create your first chatbot.
Services overview¶
Apps run on the host; everything else is a container from docker-compose.dev.yaml (and docker-compose.sim.yaml).
| Service | Host port(s) | Description |
|---|---|---|
| Frontend | 3000 | Next.js application (npm run local) |
| Backend API | 8000 | Flask API (flask run) |
| Keycloak | 8080 | Authentication service |
| Postgres (app) | 5432 | pgvector/pgvector:pg17, service postgres-illinois-chat |
| Postgres (Keycloak) | 5433 | postgres:18, service postgres-keycloak |
| Redis | 6379 | Project and user metadata |
| Qdrant | 6333, 6334 | Optional per-chatbot vector store |
| Object storage (Silo) | 10000 (API), 9001 (console) | Service minio |
| RabbitMQ | 5672, 15672 (management UI) | Ingest queue |
| drizzle-gateway | 4983 | Drizzle Studio for the app database |
| Sim AI | 3010 (app), 3011 (realtime), 55432 (Sim DB) | Tool platform; skipped with --no-sim |
Database configuration¶
PostgreSQL is recommended:
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_ENDPOINT=localhost
POSTGRES_PORT=5432
POSTGRES_DATABASE=postgres
SQLite is available as a lightweight alternative for the Flask backend and ingest worker only; the frontend needs Postgres:
Schema changes (Drizzle)¶
The app schema is owned by the frontend's Drizzle definition, apps/frontend/src/db/schema.ts, with migrations in apps/frontend/src/db/migrations. From apps/frontend:
npm run db:generate # write a new migration from schema.ts
npm run db:migrate # apply pending migrations to the database in .env
npm run db:studio # browse the database
infra/db/init-schema.sql is derived from the Drizzle schema and is what --create-schema applies to an empty database; keep it in step with new migrations. The start scripts replay only migrations 0016 and 0017 on boot.
Linting and PRs¶
Linting is enforced with Trunk (npm exec trunk check in the frontend, or the trunk CLI). What pull requests run, and how to run the same checks locally, is on Testing & CI.
Troubleshooting¶
Database connection issues
- Ensure PostgreSQL is running:
docker ps | grep postgres - Check accessibility:
docker compose --project-directory . -f infra/docker/docker-compose.dev.yaml exec postgres-illinois-chat pg_isready -U postgres
Port conflicts
- Modify port mappings in
infra/docker/docker-compose.dev.yamland update the corresponding.envvalues.
Missing dependencies
- Backend: activate the virtual environment and re-run both
pip install -rcommands inapps/backend. - Frontend:
npm ciinapps/frontend.
Environment variables
- Re-run
bash infra/scripts/start-dev.shto create or append missing local env keys. - Fill hosted model/API values in the app-local
.envfiles when you need non-local services.
Stack-level symptoms (Database is empty, the SIM_APPROVAL_ADMIN_EMAIL exit, an old Postgres volume) are on Troubleshooting.