Architecture#

Self Hosted

BrewGraph is a multi-service application. A core set of three services handles all data storage and serves the API and web UI. Optional sidecar services extend the platform with BLE ingestion, mDNS discovery, and device log collection.

Service topology#

Physical Devices (BLE / mDNS / HTTP)
         │
┌────────┴──────────────────┐
│ brewgraph_ble (optional)  │
│ brewgraph_mdns (optional) │──→  brewgraph_api   ←── brewgraph_web :80
│ brewgraph_log (optional)  │           │
└───────────────────────────┘    brewgraph_db          (PostgreSQL 17)
                                 brewgraph_redis        (Redis 7)

Core services#

brewgraph_api

The FastAPI backend. All business logic, database access, and external integrations live here. Clients — the web UI, the mDNS/BLE sidecars, and devices — reach it through the web server’s reverse proxy.

Internally structured as:

Layer

Role

core/models/

SQLAlchemy ORM abstract base classes and model registry.

oss/models/

Concrete SQLAlchemy ORM table definitions. No business logic.

core/schemas/

Pydantic v2 camelCase and pagination base classes.

oss/schemas/

Concrete request and response shapes for each domain entity.

oss/services/

All business logic. Extend BaseService for standard CRUD.

oss/routers/

Thin FastAPI endpoint functions. Delegate to services.

oss/jobs/

APScheduler background jobs: fermentation predictions, gravity forwarding.

core/ml/

Machine learning model for fermentation completion prediction.

core/middleware/

API key authentication dependency.

brewgraph_db

PostgreSQL 17 for production deployments. SQLite is used in development (set DATABASE_URL=sqlite:///./brewgraph.sqlite). Schema is managed via Alembic migrations.

brewgraph_redis

Redis 7 used for response caching and ephemeral state shared between the API and sidecars. The API stores the latest per-device readings under ble_<id>_* keys with a 6-hour TTL.

Sidecar services#

brewgraph_ble (Linux only)

Scans for Bluetooth Low Energy advertisements from brewing sensors. Parses manufacturer data using binary specs (Construct library) and POSTs readings to the API ingest endpoint. Requires network_mode: host, NET_ADMIN and NET_RAW capabilities, and the BlueZ D-Bus socket mounted at /var/run/dbus.

Supported BLE devices: Tilt, GravityMon, and PressureMon. A RAPT Pill is supported through an iSpindel-compatible bridge such as SPINDELMATE.

brewgraph_mdns

Listens for Bonjour/mDNS service announcements on the local network using Zeroconf and exposes discovered devices through GET /api/devices/mdns for the operator to review and register in the Devices view.

Scanned service types:

_pressuremon._tcp.local.
_gravitymon._tcp.local.
_gravitymon-gateway._tcp.local.
_kegmon._tcp.local.

Requires network_mode: host for LAN multicast access.

brewgraph_log

Fetches the device list from the API, then opens a WebSocket connection to each device’s serial endpoint (ws://<device_url>/serialws) and writes incoming lines to rotating log files in the brewgraph_logs shared volume.

Authentication#

All service-to-service calls and all client requests to the API use Bearer token authentication:

Authorization: Bearer <API_KEY>

Authentication can be disabled for local development by setting API_KEY_ENABLED=false.

Shared volumes#

Volume

Mounted by

Purpose

brewgraph_data

api

SQLite DB file and export artifacts

brewgraph_db_data

db

PostgreSQL data files

brewgraph_redis_data

redis

Redis persistence

brewgraph_logs

log, api

Device serial log files (read by API, written by log sidecar)

./web/certs

web

Optional SSL certificate and key

Technology stack#

Layer

Technology

Frontend

Vue 3.5, Vite 7, Pinia 3, Vue Router 4, Bootstrap 5, Chart.js 4

API

FastAPI, SQLAlchemy, Pydantic v2, APScheduler

Database

PostgreSQL 17 (production), SQLite (development)

Cache

Redis 7

BLE ingestion

Python / Bleak 0.21 / Construct

Device discovery

Python / Zeroconf 0.148

Log collection

Python / Websockets 16

Web server

Nginx Alpine

Containers

Docker Compose

Testing

Pytest (Python), Vitest (JavaScript)