Requirements and first run
The community that uses a judgebot runs it, with its own Discord application, model key
and spend cap. A complete instance is three containers from one compose file: db
(Postgres with pgvector), api (the web page and HTTP API) and bot (Discord). A fourth,
refresh, runs nightly from cron to keep cards and rules current. An optional
cloudflared publishes the API without an open port. You can stop after api and never
touch Discord. The bot is the last thing to add.
Requirements
Section titled “Requirements”- A host that stays on, with Docker and the compose plugin. Running takes about 200 MB of
RAM across the three containers. Building the image takes ~4 GB and a lot of CPU. A
low-powered host pulls the CI-built image instead (
docker compose pull). - No Rust. The image carries
judge-ingestas itsrefreshservice, so the data loads run in a container. (Working from source is Development setup. A binary run on the host verifies HTTPS against the system’s CA certificates,ca-certificateson Debian and Ubuntu, and without them it refuses to start.) - A model provider. With
.envalone that is Anthropic’s API (ANTHROPIC_API_KEY). Ajudge.tomlchooses anything else. - Optionally a Voyage AI key for the semantic-search leg, or an embedding model on an OpenAI-compatible server. Without one the bot runs on the category map and full-text legs and logs a warning at startup.
- For Discord, an application of your own and its bot token. Create the Discord app walks through the developer portal with links into Discord’s documentation.
Order of operations
Section titled “Order of operations”cp .env.example .envand fill in the keys. Every knob is documented in the file and in the configuration reference. Two are contacts, not keys: the bot requiresJUDGE_OPERATOR_DISCORD(your Discord username) andjudge-apirequiresJUDGE_OPERATOR_EMAIL(a support address).docker compose up -d db. Postgres publishes on localhost:5432 (loopback only). If something on the host already has that port, setDB_PORTin.envand change the port inDATABASE_URLto match.docker compose run --rm refresh initloads everything: the schema, the cards, the current rules, the alias and note lists, embeddings if you have an embedder, and the emoji ifDISCORD_TOKENis already set. The first embed pays the embedder once for every rule and glossary entry, a few cents on Voyage. Afterwards only changed rules are re-embedded.initis safe to run again.docker compose up -d api. Open http://localhost:8787 and ask a question. This runs the full pipeline, so it is a good place to check the model setup before Discord is involved.apiandbotboth apply pending migrations at startup unlessJUDGE_AUTO_MIGRATE=false.- Create the Discord app, then
docker compose up -d bot. Then rundocker compose run --rm refresh emojionce, so answers show mana symbols as pictures instead of{W}. - Schedule
scripts/refresh-data.shnightly andscripts/backup-db.shweekly. The deployment runbook has the cron lines.
One step at a time
Section titled “One step at a time”init is these, in this order, and each can be run by itself:
docker compose run --rm refresh migratedocker compose run --rm refresh cardsdocker compose run --rm refresh rules latestdocker compose run --rm refresh aliases # the list built into the binarydocker compose run --rm refresh notesdocker compose run --rm refresh embed # with an embedder configureddocker compose run --rm refresh emoji # once the Discord app existsaliases and notes take a file instead when you keep your own list: mount it and name
it, from the repository root. Each replaces its table, and so does init with the
built-in lists, so load your own after any init.
docker compose run --rm -v ./data:/data:ro refresh aliases /data/aliases.yamlEmbedding width
Section titled “Embedding width”The schema is created with vector(1024) columns, Voyage’s width. If your embedder
produces another width (OpenAI’s text-embedding-3-small is 1536), init handles it: on
a database that holds no vectors yet it takes the configured embedder’s width. Loading
step by step, run judge-ingest reembed --yes instead of embed the first time. It retypes the columns and
records the embedding space before filling them. The bot refuses to mix two spaces. On a
mismatch it logs an error naming both and runs with the vector leg dark.
Behind a domain
Section titled “Behind a domain”The deployment runbook publishes api through a Cloudflare Tunnel with edge rate
limiting in front of the anonymous page. Any reverse proxy works. Keep
API_CLIENT_IP=peer unless Cloudflare is the only route to the origin. The
security page gives the reason.
MTG Judgebot is unofficial Fan Content permitted under the Fan Content Policy. Not approved or endorsed by Wizards of the Coast. Portions of the materials used are property of Wizards of the Coast. ©Wizards of the Coast LLC.
The Comprehensive Rules come from Wizards of the Coast. Card data, rulings and card symbols come from Scryfall, which is not affiliated with this project. Rule links go to the Yawgatog mirror. License and attribution.