Skip to content
mp-sePublic

About

Self-hosted homebrewing telemetry platform. Ingests gravity and pressure readings from physical sensors and tracks batches through their full lifecycle — fermenting → conditioning → kegged or bottled.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Docker Pulls release issues pr

BrewGraph

Self-hosted homebrewing telemetry platform. Ingests gravity and pressure readings from physical sensors and tracks batches through their full lifecycle — fermenting → conditioning → kegged or bottled.

Runs entirely on your local network via Docker Compose. No cloud account required.

Website: www.brewgraph.com · Documentation: www.brewgraph.com/docs

Note: BrewGraph is the successor to BrewLogger. The brewlogger.com domain was already taken, so the project was renamed and rebuilt as BrewGraph. Existing BrewLogger backups can be imported directly via the restore feature. BrewGraph can be seen as BrewLogger v2.0 since it has a new datamodel and new features.

Security notice: BrewGraph is designed for trusted LAN deployment only. The shared API key authenticates the web UI and is delivered to the browser in a static file; IoT devices do not use it — they post to the open ingest endpoints with their own per-device tokens. Do not expose BrewGraph to the public internet. If remote access is needed, use a VPN (WireGuard, Tailscale) or a reverse proxy with its own auth layer. See Security model & accepted tradeoffs for the full threat model.


Contents


Supported devices

Device Protocol Ingest endpoint Firmware
iSpindel HTTP /ingest/ispindel Any version
GravityMon HTTP / BLE /ingest/gravitymon Any version
GravityMon Gateway HTTP /ingest/gravitymon Any version
PressureMon HTTP / BLE /ingest/pressuremon Any version
KegMon HTTP server only; the firmware adds /ingest/kegmon and /ingest/kegmon/beer Integration in development, not yet released
ChamberCtl HTTP / BLE /ingest/chamber Integration in development, not yet released

Where a device type has no released firmware yet, the ingest endpoint is in place but has not been verified against released firmware.


Features

  • Ingest gravity and temperature readings from iSpindel-compatible devices over HTTP
  • Ingest pressure readings from PressureMon devices
  • BLE ingestion via a sidecar for wireless sensors (Linux only)
  • Automatic device discovery via mDNS/Bonjour (Requires Linux host)
  • Batch lifecycle management with fermentation-step and dry-hop tracking, plus per-batch notes
  • Gravity, pressure, and temperature charts per batch, plus gravity comparison across batches
  • Fermentation completion prediction via a machine learning model
  • Yeast strain library with batch assignment (Used for improving machine learning result)
  • Batch data export (gravity CSV, pressure CSV, batch JSON)
  • Brewfather batch/recipe import
  • BeerXML recipe import
  • Tap and keg management with pour tracking
  • Storage vessel management
  • Public tap and bottle display
  • Configurable measurement-forwarding integrations
  • System, device, and ingestion event logs
  • Backup and restore of all data
  • REST API with interactive OpenAPI docs
  • Vue 3 single-page web UI for desktops
  • API key authentication
  • PostgreSQL backend (SQLite for local development/testing only, not a deployment option)

Requirements

  • Docker Engine 24+ and Docker Compose v2
  • Linux, macOS, or Windows (WSL2) host
  • For BLE ingestion: Linux host with Bluetooth hardware and BlueZ
  • For mDNS discovery: network_mode: host support (Linux required)

Quick start — pre-built images

The recommended path for most users. Images are published to Docker Hub under mpse2/brewgraph-*.

1. Download the compose file

curl -o docker-compose.yaml https://raw.githubusercontent.com/mp-se/brewgraph/main/docker-compose.example.yaml

2. Create a .env file with your API key

cat > .env <<'EOF'
API_KEY=replace-with-a-long-random-secret
POSTGRES_PASSWORD=replace-with-a-unique-database-password
EOF

API_KEY may contain only printable ASCII characters (letters a-z A-Z without accents, digits and symbols, no spaces). Characters such as Ö, å and ä are not accepted; the API refuses to start and logs which position is invalid. See the log with docker compose logs brewgraph-api.

3. Start the core services

docker compose up -d

The web UI is available at http://localhost:80. The API is accessible internally at brewgraph-api:8080 and through the Nginx proxy at http://localhost:80/api.

Note: The interactive API docs (/docs) are blocked at the Nginx proxy for security. Access them directly on the API's own port instead: http://localhost:8080/docs.


Optional services

The default Compose configuration starts all services. The API, database, Redis, web UI, and display service form the default stack; the remaining sidecars enable specific capabilities:

