Table of Contents
Setup
Requirements
- Docker (the image bundles the OCR models, OpenCV, and the web app).
- A Grocy instance with an API key (Grocy -> Settings -> Manage API keys).
- A local Ollama with a text model pulled —
qwen3:8bis the default and a good balance; anything that follows JSON instructions works. CPU is fine; a GPU is faster. - Optional: an IMAP mailbox if you want the emailed-receipt path (the photo path needs no mailbox).
The OCR for photographed receipts runs on CPU inside the container — no GPU required.
Build the image
There's no prebuilt image (and some stack managers can't build), so build it on any Docker host:
git clone https://git.ourserver.party/xenarathon/provisions.git
cd provisions
docker build -t provisions:latest .
Run
docker run -d --name provisions --restart unless-stopped \
-p 8092:8000 \
-v provisions-data:/app/data \
-e GROCY_URL=http://your-grocy-host:9283/api \
-e GROCY_API_KEY=xxxxxxxx \
-e OLLAMA_URL=http://your-ollama-host:11434 \
-e OLLAMA_MODEL=qwen3:8b \
provisions:latest
Open http://your-host:8092. That's enough for the photo path. For the email path, add the
IMAP variables below and set SCAN_INTERVAL.
The named volume provisions-data holds the staging database, the learned alias table, and saved
source photos — keep it across redeploys.
Enabling the emailed-receipt path
Add these and the app will poll your mailbox:
-e IMAP_HOST=your-imap-host -e IMAP_PORT=143 \
-e IMAP_USER=you@example.com -e IMAP_PASS=app-password \
-e RECEIPT_FOLDERS=Folders/Receipts \
-e SCAN_INTERVAL=900
Create a dedicated mail folder, then add a filter that files store receipts into it (so only receipts are scanned). Point your store loyalty / delivery / Amazon account emails at that mailbox so receipts arrive natively.
Configuration reference
| Variable | Default | What it's for |
|---|---|---|
GROCY_URL |
http://localhost:9283/api |
Grocy REST API base |
GROCY_API_KEY |
— | Grocy API key (required to read catalog + write stock) |
OLLAMA_URL |
http://localhost:11434 |
Ollama host. Comma-separated = fallback chain (e.g. a fast GPU host, then an always-on CPU host) |
OLLAMA_MODEL |
qwen3:8b |
Text model for cleaning names, categorizing, matching, and structuring OCR text |
OCR_LOW_CONF |
0.6 |
Lines below this OCR confidence get the OCR? flag in review |
IMAP_HOST |
protonmail-bridge |
Mailbox host for emailed receipts |
IMAP_PORT |
143 |
IMAP port |
IMAP_USER / IMAP_PASS |
— | Mailbox credentials |
RECEIPT_FOLDERS |
Folders/Receipts |
Comma-separated IMAP folders to scan |
SCAN_INTERVAL |
0 |
Seconds between background mailbox polls (0 = off; use the Scan button manually) |
STORE_DB / ALIAS_FILE / HUEY_DB / PHOTO_DIR |
under data/ |
Data paths — leave as-is so the volume persists them |
Behind a reverse proxy / SSO
Photo OCR runs as a background job and the browser polls for the result, so requests stay short even when OCR takes a while — this keeps you under proxy timeouts (e.g. Cloudflare's ~100s limit).
If you install it as a home-screen app on iOS behind an auth proxy, keep the manifest
display as browser (not standalone) so the app shares the browser's login session — a
standalone iOS web app gets an isolated cookie jar and every API call bounces to login.