Jacketed Conical | Tilt | Brewfather | Unraid

I finally spent some time getting brewblox running on my unraid server, would like to share my experience/settings in case others wish to do the same, and ask if there are recommended changes to my setup for optimal use.

My primary goals are to control a glycol jacketed conical, monitor readings from a tilt, and send those readings to brewfather. Secondary goals would be to add on a heating solution (likely warm liquid) for my conical, controlling the glycol chiller itself, controlling the warm liquid reservoir, and potentially adding a second conical with all the same features of the first.

What I currently have setup is a simple inkbird controller which achieves about +/- 2 degrees of control. The glycol jacket has huge cooling ability, so by the time it’s reading setpoint it overshoots by a lot. I could tune the glycol temp, but that would not work if I have a 2nd conical with different cooling needs. Thus, one of my primary drivers for PID control.

I already have an unraid server running and wanted to make use of that if possible. Docker compose is available from community applications, which is the direction I went to setup brewblox. I added a ‘Brewblox’ stack and the following compose and env configurations.

Compose (based upon brewblox-ctl/brewblox_ctl/deployed/config/docker-compose.shared.yml at develop · BrewBlox/brewblox-ctl · GitHub):

# DO NOT EDIT: THIS FILE WILL BE RESET DURING UPDATES
#
# This file contains configuration for the shared Brewblox services
#
# If you need to make change to any of the shared services,
# you can do so in docker-compose.yml.
#
# For more information, see https://docs.docker.com/compose/extends/
version: "3.7"
services:
  eventbus:
    image: ghcr.io/brewblox/mosquitto:${BREWBLOX_RELEASE}
    restart: unless-stopped
    labels:
      # MQTT
      - traefik.tcp.routers.mqtt.entrypoints=mqtt
      - traefik.tcp.routers.mqtt.rule=HostSNI(`*`)
      - traefik.tcp.routers.mqtt.tls=false
      - traefik.tcp.routers.mqtt.service=mqtt
      - traefik.tcp.services.mqtt.loadBalancer.server.port=1883
      # MQTTS with TLS termination by traefik
      - traefik.tcp.routers.mqtts.entrypoints=mqtts
      - traefik.tcp.routers.mqtts.rule=HostSNI(`*`)
      - traefik.tcp.routers.mqtts.tls=true
      - traefik.tcp.routers.mqtts.service=mqtts
      - traefik.tcp.services.mqtts.loadBalancer.server.port=1884
      # MQTT over websockets
      - traefik.http.services.eventbus.loadbalancer.server.port=15675
    volumes:
      - type: bind
        source: /mnt/user/appdata/mosquitto
        target: /mosquitto/include
  victoria:
    image: victoriametrics/victoria-metrics:v1.88.0
    restart: unless-stopped
    labels:
      - traefik.http.services.victoria.loadbalancer.server.port=8428
    volumes:
      - type: bind
        source: /mnt/user/appdata/victoria
        target: /victoria-metrics-data
    command: >-
      --retentionPeriod=100y
      --influxMeasurementFieldSeparator=/
      --http.pathPrefix=/victoria
      --search.latencyOffset=10s
  redis:
    image: redis:6.0
    restart: unless-stopped
    labels:
      - traefik.enable=false
    volumes:
      - type: bind
        source: /mnt/user/appdata/redis
        target: /data
    command: --appendonly yes
  history:
    image: ghcr.io/brewblox/brewblox-history:${BREWBLOX_RELEASE}
    restart: unless-stopped
    volumes:
      - type: bind
        source: /etc/localtime
        target: /etc/localtime
        read_only: true
    # Privileged to avoid a bug in libseccomp for Debian Buster hosts
    # This flag should be removed when Debian Buster is no longer supported
    # For an explanation: https://github.com/sdr-enthusiasts/Buster-Docker-Fixes
    privileged: true
  traefik:
    image: traefik:2.9
    restart: unless-stopped
    labels:
      - traefik.http.routers.api.rule=PathPrefix(`/api`) || PathPrefix(`/dashboard`)
      - traefik.http.routers.api.service=api@internal
      - traefik.http.middlewares.prefix-strip.stripprefixregex.regex=/[^/]+
    volumes:
      - type: bind
        source: /mnt/user/appdata/traefik
        target: /config
        read_only: true
      - type: bind
        source: /var/run/docker.sock
        target: /var/run/docker.sock
      - type: bind
        source: /etc/localtime
        target: /etc/localtime
        read_only: true
    ports:
      - "${BREWBLOX_PORT_HTTP:-80}:${BREWBLOX_PORT_HTTP:-80}"
      - "${BREWBLOX_PORT_HTTPS:-443}:${BREWBLOX_PORT_HTTPS:-443}"
      - "${BREWBLOX_PORT_MQTT:-1883}:${BREWBLOX_PORT_MQTT:-1883}"
      - "${BREWBLOX_PORT_MQTTS:-8883}:${BREWBLOX_PORT_MQTTS:-8883}"
    command: >-
      --api.dashboard=true
      --providers.docker=true
      --providers.docker.constraints="LabelRegex(`com.docker.compose.project`, `${COMPOSE_PROJECT_NAME}`)"
      --providers.docker.defaultrule="PathPrefix(`/{{ index .Labels \"com.docker.compose.service\" }}`)"
      --providers.file.directory=/config
      --entrypoints.web.address=:${BREWBLOX_PORT_HTTP}
      --entrypoints.websecure.address=:${BREWBLOX_PORT_HTTPS}
      --entrypoints.websecure.http.tls=true
      --entrypoints.mqtt.address=:${BREWBLOX_PORT_MQTT}/tcp
      --entrypoints.mqtts.address=:${BREWBLOX_PORT_MQTTS}/tcp
  ui:
    image: ghcr.io/brewblox/brewblox-ui:${BREWBLOX_RELEASE}
    restart: unless-stopped
    labels:
      - traefik.http.routers.ui.rule=PathPrefix(`/ui`) || Path(`/`)
    volumes:
      - type: bind
        source: /etc/localtime
        target: /etc/localtime
        read_only: true

