Guides

Running it

Day-to-day operation: the one container, the built-in broker, upgrades that wait for the roaster, label fonts, nightly backups and a checked restore.

Day to day with a running install: the one container and its broker, updating without cutting a roast short, label printers and fonts, and backups with a checked restore. Installing from scratch, step by step, is install; every variable is in the variables table in the README.

Images

The image runs on linux/amd64 and linux/arm64 (a Raspberry Pi 4 or 5 with a 64-bit OS). A release vX.Y.Z publishes the tags :vX.Y.Z and :latest. .env picks them: ROASTWARDEN_IMAGE and ROASTWARDEN_VERSION (latest when unset).

Each release also carries roastwarden-compose-vX.Y.Z.tar.gz, with the version pinned:

  • docker-compose.single.yml: the server alone, with its built-in broker (below).
  • docker-compose.yml: the server beside Mosquitto, and the Mosquitto config.
  • .env.example, the install guide, the licence and artisan/Roastwarden.aset (the setup file for Artisan as a screen).
  • The roast command line for Linux x86_64 and aarch64, with checksums.

The roast command is in the image as well: docker exec roastwarden roast health says whether the server is up, and roast status gives the version, the roasters heard and the last backup.

One container

docker-compose.single.yml runs the server with its own MQTT broker on port 1883 (ROASTWARDEN_BROKER_ADDR), so no Mosquitto is needed. It uses the host's network, so mDNS finds roasters on the LAN, and it reads every variable from .env.

  • Who may sign in. No anonymous clients. Each roaster in MQTT_DEVICES (id:password, comma separated) signs in as its id and may use only roaster/<id>/# and the server's presence. A roaster added to MQTT_DEVICES later needs a restart. MQTT_PASSWORD is optional: nobody but the server uses it, and a random one is made at each start.
  • After a restart the broker's retained messages are gone: the roasters send their status again when they reconnect.
  • What it says about itself. Settings → About and roast status say the broker is built in, where it listens and which roasters may sign in.
  • User 999. The server runs as uid 999, so data/ must be writable by it (sudo chown 999:999 data). When it is not, the server stops at start and says so. On the host's network it cannot use a port below 1024: run the Modbus slave on 1502, not 502.

What it gives up against a separate broker is the server's last will. When the server stops, its broker stops with it, so a roaster sees the connection drop and stops its heater, as it should. A server that hangs while its broker still answers is not noticed. If that matters to you, run docker-compose.yml with Mosquitto instead (security).

Deploys wait for the roaster

A restart stops a profile drive and the server's autotune, drops the cooling after DROP (the fan-off at its end never comes) and, if it lasts longer than the firmware's server-offline timeout, cuts the heater mid-roast. So before an update, ask whether a roaster is busy:

docker exec roastwarden roast busy --wait 60   # waits up to 60 min, checking every 30 s
docker compose -f docker-compose.single.yml pull
docker compose -f docker-compose.single.yml up -d

A roaster is busy during a profile drive, the server's autotune, cooling, while its heater is on, or with an open roast and beans at 50 °C or more. The simulated roaster never counts. roast busy exits 1 while a real roaster is busy, so a script can wait on it the same way (GET /api/busy). A roast started in the few seconds between the check and the restart is not caught.

The database migrates itself at start. To keep a copy from just before, run docker exec roastwarden roast backup first.

Label printers and mDNS

Label Studio → Printers can look for IPP printers by mDNS. Multicast DNS stays on one network segment, and Docker's default bridge is a segment of its own, so the container finds printers only on the host's network (the one container uses it) or a macvlan on the printer's subnet. Otherwise type the printer's address (ipp://<printer>:631/ipp/print); do not move a server to host networking for this alone. More in bag labels.

Label designs and fonts

Your own label designs and fonts live beside the database, in the data volume: /app/data/labels/designs/<id>.typ and /app/data/labels/fonts/. They are read at each render, so copying one in needs no restart:

docker exec roastwarden mkdir -p /app/data/labels/designs /app/data/labels/fonts
docker cp mine.typ roastwarden:/app/data/labels/designs/

They are files, not database rows: the nightly backup does not copy them. Keep the originals.

Backups

The server backs its database up by itself. Every night it writes a consistent copy, one self-contained file named roastwarden-<YYYYMMDD-HHMMSS>Z.db, into data/backups/ beside the database (ROASTWARDEN_BACKUP_DIR moves it).

Settings → Backups → Schedule:

SettingDefaultWhat it does
Keep14The newest copies kept. 0 turns the nightly copy off; Back up now still works.
Hour (UTC)3When the nightly copy runs; the page shows the hour in your time.

The page shows the last copy (or its error), the next run and every copy kept. A roast in progress delays the nightly copy; a server that was down at that hour makes it a few minutes after it starts. Older copies are deleted only after a new one is in place.

A copy holds everything: roasts with their telemetry, profiles, beans and stock, settings, alarms, and the secrets kept there (token hashes, the Web Push private key, the notification channels' tokens). Keep copies as private as the database. The owner key is not in it: it is in .env, or in data/owner.key when the server made it.

Off the host. The copies sit on the same disk as the database, so a dead SD card takes them with it. Copy them elsewhere now and then. The folder belongs to the container's user, so go through Docker: docker exec roastwarden ls /app/data/backups, then docker cp roastwarden:/app/data/backups/<name> . on the host. A host backup or snapshot works too: a snapshot of a finished copy is a good copy, a snapshot of the live database only a crash copy.

From the command line: roast backups lists the copies and the last attempt; docker exec roastwarden roast backup writes a copy now, with no token or server needed.

Restore. In Settings → Backups, Check proves a copy is whole, is a Roastwarden database and needs no migration this version does not know, and says how many roasts, profiles and beans it holds. Restore… runs the same check and stages the copy; nothing changes until the server next starts, and Cancel restore drops it. Then:

docker restart roastwarden

At start the server moves the current database into data/before-restore-<time>/, puts the copy in its place and runs migrations forward; its log says database restored from a backup. Delete before-restore-* once you are happy.

The same from the command line: docker exec roastwarden roast restore <name> --check, then docker exec roastwarden roast restore <name> (it checks and stages the copy), and restart. A copy older than the running release is fine: the start runs the missing migrations. A copy from a newer release is refused: restore it with the release that made it. A restore also brings back the copy's tokens, which is why only the owner key may restore.