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.
114 lines
5.0 KiB
Docker
114 lines
5.0 KiB
Docker
# The whole instance in one image: engine, API and dashboard on port 8000.
|
|
#
|
|
# docker build -f backend/Dockerfile -t fluksio .
|
|
# docker run -d -p 8080:8000 -v fluksio-data:/data fluksio
|
|
#
|
|
# The build context is the app repository root. EXTRAS picks the optional
|
|
# features (mqtt, influx, email, sentry, ml, or server for all of them); the
|
|
# default is the minimal engine.
|
|
|
|
# ── Dashboard ────────────────────────────────────────────────────
|
|
FROM oven/bun:1 AS frontend
|
|
|
|
WORKDIR /app
|
|
COPY package.json bun.lock /app/
|
|
COPY frontend/package.json /app/frontend/
|
|
WORKDIR /app/frontend
|
|
RUN bun install
|
|
|
|
COPY ./frontend /app/frontend
|
|
# Empty: the dashboard calls the API as a path on whichever origin served it,
|
|
# so one build works behind any port, hostname or proxy.
|
|
ENV VITE_API_URL=
|
|
RUN bun run build
|
|
|
|
# ── Python environment ───────────────────────────────────────────
|
|
FROM python:3.13-slim AS backend
|
|
|
|
# Ref: https://docs.astral.sh/uv/guides/integration/docker/
|
|
COPY --from=ghcr.io/astral-sh/uv:0.9.26 /uv /bin/
|
|
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
|
|
|
|
WORKDIR /app/
|
|
|
|
ARG EXTRAS=""
|
|
|
|
# Dependencies first, so a source change does not reinstall them.
|
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
--mount=type=bind,source=uv.lock,target=uv.lock \
|
|
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
|
uv sync --frozen --no-dev --no-install-workspace --package fluksio \
|
|
$(for e in $(echo "$EXTRAS" | tr , ' '); do printf -- '--extra %s ' "$e"; done)
|
|
|
|
COPY ./backend/scripts /app/backend/scripts
|
|
|
|
# README.md and LICENSE come along because pyproject.toml names them in
|
|
# `readme` and `license-files`; hatchling reads both when it builds the
|
|
# package metadata below, and fails the build if either is missing.
|
|
COPY ./backend/pyproject.toml ./backend/alembic.ini ./backend/README.md ./backend/LICENSE /app/backend/
|
|
|
|
COPY ./backend/fluksio /app/backend/fluksio
|
|
|
|
# The worker is a workspace member of its own, so the engine's sync needs it
|
|
# present to resolve the dependency on it. It is also what the engine runs
|
|
# node code with.
|
|
COPY ./worker /app/worker
|
|
|
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
--mount=type=bind,source=uv.lock,target=uv.lock \
|
|
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
|
uv sync --frozen --no-dev --package fluksio \
|
|
$(for e in $(echo "$EXTRAS" | tr , ' '); do printf -- '--extra %s ' "$e"; done)
|
|
|
|
# Connectors are ordinary installed packages found through the
|
|
# `fluksio.node_types` entry point. `make connectors` builds them into here;
|
|
# an image with none is the normal case.
|
|
COPY ./backend/connector-wheels /tmp/connector-wheels
|
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
sh -c 'ls /tmp/connector-wheels/*.whl >/dev/null 2>&1 \
|
|
&& uv pip install --python /app/.venv/bin/python /tmp/connector-wheels/*.whl \
|
|
|| echo "no connectors bundled"'
|
|
|
|
# ── Runtime ──────────────────────────────────────────────────────
|
|
FROM python:3.13-slim
|
|
|
|
# git versions the flows. redis-server keeps message state when no external
|
|
# Redis is named. tini reaps the python node workers that outlive their pool,
|
|
# which would otherwise pile up as zombies under PID 1. procps is uptime, ps
|
|
# and free for Command nodes, the demo's among them.
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends git procps redis-server tini \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# /app/.venv holds the app and nothing of anybody else's, so there is nothing
|
|
# here to adopt: node code gets a venv of its own on the data volume, which is
|
|
# what the Modules screen installs into.
|
|
ENV PYTHONUNBUFFERED=1 \
|
|
PATH="/app/.venv/bin:$PATH" \
|
|
NODE_VENV=managed \
|
|
FRONTEND_DIST=/app/frontend/dist \
|
|
FRONTEND_HOST=http://localhost:8000
|
|
|
|
# The venv installs the app editable, so the source stays at the path it was
|
|
# synced from. uv comes with it: the Modules screen installs with it.
|
|
COPY --from=backend /app /app
|
|
COPY --from=frontend /app/frontend/dist /app/frontend/dist
|
|
COPY ./backend/docker-entrypoint.sh /usr/local/bin/
|
|
|
|
WORKDIR /app/backend/
|
|
VOLUME /data
|
|
EXPOSE 8000
|
|
|
|
# Deep health: fails when the event loop is wedged or Redis is gone, not just
|
|
# when the process is dead.
|
|
HEALTHCHECK --interval=10s --timeout=5s --start-period=30s --retries=5 \
|
|
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/v1/utils/health/', timeout=4)"
|
|
|
|
ENTRYPOINT ["tini", "--", "docker-entrypoint.sh"]
|
|
|
|
# Single process on purpose: it hosts the flow engine, and a second worker
|
|
# would be a second engine — duplicated subscriptions, cron ticks and webhooks.
|
|
# `serve` makes the secret key and the first admin on first start, and prints
|
|
# the admin's password once.
|
|
CMD ["fluksio", "serve", "--plain", "--host", "0.0.0.0", "--port", "8000", "--data-dir", "/data"]
|