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
- Docker 24+
- Docker Compose v2
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
| Task | Command |
|---|---|
| View backend logs | docker compose -f docker-compose.dev.yml logs -f backend |
| Open a Python shell | docker compose -f docker-compose.dev.yml exec backend python |
| Run backend tests | docker compose -f docker-compose.dev.yml exec backend pytest |
| Reset the dev database | docker 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):
| Variable | Dev value |
|---|---|
DATABASE_URL | postgresql+psycopg2://workbench:workbench@db:5432/llm_workbench |
SECRET_KEY | dev-secret-key |
DEBUG | true |
VITE_API_URL | (empty — same-origin requests through nginx) |
VITE_HMR_CLIENT_PORT | value of NGINX_PORT (default 80) |