Env (settings from brewblox-ctl/brewblox_ctl/const.py at develop · BrewBlox/brewblox-ctl · GitHub):

BREWBLOX_RELEASE = 'edge'
BREWBLOX_PORT_HTTP = 3780
BREWBLOX_PORT_HTTPS = 37443
BREWBLOX_PORT_MQTT = 1883
BREWBLOX_PORT_MQTTS = 8883

My only change to compose was updating relative directory location to /mnt/user/appdata/ for each app, as that is a common location for docker images to store their data. I had to create each directory in appdata before they would successfully launch. I also updated the HTTP/HTTPS ports as they are already in use for unraid. Besides that, everything seems to be working. I am hoping for some feedback if these changes make sense, and hopefully allow simply calling to update the stack as new releases become available. If not, I’m all ears to any recommended changes

I haven’t added any devices yet, but probably add a Spark Simulator to play around with, and ultimately get a Spark 4. Can adding devices be done from the web interface? I may be completely missing something obvious, but I can’t seem to find this configuration. I see in the documentation to use the ‘brewblox-ctl add-spark’ command, but I am unclear where I would do this running from docker compose.

Thanks in advance!

The configuration looks solid.

Newly released docker images can be downloaded with docker compose pull. Automated configuration migrations applied by brewblox-ctl will have to be replicated manually.

Devices are added by editing the compose file. The web interface can’t do this.
The add-spark command handles device discovery, and then writes to the docker-compose.yml file.
The source code can be found at brewblox-ctl/brewblox_ctl/commands/add_service.py at develop · BrewBlox/brewblox-ctl · GitHub, but may not be the easiest route to reverse engineer configuration.

The generated config for a simulation service called “spark-sim” would be:

  spark-sim:
    command: --name=spark-sim --discovery=all --simulation
    image: ghcr.io/brewblox/brewblox-devcon-spark:${BREWBLOX_RELEASE}
    privileged: true
    restart: unless-stopped
    volumes:
    - type: bind
      source: /etc/localtime
      target: /etc/localtime
      read_only: true
    - type: bind
      source: ./spark/backup
      target: /app/backup
    - type: bind
      source: ./simulator__spark-sim
      target: /app/simulator

For a real Spark, --simulation should be omitted, --device-id should be set, and the /app/simulator directory does not have to be mounted.

If your unraid server does not have access to mDNS, you also need to set the --device-host argument.

For a full explanation, see Brewblox Spark | Brewblox

Example service for a real spark, with --device-host set.

  spark-four:
    image: ghcr.io/brewblox/brewblox-devcon-spark:${BREWBLOX_RELEASE}
    privileged: true
    restart: unless-stopped
    command: --name=spark-four --device-id=c4dd5766bb18 --device-host=192.168.2.3
    volumes:
    - type: bind
      source: /etc/localtime
      target: /etc/localtime
      read_only: true
    - type: bind
      source: ./spark/backup
      target: /app/backup