Everything you need to install, configure and run iot bees on your own infrastructure.
iot bees is a self-hosted platform that connects IoT message brokers (RabbitMQ, MQTT, Kafka) to time-series and file storage (InfluxDB, local logs) through configurable, schema-validated pipelines. You configure everything from a web UI; iot bees handles the connections, validation, transforms and persistence in a single Rust binary.
The point: stop writing one-off services every time a new sensor shows up. Define a source, a schema, and a store, glue them together as a pipeline, hit start.
/swagger-ui/.The fastest path from zero to a running pipeline, assuming the backend and the web UI are already up.
iot bees ships without any user. The very first time you visit the app, the login screen offers to create the admin account for you. Once that account exists, open registration is disabled — only the admin can run the instance.
http://localhost:3000/login (or whatever URL your deployment is at). The frontend asks the backend GET /auth/has-users; if it comes back as false, the form switches to create admin account.admin@your-company.com), name (the label shown in the top right of the app), password (8 characters minimum — longer is better; this is a self-hosted instance and you set the security bar).+ CREATE ADMIN. The backend hashes the password with Argon2id, stores the row, and signs a JWT. The frontend writes the JWT into an HttpOnly session cookie and redirects you to /app./login show the regular login form — the backend remembers a user already exists and rejects further registration with 403 RegistrationDisabled. You can log out via the dropdown in the top right.users table in your SQLite file (or delete data/iot-bee.db entirely if you have nothing else to keep), restart, and create a fresh admin.Once you're inside /app, the overview page shows a setup checklist with five cards. Click each one in order — the page tracks which ones are done and hides the checklist when everything is set up.
/app the setup checklist takes over./sources → + NEW SOURCE. Pick a type (RabbitMQ, MQTT or Kafka), fill the connection details, save./stores → + NEW STORE. Pick LOCAL_LOG for the easiest demo, or INFLUX_DB if you have one running./schemas → + NEW SCHEMA. Use the load example button to bootstrap a 3-field schema you can edit./pipelines → + NEW PIPELINE. The 5-step wizard walks you through name → source → schema → store → replicas. Save, then click ▸ start. The status pill flips to RUNNING within 5 seconds.⌘K (or Ctrl+K) to jump anywhere or create anything without using the menu.pnpm for the web UI: npm i -g pnpm.LOCAL_LOG while you experiment.$ git clone https://github.com/manuelmj/iot-bee.git $ cd iot-bee $ mkdir -p data $ cp .env.example .env
Migrations are bundled into the binary; on first start it applies any pending migration to the SQLite database automatically. If you want to apply them manually:
$ cargo install sqlx-cli --features sqlite $ sqlx migrate run --database-url sqlite://data/iot-bee.db
$ JWT_SECRET=change-me-to-a-long-random-string make run # equivalent to: cargo fmt && cargo check && RUST_LOG=info cargo run
The HTTP server starts at http://127.0.0.1:8080. Swagger UI lives at /swagger-ui/.
$ cd web $ cp .env.local.example .env.local $ pnpm install $ pnpm dev # http://localhost:3000
Open http://localhost:3000. The first visit to /login on a fresh database will offer to create the admin account.
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL | yes | — | SQLite connection string. Example: sqlite://data/iot-bee.db |
JWT_SECRET | yes | — | HS256 signing secret. Use a long random value in production. |
JWT_EXPIRES_IN_HOURS | no | 24 | Access token lifetime in hours. |
CORS_ORIGINS | no | http://localhost:3000 | Comma-separated list of origins allowed to call the API. |
API_HOST | no | 127.0.0.1 | Bind address. |
API_PORT | no | 8080 | Bind port. |
RUST_LOG | no | info | Tracing filter. Try iot_bee=debug for more detail. |
| Variable | Default | Description |
|---|---|---|
NEXT_PUBLIC_API_URL | http://localhost:8080 | Backend URL surfaced in the footer / status indicator. |
INTERNAL_API_URL | http://localhost:8080 | Backend URL used by Next route handlers (server-side). |
AUTH_COOKIE_NAME | iot_bee_session | Name of the HttpOnly session cookie. |
AUTH_COOKIE_MAX_AGE_HOURS | 24 | Cookie lifetime, must match JWT_EXPIRES_IN_HOURS. |
Five resources make up an iot bees deployment. Get familiar with them — every UI page maps to one.
A connection to a message broker. Each source has a name, a type (RABBIT_MQ, MQTT or KAFKA), and a config object with the broker-specific fields (host, queue/topic, group_id, etc.).
Defines the contract of a message: which fields to expect, their type, range, default value and (optionally) a transformation expression. Messages that don't match the schema are rejected; matching messages get cleaned and forwarded.
{
"name": "environmental_station",
"schema": {
"temperature": {
"type": "float",
"required": true,
"default": null,
"validation": { "min": -40, "max": 85 },
"operation": null
},
"humidity": {
"type": "float",
"required": true,
"validation": { "min": 0, "max": 100 },
"operation": null
}
}
}The operation field is an arithmetic AST: num, var and bin_op nodes (Add, Sub, Mul, Div) that runs after validation. Use it for unit conversion, calibration, etc.
Where validated records land. Two persistence types:
INFLUX_DB — writes to a database + measurement. String fields listed in tag_fields become InfluxDB tags; everything else becomes fields.LOCAL_LOG — appends newline-delimited JSON to a file. Great for local dev and small deployments.Wires one source + one schema + one store. Has a name, a replication count (number of concurrent worker actors), and an optional group_id. A pipeline is the thing you start and stop at runtime.
Optional logical container. Use groups to organise pipelines by site, customer, or environment.
Pipelines live in three states:
RUNNING — actor hierarchy is active and consuming.STOPPED — pipeline exists in DB but isn't consuming.ERROR — pipeline crashed or failed to start; check the backend logs.Visit /login. If the database has no users, the form shows create admin account. Fill email + name + password (≥ 8 chars), submit. You land on /app with an HttpOnly session cookie.
The top bar has six tabs:
On mobile the tabs collapse into a full-screen navigation overlay. Your user badge sits in the top-right with a dropdown showing email, role and sign-out.
⌘K (or Ctrl+K) opens a command palette to navigate or create resources without touching the menu. On mobile, the command bar collapses to a floating ⌘ button at the bottom-right.
The schema editor is a raw-JSON editor with a live preview panel. The load example button bootstraps a working three-field schema you can adapt. Click save; the preview updates as you type.
{ "type": "bin_op", "op": "Mul", "left": ..., "right": ... }). A visual editor for the full grammar is out of scope; raw JSON keeps the door open to every backend feature.Every page in the web app is backed by a REST endpoint. Interactive Swagger UI is served at http://127.0.0.1:8080/swagger-ui/.
All endpoints (except /auth/* and /swagger-ui/*) require an HS256 JWT in the Authorization header.
$ curl -X POST http://127.0.0.1:8080/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@iot-bee.dev","password":"iotbee2026"}'
# response:
{
"user": { "id": 1, "email": "...", "name": "...", "role": "admin" },
"token": "eyJ0eXAi..."
}
$ curl http://127.0.0.1:8080/data-sources \
-H "Authorization: Bearer eyJ0eXAi..."| Resource | Base path | Description |
|---|---|---|
| Auth | /auth | Login, first-admin register, has-users probe, current user. |
| Connection Types | /connection-types | Read-only list of supported source/store identifiers. |
| Data Sources | /data-sources | CRUD for broker connections. |
| Data Stores | /data-stores | CRUD for persistence destinations. |
| Validation Schemas | /validation-schemas | CRUD for schemas; PUT /{id}/name for rename. |
| Pipeline Groups | /pipeline-groups | Create / list / delete logical groups. |
| Pipelines | /pipelines | CRUD plus PUT endpoints for relation assignments. |
| Pipeline Lifecycle | /pipeline-lifecycle | Start, stop, status (single + all). |
Failures return JSON of the shape { "error": "<message>" } with the appropriate HTTP status (400, 401, 403, 404, 409, 500).
Hexagonal layering with a top-level supervisor that owns one supervisor per pipeline. Each pipeline runs N replicas. Each replica is a chain of three actors (consumer → processor → store) connected through MPSC channels.
SystemActorSupervisor
└─ PipelineSupervisor (one per pipeline)
├─ Replica 1: [consumer] → [processor] → [store]
├─ Replica 2: [consumer] → [processor] → [store]
└─ Replica N: [consumer] → [processor] → [store]crates/ ├── domain/ pure entities, ports and error types ├── application/ use cases (one module per aggregate) ├── infrastructure/ SQLite repos, broker drivers, security (argon2 + jwt) ├── adapters/ Actix HTTP handlers + actor system └── logging/ tracing wrapper
If a single replica crashes, only that replica is restarted — the rest of the pipeline keeps streaming. Pipelines are isolated from each other; a runaway pipeline cannot take down the others or the HTTP server.
The frontend and backend are intentionally decoupled — they only talk over HTTP. You can host them anywhere; the recommended split is the frontend on Vercel and the backend on a server you control (VPS, fly.io, Railway, your own metal).
Browser
└─ Next.js (Vercel) ← serves landing, login, app, /api/proxy
└─ HTTP ← server-to-server, with Bearer
└─ iot-bee backend ← Rust binary on YOUR serverThe browser never calls the backend directly. All API calls go through a thin Next route handler at /api/proxy/[...path] that translates the HttpOnly session cookie into an Authorization: Bearer header. Three consequences:
Build a release binary on the target machine (or in a CI pipeline) and run it under a process supervisor of your choice (systemd, supervisord, fly.io machines, etc.).
# on the server $ cargo build --release $ ./target/release/iot-bee # listens on $API_HOST:$API_PORT
Required env on the backend:
| Variable | Example value |
|---|---|
DATABASE_URL | sqlite:///var/lib/iot-bee/data.db |
JWT_SECRET | <output of `openssl rand -hex 32`> |
CORS_ORIGINS | https://iotbees.com |
API_HOST | 0.0.0.0 |
API_PORT | 8080 |
The web/ directory is a standard Next.js 15 project — no vercel.json required.
web. Framework auto-detects.INTERNAL_API_URL and NEXT_PUBLIC_API_URL as required for both Production and Preview environments.CORS_ORIGINS (e.g. https://iotbees.com). Restart the backend so the new value takes effect./login on a fresh backend will offer to create the admin account.| Vercel env var | Value |
|---|---|
NEXT_PUBLIC_API_URL | https://api.your-domain.com |
INTERNAL_API_URL | https://api.your-domain.com |
AUTH_COOKIE_NAME | iot_bee_session |
AUTH_COOKIE_MAX_AGE_HOURS | 24 (must match backend JWT_EXPIRES_IN_HOURS) |
NEXT_PUBLIC_* is baked at build time — change → redeploy.https://<vercel-url>/auth/has-users via the Next proxy isn't public — instead curl your backend directly and check it responds with { "has_users": false }./login and create the admin. The redirect to /app proves the cookie + proxy chain works end-to-end.Backend ONLINE.The backend fails fast if JWT_SECRET is missing. Set it in your shell or in .env:
$ export JWT_SECRET="$(openssl rand -hex 32)"
This means the backend reports zero users. If you registered successfully and you still see the create-admin form, refresh; the page calls GET /auth/has-users on mount. If the form keeps appearing, your database is empty — check that the backend is talking to the same DATABASE_URL you registered against.
Add the front-end origin to CORS_ORIGINS on the backend and restart. In dev the default already includes http://localhost:3000.
Most often this means the broker connection failed or the schema doesn't parse the incoming payload. Tail the backend logs with RUST_LOG=iot_bee=debug to see the exact reason; the actor reports the error to the supervisor before transitioning state.
Open an issue at github.com/manuelmj/iot-bee/issues with the relevant log snippet, your config (redact secrets!), and the steps to reproduce.