Service Required When to enable
brewgraph-api Yes Always
brewgraph-db Yes Always
brewgraph-redis Yes (default configuration) Required for caching and integrations; can be disabled only when caching and integrations are unused
brewgraph-web Yes Needed for the web UI
brewgraph-display Yes Public tap and bottle display
brewgraph-log No Device log collection via device websocket API
brewgraph-mdns No Auto-discovery of devices on the LAN (Linux only)
brewgraph-ble No BLE ingestion for GravityMon / PressureMon / ChamberCtl (Linux only)
brew_pgadmin No Optional database management UI

Redis is load-bearing for Integrations, not just an optional cache. Measurement forwarding (Settings → Integrations) queues each job on Redis at ingest time and drains it in the background; with CACHE_ENABLED=false or Redis unreachable, enqueue_forward logs a warning and drops the job — the reading itself still saves, but nothing gets forwarded, with no error surfaced anywhere in the UI. If you use Integrations, run brewgraph-redis.


Quick start — build from source

Use this path if you want to modify the code or run the latest unreleased version.

git clone https://github.com/mp-se/brewgraph.git
cd brewgraph
cp docker-compose.example.yaml docker-compose.yaml
cat > .env <<'EOF'
API_KEY=replace-with-a-long-random-secret
POSTGRES_PASSWORD=replace-with-a-unique-database-password
EOF
docker compose build
docker compose up -d

Services

Service Image Purpose
brewgraph-api mpse2/brewgraph-api FastAPI backend, REST API, scheduler
brewgraph-db postgres:17-alpine Primary database
brewgraph-redis redis:7-alpine Ephemeral state cache
brewgraph-web mpse2/brewgraph-web Vue 3 UI served by Nginx (HTTP or HTTPS)
brewgraph-display mpse2/brewgraph-display Public tap and bottle display
brewgraph-log mpse2/brewgraph-log Collects device serial logs over WebSocket
brewgraph-mdns mpse2/brewgraph-mdns Auto-discovers devices via mDNS/Bonjour (Linux only)
brewgraph-ble mpse2/brewgraph-ble BLE ingestion from wireless sensors (Linux only)

brewgraph-ble and brewgraph-mdns use network_mode: host and only work on Linux. On macOS they start but cannot access LAN multicast or the host Bluetooth stack.


Environment variables

API (brewgraph-api)

Variable Required Default Description
DATABASE_URL Yes — SQLAlchemy URL for the PostgreSQL database. Set automatically by docker-compose.yaml
API_KEY Yes — Bearer token for authenticated UI and management API requests; ingest endpoints use per-device tokens. Printable ASCII only, no spaces (no Ö, å, ä); the API refuses to start otherwise and logs the invalid position
DB_POOL_SIZE No 20 PostgreSQL connections kept open per API process (integer >= 1). Ignored for SQLite
DB_MAX_OVERFLOW No 20 Extra PostgreSQL connections a process may open beyond DB_POOL_SIZE under load (integer >= 0)
DB_POOL_TIMEOUT No 10 Seconds a request waits for a free connection before failing (integer >= 1)
SCHEDULER_ENABLED No true Enable background jobs
CACHE_ENABLED No true Enable Redis cache
REDIS_HOST No — Redis hostname (required if CACHE_ENABLED=true)
TRUST_PROXY_HEADERS No false Trust X-Real-IP / X-Forwarded-For (rightmost entry) from a reverse proxy. Set true when behind Nginx.
TRUSTED_PROXIES No loopback and private ranges Comma-separated IPs or CIDR ranges whose proxy headers are believed. Headers from any other peer, and values that are not IP addresses, are ignored. Narrow it to your proxy address if you can.
SSE_MAX_CONNECTIONS_PER_IP No 10 Concurrent live-update (/events) streams allowed per client IP; extra connections get 429. 0 = no limit
SSE_MAX_CONNECTIONS No 50 Concurrent live-update streams in total (the API key is shared, so this is also the per-key limit). 0 = no limit
AUTH_MAX_FAILURES No 10 Failed auth attempts before IP block
AUTH_BLOCK_SECONDS No 300 Seconds an IP stays blocked after too many failures
BREWFATHER_USER_KEY No — Brewfather integration user key
BREWFATHER_API_KEY No — Brewfather integration API key

Each API process can open up to DB_POOL_SIZE + DB_MAX_OVERFLOW database connections (40 with the defaults). When several processes share one database, the sum of that figure over all of them must stay below PostgreSQL's max_connections (default 100), with headroom for migrations and admin sessions. An invalid value stops startup with an error naming the variable.

Web (brewgraph-web)

Variable Required Description
API_HOST Yes Hostname of brewgraph-api (e.g. brewgraph-api)
API_KEY Yes Must match the API's API_KEY (printable ASCII only; the service exits with an error otherwise)

