Intelligent MTG Commander/EDH deck builder with theme detection and automated card suggestions
10K+
Spin up the MTG Python Deckbuilder inside containers. The image defaults to the Web UI; switch to the CLI/headless runner by flipping environment variables. All commands assume Windows PowerShell.
Windows note: PowerShell uses
${PWD}for the current directory in volume mounts; CMD uses%cd%. The commands below use PowerShell syntax. If you are on Linux/macOS, replace${PWD}with$(pwd)and backtick line continuations with backslashes.
web service in detached mode:docker compose up --build --no-deps -d web
Open http://localhost:8080 to use the browser experience. First launch seeds data, downloads the latest card catalog, and tags cards automatically (WEB_AUTO_SETUP=1, WEB_TAG_PARALLEL=1, WEB_TAG_WORKERS=4 in docker-compose.yml).
Stop or restart the service when you're done:
docker compose stop web
docker compose start web
Then open http://localhost:8080
Volumes are the same as the CLI service, so deck exports/logs/configs persist in your working folder.
The app serves a favicon at /favicon.ico and exposes a health endpoint at /healthz.
Compare view offers a Copy summary button to copy a plain-text diff of two runs. The sidebar has a subtle depth shadow for clearer separation.
Web UI feature highlights:
Virtualized lists and lazy images (opt‑in)
WEB_VIRTUALIZE=1 to enable virtualization in Step 5 grids/lists and the Owned library for smoother scrolling on large sets.services:
web:
environment:
- WEB_VIRTUALIZE=1
docker run --rm -p 8080:8080 `
-e WEB_VIRTUALIZE=1 `
-v "${PWD}/deck_files:/app/deck_files" `
-v "${PWD}/logs:/app/logs" `
-v "${PWD}/csv_files:/app/csv_files" `
-v "${PWD}/owned_cards:/app/owned_cards" `
-v "${PWD}/config:/app/config" `
-e SHOW_DIAGNOSTICS=1 ` # optional: enables diagnostics tools and overlay
mwisnowski/mtg-python-deckbuilder:latest `
bash -lc "cd /app && uvicorn code.web.app:app --host 0.0.0.0 --port 8080"
Enable internal diagnostics and a read-only logs viewer with environment flags.
SHOW_DIAGNOSTICS=1 — adds a Diagnostics nav link and /diagnostics toolsSHOW_LOGS=1 — enables /logs and /status/logs?tail=200Per-face MDFC snapshot (opt-in)
DFC_PER_FACE_SNAPSHOT=1 — write merged MDFC face metadata to logs/dfc_per_face_snapshot.json; disable parallel tagging (WEB_TAG_PARALLEL=0) if you need the snapshot during setup.DFC_PER_FACE_SNAPSHOT_PATH=/app/logs/custom_snapshot.json — optional path override for the snapshot artifact.When enabled:
/logs supports an auto-refresh toggle with interval, a level filter (All/Error/Warning/Info/Debug), and a Copy button to copy the visible tail./status/sys returns a simple system summary (version, uptime, UTC server time, and feature flags) and is shown on the Diagnostics page when SHOW_DIAGNOSTICS=1.v on pages with virtualized grids to toggle per-grid overlays and a global summary bubble.Compose example (web service):
environment:
- SHOW_LOGS=1
- SHOW_DIAGNOSTICS=1
docker run --rm -p 8080:8080 `
-v "${PWD}/deck_files:/app/deck_files" `
-v "${PWD}/logs:/app/logs" `
-v "${PWD}/csv_files:/app/csv_files" `
-v "${PWD}/config:/app/config" `
-v "${PWD}/owned_cards:/app/owned_cards" `
mwisnowski/mtg-python-deckbuilder:latest
The web service now runs the MDFC merge by default. Set DFC_COMPAT_SNAPSHOT=1 on the web service when you need the legacy unmerged compatibility snapshot (csv_files/compat_faces/). Combine this with python -m code.scripts.refresh_commander_catalog --compat-snapshot inside the container to regenerate the commander files before smoke testing.
Follow the QA steps in docs/qa/mdfc_staging_checklist.md after toggling the flag.
Compose example:
services:
web:
environment:
- DFC_COMPAT_SNAPSHOT=1
Verify the refresh inside the container:
docker compose run --rm web bash -lc "python -m code.scripts.refresh_commander_catalog"
Downstream consumers can diff csv_files/compat_faces/commander_cards_unmerged.csv against historical exports during the staging window.
First-time setup or stale data triggers card tagging. The web service uses parallel workers by default.
Use the homepage “Run a JSON Config” button or run the same flow in-container:
docker compose run --rm `
-e APP_MODE=cli `
-e DECK_MODE=headless `
-e DECK_CONFIG=/app/config/deck.json `
web
APP_MODE=cli routes the entrypoint to the CLI menu.DECK_MODE=headless skips prompts and calls headless_runner.config/ so both the UI and CLI can pick them up.ENABLE_PARTNER_MECHANICS=1 and pass --secondary-commander or --background (mutually exclusive) to layer partners/backgrounds into headless runs; Partner With and Doctor/Doctor’s Companion pairings auto-resolve (with opt-out), and --dry-run echoes the resolved pairing for verification.ENABLE_PARTNER_SUGGESTIONS=1 (and ensure config/analytics/partner_synergy.json exists) to expose ranked pairings in the UI and API.Override counts, theme tags, or include/exclude lists by setting the matching environment variables before running the container (see “Environment variables” below).
The homepage “Initial Setup” tile appears when SHOW_SETUP=1 (enabled in compose). It re-runs:
To force a rebuild from the host:
docker compose run --rm --entrypoint bash web -lc "python -m code.file_setup.setup"
Add --entrypoint bash ... "python -m code.scripts.refresh_commander_catalog" when you only need the commander catalog (with MDFC merge and optional compatibility snapshot).
Store .txt or .csv lists in owned_cards/ (mounted to /app/owned_cards). The Web UI uses them for:
WEB_VIRTUALIZE=1).Use /owned to upload files and export enriched lists. These files persist through the owned_cards volume.
SHOW_COMMANDERS=1 exposes the commander browser tile.
csv_files/commander_cards.csv.docker compose run --rm --entrypoint bash web -lc "python -m code.scripts.refresh_commander_catalog"
Pass --compat-snapshot if you also need an unmerged compatibility CSV under csv_files/compat_faces/.
The Finished Decks page reads the deck_files/ volume for completed builds:
Ensure the deck exports volume remains mounted so these artifacts survive container restarts.
Enable cost-aware deck building with ENABLE_BUDGET_MODE=1 (default). A per-card budget ceiling is entered in the New Deck modal.
BUDGET_POOL_TOLERANCE (default 0.15) sets fractional headroom above the ceiling before exclusion (e.g. 0.15 = 15% overhead). Read at build time; not surfaced in the diagnostics panel.PRICE_LAZY_REFRESH=1 (default) refreshes stale per-card prices in the background. PRICE_AUTO_REFRESH=1 rebuilds the full cache daily at 01:00 UTC.PRICE_STALE_WARNING_HOURS (default 24) controls when a cached price shows a stale indicator. Set to 0 to disable.Set ALLOW_MUST_HAVES=1 (default) to enable include/exclude enforcement.
SHOW_MUST_HAVE_BUTTONS=1 surfaces the Must Include and Must Exclude buttons and quick-add UI in Step 5 (hidden by default).must_include and must_exclude keys.The Themes browser exposes the merged theme catalog with search, filters, and diagnostics.
ENABLE_THEMES=1 keeps the selector visible.WEB_THEME_PICKER_DIAGNOSTICS=1 unlocks uncapped synergies, extra metadata, and /themes/metrics.docker compose run --rm --entrypoint bash web -lc "python -m code.scripts.build_theme_catalog"
Advanced options (e.g., EDITORIAL_* variables) live in .env.example.
Enable the Surprise/Reroll flow by setting:
RANDOM_MODES=1 to expose backend random endpoints.RANDOM_UI=1 to show the Random Build tile.RANDOM_MAX_ATTEMPTS, RANDOM_TIMEOUT_MS, RANDOM_PRIMARY_THEME, RANDOM_SEED, and auto-fill flags.Headless parity is available by pairing APP_MODE=cli with DECK_MODE=headless and the same random variables.
SHOW_DIAGNOSTICS=1 unlocks /diagnostics for system summaries, feature flags, and performance probes. Highlights:
/healthz returns {status, version, uptime_seconds} for external monitoring.v on pages with virtualized grids (when WEB_VIRTUALIZE=1) to toggle the range overlay.WEB_AUTO_ENFORCE=1 (optional) applies bracket enforcement automatically after each build.SHOW_LOGS=1 enables the logs tile and /logs interface:
/status/logs?tail=200 offers a lightweight JSON endpoint.logs/ on the host; rotate or archive them as needed.See .env.example for the full catalog. Common knobs:
| Variable | Default | Purpose |
|---|---|---|
APP_MODE | web | Switch between Web UI (web) and CLI (cli). |
DECK_MODE | (unset) | headless auto-runs the headless builder when the CLI starts. |
DECK_CONFIG | /app/config/deck.json | JSON config file or directory (auto-discovery). |
HOST / PORT / WORKERS | 0.0.0.0 / 8080 / 1 | Uvicorn binding when APP_MODE=web. |
| Variable | Default | Purpose |
|---|---|---|
ENABLE_PARTNER_MECHANICS | 0 | Unlock partner/background commander inputs for headless runs and Step 2 of the web UI. |
ENABLE_PARTNER_SUGGESTIONS | 0 | Serve partner/background/Doctor suggestion chips based on config/analytics/partner_synergy.json (auto-regenerated when missing; override path with PARTNER_SUGGESTIONS_DATASET). |
| Variable | Default | Purpose |
|---|---|---|
SHOW_SETUP | 1 | Show the Initial Setup card. |
SHOW_LOGS | 1 | Enable the View Logs tile and endpoints. |
SHOW_DIAGNOSTICS | 1 | Enable Diagnostics tools and overlays. |
SHOW_COMMANDERS | 1 | Expose the commander browser. |
ENABLE_THEMES | 1 | Keep the theme selector and themes explorer visible. |
SHOW_THEME_QUALITY_BADGES | 1 | Show quality badges in theme catalog (editorial quality score). |
SHOW_THEME_POOL_BADGES | 1 | Show pool size badges in theme catalog (total available cards). |
SHOW_THEME_POPULARITY_BADGES | 1 | Show popularity badges in theme catalog (usage frequency). |
SHOW_THEME_FILTERS | 1 | Show filter dropdowns and quick chips in theme catalog. |
WEB_VIRTUALIZE | 1 | Opt-in to virtualized lists/grids for large result sets. |
ALLOW_MUST_HAVES | 1 | Enable include/exclude enforcement in Step 5. |
SHOW_MUST_HAVE_BUTTONS | 0 | Surface the must include/exclude buttons and quick-add UI (requires ALLOW_MUST_HAVES=1). |
ENABLE_BUDGET_MODE | 1 | Enable budget mode controls (per-card ceiling, soft enforcement) and price display throughout the builder. |
BUDGET_POOL_TOLERANCE | 0.15 | Fractional overhead above the per-card ceiling before a card is excluded from the selection pool (e.g. 0.15 = 15%). |
PRICE_AUTO_REFRESH | 0 | Rebuild the price cache automatically once daily at 01:00 UTC. |
PRICE_LAZY_REFRESH | 1 | Refresh per-card prices in the background when they are more than 7 days old (uses Scryfall named-card API with rate-limit delay). |
PRICE_STALE_WARNING_HOURS | 24 | Hours before a cached price is marked stale with a visual indicator. Set to 0 to disable. |
THEME | dark | Initial UI theme (system, light, or dark). |
WEB_STAGE_ORDER | new | Build stage execution order: new (creatures→spells→lands) or legacy (lands→creatures→spells). |
WEB_IDEALS_UI | slider | Ideal counts interface: slider (range inputs with live validation) or input (text boxes with placeholders). |
ENABLE_CARD_DETAILS | 0 | Show card detail pages with similar card recommendations at /cards/<name>. |
SIMILARITY_CACHE_ENABLED | 1 | Use pre-computed similarity cache for fast card detail pages. |
ENABLE_BATCH_BUILD | 1 | Enable Build X and Compare feature (build multiple decks in parallel and compare results). |
| Variable | Default | Purpose |
|---|---|---|
RANDOM_MODES | (unset) | Enable random build endpoints. |
RANDOM_UI | (unset) | Show the Random Build homepage tile. |
RANDOM_MAX_ATTEMPTS | 5 | Retry budget for constrained random rolls. |
RANDOM_TIMEOUT_MS | 5000 | Per-attempt timeout in milliseconds. |
RANDOM_REROLL_THROTTLE_MS | 350 | Minimum ms between reroll requests (client guard). |
RANDOM_STRUCTURED_LOGS | 0 | Emit structured JSON logs for random builds. |
RANDOM_TELEMETRY | 0 | Enable lightweight timing/attempt counters. |
RANDOM_PRIMARY_THEME / RANDOM_SECONDARY_THEME / RANDOM_TERTIARY_THEME | (blank) | Override theme slots for random runs. |
RANDOM_SEED | (blank) | Deterministic seed. |
RANDOM_AUTO_FILL | 1 | Allow automatic backfill of missing theme slots. |
| Variable | Default | Purpose |
|---|---|---|
WEB_AUTO_SETUP | 1 | Auto-run data setup when artifacts are missing or stale. |
WEB_AUTO_REFRESH_DAYS | 7 | Refresh cards.csv if older than N days. |
WEB_TAG_PARALLEL | 1 | Use parallel workers during tagging. |
WEB_TAG_WORKERS | 4 | Worker count for parallel tagging. |
CACHE_CARD_IMAGES | 0 | Download card images to card_files/images/ (1=enable, 0=fetch from API on demand). Requires ~3-6 GB. |
WEB_AUTO_ENFORCE | 0 | Re-export decks after auto-applying compliance fixes. |
WEB_THEME_PICKER_DIAGNOSTICS | 1 | Enable theme diagnostics endpoints. |
THEME_MIN_CARDS | 5 | Minimum card count threshold for themes. Themes with fewer cards are stripped from YAML catalogs, JSON picker files, and parquet metadata during setup/tagging. Set to 1 to keep all themes. |
| Variable | Default | Purpose |
|---|---|---|
CSV_FILES_DIR | /app/csv_files | Point the app at an alternate dataset (e.g., test snapshots). |
DECK_EXPORTS | /app/deck_files | Override where the web UI looks for exports. |
OWNED_CARDS_DIR / CARD_LIBRARY_DIR | /app/owned_cards | Override owned library directory. |
CARD_INDEX_EXTRA_CSV | (blank) | Inject a synthetic CSV into the card index for testing. |
| Variable | Default | Purpose |
|---|---|---|
DECK_ADDITIONAL_THEMES | (blank) | Comma/semicolon separated list of supplemental themes for headless builds (JSON exports also include the camelCase userThemes alias and themeCatalogVersion metadata; either alias is accepted on import). |
THEME_MATCH_MODE | permissive | Controls fuzzy theme resolution (strict blocks unresolved inputs). |
| Variable | Default | Purpose |
|---|---|---|
RATE_LIMIT_ENABLED | 0 | Enable server-side rate limiting for random endpoints. |
RATE_LIMIT_WINDOW_S | 10 | Rolling window in seconds. |
RATE_LIMIT_RANDOM | 10 | Max random attempts per window. |
RATE_LIMIT_BUILD | 10 | Max full builds per window. |
RATE_LIMIT_SUGGEST | 30 | Max suggestion calls per window. |
Advanced editorial and theme-catalog knobs (EDITORIAL_*, SPLASH_ADAPTIVE, etc.) are documented inline in docker-compose.yml and .env.example.
| Host path | Container path | Contents |
|---|---|---|
deck_files/ | /app/deck_files | CSV/TXT exports, summary JSON, compliance reports. |
logs/ | /app/logs | Application logs and taxonomy snapshots. |
csv_files/ | /app/csv_files | Card datasets, commander catalog, tagging flags. |
config/ | /app/config | JSON configs, bracket policy, card list overrides. |
owned_cards/ | /app/owned_cards | Uploaded inventory files for owned-only flows. |
Run ad-hoc tasks by overriding the entrypoint:
# Theme catalog rebuild
docker compose run --rm --entrypoint bash web -lc "python -m code.scripts.build_theme_catalog"
# Snapshot taxonomy (writes logs/taxonomy_snapshots/)
docker compose run --rm --entrypoint bash web -lc "python -m code.scripts.snapshot_taxonomy"
# Preview the MDFC commander diff
docker compose run --rm --entrypoint bash web -lc "python -m code.scripts.preview_dfc_catalog_diff"
Use the --compat-snapshot or other script arguments as needed.
/healthz and /logs (enable with SHOW_LOGS=1), then inspect the logs/ volume./setup.RANDOM_MAX_ATTEMPTS or raise RANDOM_TIMEOUT_MS, and confirm your theme overrides are valid slugs via /themes/.csv_files/.tagging_complete.json to force a full rebuild on next start.Content type
Image
Digest
sha256:a2c295eca…
Size
317.8 MB
Last updated
2 months ago
docker pull mwisnowski/mtg-python-deckbuilder