Development Startup
This page describes the most common local development setup with three processes: EMQX started by Docker, the FastAPI backend started on the host machine, and the Vite frontend started on the host machine. For full Docker deployment, see Docker Deploy.
Version Requirements
Local development should stay aligned with the repository and Docker build environment:
| Component | Version / Constraint | Notes |
|---|---|---|
| Python | >=3.12 | Required by backend src/pyproject.toml. The recommended local setup is a conda environment such as wfm. |
| Node.js | 22.x | Frontend Docker build uses node:22-alpine. |
| pnpm | 10.33.0 | Docker build pins this version. |
| EMQX | 5.8.5 | Docker Compose uses emqx/emqx:5.8.5. |
Dependency installation changes the local environment and should be done manually by the maintainer. Automation helpers must not install, upgrade, or remove dependencies on behalf of the maintainer.
Startup Order
Start the development environment in this order:
- Start EMQX with Docker.
- Start the backend with
uvicorn. - Start the frontend with
pnpm run dev.
1. Start EMQX
Reuse the EMQX configuration from the SQLite deployment directory:
cd docker/sqlite
cp .env.example .envChange the callback URL to the host machine:
WFM_EMQX_AUTHZ_URL=http://host.docker.internal:8000/api/internal/emqx/authzUncomment the EMQX Dashboard/API port 18083 in the compose file, otherwise EMQX callback and management features cannot work correctly during local development.
Start only EMQX:
docker compose up -d emqx2. Start the Backend
The backend is started from src/ and reads src/.env.
Enter the backend directory:
cd src
python -m pip install -e .[dev]
cp .env.example .envNotes:
WFM_ENABLE_DEV_TEST_API=trueregisters/api/v0and relaxes production source checks. Use it only for development.WFM_EXTRA_ALLOWED_ORIGINSmust include the Vite frontend origin, otherwise browser requests from5173are rejected by source checks.WFM_MQTT_URLis the broker URL used by the backend. When the backend runs on the host and EMQX runs in Docker, use127.0.0.1:1883.WFM_EMQX_API_BASE_URLis the EMQX Dashboard/API URL used by the backend. This requires port18083to be exposed for development.WFM_PUBLIC_ORIGINaffects client downloads, bind commands, and some public URLs. For frontend local development, it usually points to the backend address.
Start with uvicorn:
python -m uvicorn app.main:app --host 127.0.0.1 --port 8000 --reload --reload-exclude data --timeout-graceful-shutdown 1Common backend URLs:
- API docs:
http://127.0.0.1:8000/docs - Health check:
http://127.0.0.1:8000/api/v1/system/health - SSE:
http://127.0.0.1:8000/api/v1/events/stream
3. Start the Frontend
The frontend uses Vite:
cd front
pnpm run devOpen:
http://127.0.0.1:5173When the frontend runs on 5173, API and SSE requests default to the same host on port 8000:
http://127.0.0.1:8000If you need to explicitly set the backend URL before starting the frontend:
export VITE_API_BASE_URL='http://127.0.0.1:8000'
pnpm run devThe frontend does not use Vite proxy. Cross-origin access during development is controlled by backend source settings.
Common Issues
The frontend opens, but API requests are rejected
Check src/.env first:
WFM_ENABLE_DEV_TEST_API=true
WFM_EXTRA_ALLOWED_ORIGINS=["http://127.0.0.1:5173","http://localhost:5173"]If you access the frontend from a phone or another LAN device, add that origin to WFM_EXTRA_ALLOWED_ORIGINS and make sure the backend Host restriction allows that access path.
The backend reports MQTT or EMQX errors
Check in order:
- Whether the EMQX container is running.
- Whether
1883is mapped to the host. - Whether
18083is temporarily exposed for development. - Whether
src/.envanddocker/sqlite/.envuse the same EMQX username, password, and AuthZ shared key. - Whether
docker/sqlite/.envpointsWFM_EMQX_AUTHZ_URLtohttp://host.docker.internal:8000/api/internal/emqx/authz.
Backend reload keeps restarting
Confirm the startup command includes:
--reload-exclude datasrc/data is the local runtime data directory. It stores the database, download artifacts, bulk config packages, and snapshots. Changes there should not trigger backend reloads.