Log sidecar (brewgraph-log)

Variable Default Description
API_HOST — Hostname of brewgraph-api
API_KEY — Bearer token
REDIS_HOST — Redis hostname (optional)
MAX_FILE_SIZE 1048576 Bytes before log rotation
MAX_LOG_FILES 5 Retained files per device, including the active log

BLE sidecar (brewgraph-ble)

Variable Default Description
API_HOST — Use localhost with host networking
API_KEY — Bearer token
REDIS_HOST — Redis hostname (optional)
MIN_INTERVAL 900 Min seconds between posts per device

HTTPS (optional)

Place your certificate and key in web/certs/, named exactly:

web/certs/server.crt
web/certs/server.key

Then, in docker-compose.yaml, uncomment both the brewgraph-web volume mount and the 443:443 port mapping:

  brewgraph-web:
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./web/certs:/etc/nginx/ssl:ro

The Nginx entrypoint detects the certificate files and switches to HTTP+HTTPS automatically. Without the port mapping above, HTTPS is enabled inside the container but unreachable from outside it.


Updating

Pre-built images:

docker compose pull
docker compose up -d

Built from source:

git pull
docker compose build
docker compose up -d

Development

API

cd api
make install-test   # install deps including test extras
make run            # dev server on :8080 (SQLite, API key "devkey")
make test           # run test suite
make lint           # pylint
make fmt            # autoformat

Web

cd web
npm install
VITE_APP_TOKEN=devkey npm run dev   # dev server on :5173 (proxies API to localhost:8080)
npm run test        # Vitest unit tests
npm run lint        # ESLint

Security model & accepted tradeoffs

BrewGraph OSS targets a single trusted operator on a private LAN. The items below are deliberate design decisions for that threat model — they are not findings, and should not be re-reported in security reviews of the OSS build.

Trust boundary

The LAN is trusted: anyone who can reach the web UI is treated as the operator. Do not expose BrewGraph to the public internet. For remote access use a VPN (WireGuard/Tailscale) or a reverse proxy that adds its own authentication.

Accepted by design (LAN)

Area Decision Rationale
API key in the SPA The web container injects API_KEY into a static env-config.js that the browser reads. The key is a web-UI credential only. Any LAN client is already trusted, and without TLS (which most LAN deployments skip) a bootstrap handshake would add no real protection.
Open ingest endpoints /ingest/* requires no API key. IoT firmware (iSpindel, GravityMon, PressureMon, KegMon…) authenticates with a per-device token in the payload; endpoints are per-IP rate-limited and per-device throttled.
Device tokens stored in plaintext Device tokens are kept readable in the database and shown in the UI. A token only ties a device's readings to a batch or tap; it grants no access to the API or to other data. The UI has to display it so it can be copied into the device firmware, and a hash could not be shown again. Tokens are 192-bit random values, unique per device, and can be regenerated at any time.
CORS allow_origins: * Wildcard origin with allow_credentials=false. No cookies are used and auth is a bearer header, so a wildcard origin cannot drive cross-site credentialed attacks. Simplifies serving the UI and API from different origins/ports.
Single shared API key One full-privilege bearer token, no per-user roles. Single-operator deployment — there are no other users to isolate.
Best-effort rate limiting Auth brute-force blocking, ingest rate limits, and device throttling require Redis (CACHE_ENABLED=true) and no-op if Redis is unavailable. The API key is a 128-bit secrets.token_urlsafe value, so online brute force is infeasible regardless. The limiter is defense-in-depth against device abuse, not the primary control.
proxy_fetch reaches LAN hosts The authenticated UI can proxy requests to private (RFC1918) IPs; cloud-metadata, loopback, and link-local targets are blocked, and the resolved IP is pinned against DNS rebinding. Configuring physical devices on the LAN is the intended feature, and the caller is already the trusted operator.

Still enforced (do not weaken)

  • API key compared with hmac.compare_digest (constant-time).
  • proxy_fetch rejects public, loopback, and link-local targets and pins the resolved IP to prevent DNS rebinding.
  • Secrets are stored as SecretStr and redacted from logs.
  • Containers run as a non-root user; CSP and security headers are set at the edge.

License

BrewGraph is licensed under the GNU General Public License v3.0 for open source use. For commercial use without source disclosure, a separate Commercial License is required.

© 2024–2026 Magnus Persson

About

Self-hosted homebrewing telemetry platform. Ingests gravity and pressure readings from physical sensors and tracks batches through their full lifecycle — fermenting → conditioning → kegged or bottled.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages