Development Setup

The development stack mounts your local source directories into the containers and enables hot reload for both the backend and the frontend, so changes are visible immediately without rebuilding images.

Prerequisites

1. Configure the environment

Copy the environment template (if you haven't already):

cp .env.example .env

The dev compose file sets SECRET_KEY=dev-secret-key and DEBUG=true automatically, so no extra changes are required for local development.

2. Start the dev stack

docker compose -f docker-compose.dev.yml up --build

Open http://localhost (or http://localhost:<NGINX_PORT> if you set a custom port).

3. Apply database migrations

docker compose -f docker-compose.dev.yml exec backend alembic upgrade head

4. Create the first admin user

docker compose -f docker-compose.dev.yml exec -it backend python cli.py users add-admin

The command prompts for a name, email address, and password, then creates the account with admin, researcher, and annotator roles assigned.

How it works

The dev compose file (docker-compose.dev.yml) differs from production in three important ways.

Backend — hot reload

The ./backend directory is mounted into the container at /app and the server starts with uvicorn --reload, so any change to a Python file restarts the server automatically.

Frontend — Vite HMR

The frontend service uses the official node:20-alpine image (no custom Dockerfile). The ./frontend source directory is mounted into the container and npm run dev starts the Vite dev server with Hot Module Replacement. Node modules are stored in a named volume (frontend_node_modules) so they survive container restarts without being re-installed each time.

HMR events are proxied through nginx. The VITE_HMR_CLIENT_PORT variable is set to the same value as NGINX_PORT so the browser WebSocket connects to the right port.

Plugins — live editing

The plugins/ directory is mounted read-write (without the :ro flag used in production). When the backend reloads, it picks up changes to plugin Python files automatically. New local plugins can be installed without restarting via the admin UI: Admin → Plugins → Local Plugins → Install.

Database

The dev stack uses its own named volume (postgres_data_dev) so it does not share data with a production volume on the same host.

Running CLI commands in dev

Prefix every docker compose command with -f docker-compose.dev.yml:

docker compose -f docker-compose.dev.yml exec backend python cli.py users list
docker compose -f docker-compose.dev.yml exec backend alembic upgrade head

Useful shortcuts

TaskCommand
View backend logsdocker compose -f docker-compose.dev.yml logs -f backend
Open a Python shelldocker compose -f docker-compose.dev.yml exec backend python
Run backend testsdocker compose -f docker-compose.dev.yml exec backend pytest
Reset the dev databasedocker compose -f docker-compose.dev.yml exec backend python cli.py database reset

Environment variables

The dev compose file sets these values directly (no .env entry required):

VariableDev value
DATABASE_URLpostgresql+psycopg2://workbench:workbench@db:5432/llm_workbench
SECRET_KEYdev-secret-key
DEBUGtrue
VITE_API_URL(empty — same-origin requests through nginx)
VITE_HMR_CLIENT_PORTvalue of NGINX_PORT (default 80)