One image for the whole app: engine, API and dashboard on one port

The backend image now builds the SPA and serves it on the API's own origin
(fluksio/spa.py), so an instance is a single container with no proxy, DNS,
port 80 or .env: `docker compose up -d` with the new compose.yml runs it on
localhost:8080. It starts on fluksio serve (persisted secret key, first
admin printed once) and brings its own Redis when REDIS_HOST is unset.

Slimmer: python:3.13-slim, no dev group, uv once, fastapi without the cloud
CLI. Optional features are per-feature extras (mqtt, influx, email, sentry,
ml, server for all) picked with the EXTRAS build argument; numpy moved to
`ml`. ffmpeg left the image for imageio-ffmpeg via the Modules screen.

The operator stack runs the same image with app. and api. routed to it; the
nginx frontend service, the LAN nginx proxy and DOMAIN in the backend are
gone, and the MCP issuer is FRONTEND_HOST.
This commit is contained in:
2026-10-16 11:40:02 +02:00
parent 9d9dac0bd0
commit dea25ec08d
39 changed files with 573 additions and 728 deletions
+7 -4
View File
@@ -5,13 +5,16 @@ and run flows with the permissions of the user who authorizes it.
## Connect an agent
Set `MCP_ENABLED=true` in the engine's environment and restart it. The Python
package defaults to off; the Docker template enables it.
Set `MCP_ENABLED=true` in the engine's environment and restart it, under
`environment` in `compose.yml` for the Docker container. It defaults to off.
Configure your MCP client with an HTTP endpoint:
- Local Docker stack: `http://api.localhost/mcp`
- Deployment: `https://api.<your-domain>/mcp`
- Docker container: `http://localhost:8080/mcp`
- Deployment: `https://<your-domain>/mcp`
The endpoint has to be the address in `FRONTEND_HOST`, which is also the
issuer of the client's tokens.
The client registers, opens browser authorization, and receives an OAuth
token after you approve access. The issuer and browser redirects use
+2 -2
View File
@@ -10,8 +10,8 @@ export FLUKSIO=http://127.0.0.1:8000/api/v1
```
Use the port printed by `fluksio serve`. For Docker, use
`http://api.localhost/api/v1`, `http://<host-ip>:8080/api/v1` on a LAN stack,
or `https://api.<your-domain>/api/v1` for a deployment.
`http://localhost:8080/api/v1`, or `https://<your-domain>/api/v1` for a
deployment.
All paths below are relative to this base. Timestamps in responses are
ISO 8601 and always carry their UTC offset.
+5 -4
View File
@@ -11,8 +11,9 @@ For the browser dashboard, sign up at [fluksio.com](https://fluksio.com), add
an instance, then run `fluksio enroll <code>` in the same directory.
Use `fluksio --help` or `fluksio COMMAND --help` for command options.
`pip install 'fluksio[server]'` adds MQTT, InfluxDB, outbound mail and error
reporting. A machine that only executes remote nodes can install
`pip install 'fluksio[server]'` adds MQTT, InfluxDB, outbound mail, error
reporting and numpy, or name only what you need, such as `fluksio[mqtt]`. A
machine that only executes remote nodes can install
[`fluksio-worker`](workers.md).
## Where an instance lives
@@ -123,7 +124,7 @@ Starts the same agent as the standalone `fluksio-worker` command:
```sh
FLUKSIO_WORKER_TOKEN=your-token fluksio worker \
--url wss://api.example.com/api/v1/workers/attach --labels gpu
--url wss://fluksio.example.com/api/v1/workers/attach --labels gpu
```
Create the worker token on the engine first. See [Remote workers](workers.md)
@@ -140,7 +141,7 @@ is a fallback.
### `fluksio login`
```sh
fluksio login --url https://api.example.com
fluksio login --url https://fluksio.example.com
```
Prompts for credentials and saves an API token for a remote engine.
+6 -5
View File
@@ -144,6 +144,7 @@ import subprocess
import tempfile
import fluksio
import imageio_ffmpeg
# H.264 Constrained Baseline, at most 640x360 and 15 fps, mono AAC.
PANEL = [
@@ -157,10 +158,11 @@ PANEL = [
def process(clip):
source = fluksio.load_artifact(clip)
ffmpeg = imageio_ffmpeg.get_ffmpeg_exe()
with tempfile.TemporaryDirectory() as tmp:
target = os.path.join(tmp, "panel.mp4")
subprocess.run(
["ffmpeg", "-nostdin", "-loglevel", "error", "-i", source, *PANEL, target],
[ffmpeg, "-nostdin", "-loglevel", "error", "-i", source, *PANEL, target],
check=True,
timeout=600,
)
@@ -169,10 +171,9 @@ def process(clip):
```
Use it only for clips headed for such a panel. Browsers play the originals,
and the result plays there too, just smaller. The node needs `ffmpeg` on the
`PATH`. The Docker image includes it only when built with
[`WITH_FFMPEG=true`](../reference/configuration.md#image-options) in `.env`.
Otherwise run the node on a [remote worker](workers.md) that has it.
and the result plays there too, just smaller. Add `imageio-ffmpeg` on the
**Modules** screen first. It brings a static `ffmpeg` build for Linux on x86
and ARM, so nothing has to be installed into the image.
### Speech to and from a panel
+3 -3
View File
@@ -9,7 +9,7 @@ WebSocket. Keep one engine; add workers where the hardware lives.
On the engine, use a superuser access token. In these examples:
- `API_URL` is the engine's API base, including `/api/v1`, such as
`https://api.example.com/api/v1` or `http://localhost:8080/api/v1` for a LAN stack.
`https://fluksio.example.com/api/v1` or `http://localhost:8080/api/v1` for a Docker container.
- `TOKEN` is your superuser bearer token. The login endpoint
`POST /api/v1/login/access-token` accepts form fields `username` and `password`.
@@ -36,7 +36,7 @@ pip install fluksio-worker
export FLUKSIO_WORKER_TOKEN='<token from the engine>'
fluksio-worker \
--url wss://api.example.com/api/v1/workers/attach \
--url wss://fluksio.example.com/api/v1/workers/attach \
--labels gpu,cuda12 \
--python /opt/torch-venv/bin/python
```
@@ -163,7 +163,7 @@ in `$DATA_DIR/provisioners.json`, replacing the example addresses and paths:
"name": "hpc",
"login": "me@login.cluster",
"ssh_key": "/secrets/hpc_ed25519",
"engine_url": "wss://api.example.com/api/v1/workers/attach",
"engine_url": "wss://fluksio.example.com/api/v1/workers/attach",
"max_idle_s": 300,
"provision_timeout_s": 900,
"profiles": [{
+49 -63
View File
@@ -3,40 +3,43 @@
Build a small temperature flow, then connect a real sensor when you're ready.
No hardware required for the first lap.
## Start the Docker stack
## Start Fluksio
You need Git, Docker with Compose v2, `make`, and `openssl`. Port 80 must be
free. Clone the public app repository and run its commands from the repo root:
You need Git and Docker with Compose v2. Clone the app repository and start
its single container:
```sh
git clone https://git.stroblme.de/Fluksio/app.git fluksio
cd fluksio
make init
make dev-local
docker compose up -d
docker compose logs fluksio
```
`make init` creates `.env`, generates secrets, creates the `proxy` network,
and prints your admin login. It is safe to run again: existing secrets stay
as they are.
The first build takes a few minutes. The log shows the admin account and its
password once, on the first start. Open [http://localhost:8080](http://localhost:8080)
and sign in. Adding Python code requires that superuser account.
Give the containers a moment to start on the first build, then open
[http://app.localhost](http://app.localhost). Sign in with the admin login:
adding Python code requires a superuser account. The API is at
[http://api.localhost](http://api.localhost), and the docs are at
[http://docs.localhost](http://docs.localhost).
Everything runs in that one container on one port: the engine, its API and the
dashboard. To use another port, change the left side of `ports` in
`compose.yml`, for example `"9000:8000"`. From another machine on your network,
open `http://<host-ip>:8080`. Set `FRONTEND_HOST` in `compose.yml` to the
address you open, since links for wall panels and mails are built from it.
For a Docker instance reached from another machine on your network, use:
The image starts minimal. MQTT, InfluxDB, outbound mail and the rest are
[extras](../reference/configuration.md#image-options). Add the ones you need to
`EXTRAS` in `compose.yml` and rebuild:
```yaml
args:
EXTRAS: "mqtt,influx"
```
```sh
make dev-lan
docker compose up -d --build
```
Open `http://<host-ip>:8080`. Choose another port with
`make dev-lan APP_PORT=8081`. This is a development stack; see
[running it long term](#run-it-long-term) before using it for a deployment.
When you are done, stop the stack with `make down`. Your data volumes stay
in place for the next start.
Stop the container with `docker compose down`. Your data stays on the `data`
volume for the next start.
## Your first flow
@@ -97,10 +100,8 @@ checking the payload format your device expects. A relay may need `ON` and
and **Step** let you inspect queued messages one at a time. A real heating
controller also needs appropriate limits and hysteresis.
Docker development stacks include a test broker at `mosquitto:1883`; it has
no published host port, so your physical sensors cannot reach it directly.
For real sensors, use your own broker. The Docker image includes the MQTT
and InfluxDB connector dependencies.
The MQTT node needs the `mqtt` extra. Fluksio does not run a broker, so point
the node at your own, such as Mosquitto.
## Show it on a dashboard
@@ -112,53 +113,38 @@ See [Dashboards and panels](../interface/dashboards.md) for widgets and pairing
a wall tablet.
For longer-term history, add an **InfluxDB** node that consumes
`living_temperature`. Configure your server URL, organization, bucket, token
`living_temperature`. It needs the `influx` extra. Configure your server URL, organization, bucket, token
and `writes` mapping. Store the token under **Secrets** and select it in the
node's credential field. [Node types](../reference/node-types.md#influxdb)
has the write and query formats.
The Docker development stack includes an InfluxDB test service at
`http://influxdb:8086`, with organization and bucket `fluksio` and token
`fluksio-dev-token`. It is for testing; configure a persistent InfluxDB server
for data you want to keep. The production stack includes neither InfluxDB nor
an MQTT broker.
Fluksio does not run an InfluxDB server either. Use your own.
## Run it long term
For a Docker deployment, run `make init`, then edit the repository's `.env`:
The container from `compose.yml` is fit for long-term use. It restarts with
Docker, and `/data` holds everything it keeps: the database, the flows,
secrets, artifacts and its Redis. Back up that volume. Compose names it
`fluksio_data`.
| Setting | Example |
|---|---|
| `DOMAIN` | `example.com` |
| `ENVIRONMENT` | `production` |
| `FRONTEND_HOST` | `https://app.example.com` |
| `VITE_API_URL` | `https://api.example.com` |
| `BACKEND_CORS_ORIGINS` | `https://app.example.com` |
| `FIRST_SUPERUSER` | your administrator email |
| `TZ` | your timezone, such as `Europe/Berlin` |
The container speaks plain HTTP. For access from outside your network, put a
reverse proxy with HTTPS and WebSocket support in front of it, such as Caddy,
Traefik or nginx, or [enroll with the portal](../interface/portal.md). A
Caddyfile for `fluksio.example.com` is one line:
Keep the generated secrets. Stop any development stack with `make down`.
Set up DNS and a reverse proxy with HTTPS and WebSocket support before running
`make up`. That target starts the application services; it requires a proxy
on the Docker `proxy` network. Route
`app.example.com` to `fluksio-app:80`, `api.example.com` to `fluksio-api:8000`
and, optionally, `docs.example.com` to `fluksio-docs:80`.
```text
fluksio.example.com {
reverse_proxy localhost:8080
}
```
`docker/compose.traefik.yml` is an optional proxy configuration. It also needs
`EMAIL`, `USERNAME` and `HASHED_PASSWORD` for certificates and dashboard
authentication; `make init` does not set these up.
Set `FRONTEND_HOST` to `https://fluksio.example.com` and `TZ` to your
timezone, such as `Europe/Berlin`. See [Configuration](../reference/configuration.md)
for the other settings, which go under `environment` in `compose.yml`.
The published port from `make dev-lan` uses HTTP. For access outside your
network, use HTTPS or [enroll with the portal](../interface/portal.md).
To update, pull the repository and rebuild:
Back up `.env` and both persistent volumes: `app-flow-data` holds the database,
flows, secrets and artifacts; `app-redis-data` holds message values, node state
and queued work. Compose prefixes volume names with the project name
`fluksio-app`. Also back up any external MQTT or InfluxDB data you need.
## Next steps
- [Flows, nodes and messages](../concepts/flows.md): how the graph works.
- [Secrets, modules and alerts](../interface/operations.md): credentials and notifications.
- [Remote workers](../code/workers.md): run node code on another machine.
- [Configuration](../reference/configuration.md): settings and storage paths.
```sh
git pull
docker compose up -d --build
```
+32 -31
View File
@@ -42,9 +42,10 @@ Docker stores instance data under `/data` on the `app-flow-data` volume.
Docker uses `managed`. When Fluksio adopts your own venv, the Modules screen
is read-only; install packages into that environment yourself.
Back up the whole data directory and the persistent `SECRET_KEY`. In Docker,
keep `.env` and `app-flow-data`; back up `app-redis-data` too when preserving
live state. Compose prefixes volume names with the project name `fluksio-app`.
Back up the whole data directory and the persistent `SECRET_KEY`. The single
container keeps both, and its Redis, on the `/data` volume. The operator stack
under `docker/` keeps `.env` and `app-flow-data`, plus `app-redis-data` for
live state. Compose prefixes volume names with the project name.
Keep runtime files on persistent storage so rebuilds preserve credentials,
pairings and subscriptions.
@@ -52,13 +53,15 @@ pairings and subscriptions.
| Variable | Default | Purpose |
|---|---|---|
| `REDIS_HOST` | unset | unset uses memory; a Redis host enables persistent flow state |
| `REDIS_HOST` | unset | unset uses memory, or the image's own Redis in Docker; a Redis host enables persistent flow state |
| `REDIS_PORT` | `6379` | Redis port |
Live state includes message values, node memory and queued work. In-memory
state is lost on restart. The Docker stack uses Redis with append-only
persistence on its own `app-redis-data` volume. Its default sync policy may
lose about a second of recent writes after an unclean shutdown.
state is lost on restart. The Docker image starts a Redis of its own with
append-only persistence in `/data/redis` whenever `REDIS_HOST` is unset. Set
`REDIS_HOST` to use an external one instead, as the operator stack does with
its `app-redis-data` volume. The default sync policy may lose about a second of
recent writes after a power cut.
## Accounts and access
@@ -68,8 +71,7 @@ lose about a second of recent writes after an unclean shutdown.
| `ACCESS_TOKEN_EXPIRE_MINUTES` | `11520` (8 days) | session-token lifetime |
| `FIRST_SUPERUSER` | unset | initial administrator email; the CLI creates an account if needed |
| `FIRST_SUPERUSER_PASSWORD` | unset | initial password; the CLI generates and prints one if needed |
| `DOMAIN` | `localhost` | API hostname and OAuth issuer; Docker also uses it for routing |
| `FRONTEND_HOST` | `http://localhost:5173` | interface URL for links and metadata |
| `FRONTEND_HOST` | `http://localhost:5173`, `http://localhost:8000` in Docker | the address people and devices reach the instance at; panel links, mails and the OAuth issuer use it |
| `BACKEND_CORS_ORIGINS` | `[]` | allowed origins, as comma-separated URLs or a JSON list; `FRONTEND_HOST` is also allowed |
| `FORWARDED_ALLOW_IPS` | `127.0.0.1` | trusted proxies; Docker adds `172.16.0.0/12` |
| `SIGNUP_ENABLED` | `false` | allows account creation from the login page; otherwise use **Admin → Users** |
@@ -79,32 +81,31 @@ lose about a second of recent writes after an unclean shutdown.
Changing `SECRET_KEY` signs everyone out and makes existing encrypted
secrets unreadable. Back it up with your data.
## Interface API address
## Interface address
`VITE_API_URL` is a frontend **build argument**, so changing it requires
rebuilding the frontend image.
| Setup | Value |
|---|---|
| Production Docker | set `.env` to `https://api.<your-domain>` |
| `make dev-local` | overlay sets `http://api.localhost` by default |
| `make dev-lan` | overlay sets an empty value, using `/api` on the page's own origin |
Without an explicit value, the production Compose file falls back to
`https://api.${DOMAIN}`. Set `FRONTEND_HOST` and `BACKEND_CORS_ORIGINS` to
match your deployment as well; `make init` generates secrets, not deployment
URLs.
The LAN overlay also configures nginx to proxy `/api` and WebSocket requests
to the backend. An empty value needs that proxy when the frontend and backend
are served separately. See [facility automation](../getting-started/facility-automation.md#run-it-long-term)
for deployment settings.
The image serves the interface and the API on one origin, so the interface
calls the API as a path on whatever address served it. One build works behind
any port, hostname or reverse proxy. `FRONTEND_DIST` points at the built
interface and is set by the image. A checkout leaves it empty and runs the Vite
dev server, which needs its origin in `BACKEND_CORS_ORIGINS`.
## Image options
`WITH_FFMPEG=true` in `.env` installs `ffmpeg` into the backend image on the
next build, for node code that transcodes video. It is off by default because
it adds about 400 MB.
The `EXTRAS` build argument picks the optional features built into the image,
comma separated. Empty, the default, is the minimal engine.
| Extra | Adds |
|---|---|
| `mqtt` | the MQTT node |
| `influx` | the InfluxDB node |
| `email` | outbound mail, such as password resets |
| `sentry` | error reporting to `SENTRY_DSN` |
| `ml` | numpy, for the `mlp` node |
| `server` | all of the above |
A node whose extra is missing says so on the canvas. The same names work as
pip extras, such as `pip install 'fluksio[mqtt]'`. Connector packages built
with `make connectors` are installed into the image too.
## Environment
+3 -3
View File
@@ -6,9 +6,9 @@ settings in the node panel; use a **Function** node for your own Python.
`GET /flows/node-types` returns this list with each type's full parameter
schema, including any [connectors](#connectors) installed on your instance.
MQTT and InfluxDB need `pip install 'fluksio[server]'` on a Python installation.
The Docker image includes these dependencies. Run the broker and database
separately, or use the development stack's test services.
MQTT and InfluxDB need their [extras](configuration.md#image-options): `mqtt`
and `influx` in the image's `EXTRAS`, or `pip install 'fluksio[mqtt,influx]'`
on a Python installation. Run the broker and database separately.
## Function