Skip to content

Configuration reference

Pierre edited this page Oct 29, 2026 · 1 revision

This reference contains every assignment documented in the reviewed .env.example, including commented optional settings. Values shown are defaults or examples from that file, not safe production secrets. Provider-specific values and source credentials require your own configuration.

How configuration is loaded

Docker Compose reads .env for substitution and passes it to API and worker with env_file. The Compose files explicitly construct TCG_DATABASE_URL from POSTGRES_PASSWORD. Pydantic reads TCG_* variables and, for local processes, the working directory's .env. Environment values override dotenv values. Unknown dotenv keys are ignored by the base settings object.

Provider-specific settings and plugin tokens are loaded by their own classes/helpers. Do not add undocumented unprefixed names and expect the backend to read them. Lists use JSON, for example ["en","fr"]; booleans use true/false. Compose also interprets dollar signs, so use proper dotenv escaping/quoting for secrets and URL-encode non-hexadecimal database passwords when constructing a URL yourself.

After changes use docker compose up -d api worker; restarting the old containers does not change their attached environment. Persisted synchronization schedules override environment schedule defaults until reset through the administration page/API. Bootstrap administrator settings apply only to a database with no users.

For local HTTP set TCG_COOKIE_SECURE=false; public HTTPS normally uses true. Never commit your real .env.

Deployment and application variables

POSTGRES_PASSWORD

PostgreSQL password, used by the db service and in the server's database URL.

POSTGRES_PASSWORD=change-me

TCG_SECRET_KEY

Signs session tokens. At least 32 random characters (the server refuses to start otherwise). Changing it signs everybody out. Generate with: python -c "import secrets; print(secrets.token_urlsafe(48))"

TCG_SECRET_KEY=change-me

ZUKAN_PORT

Host port the web UI and API are published on.

ZUKAN_PORT=8000

ZUKAN_TAG

Image tag pulled by docker-compose.yml: latest, a version (0.1.0) or a dev build (dev-<branch>). Ignored by docker-compose-dev.yml, which builds from the checkout.

ZUKAN_TAG=latest

TCG_NETWORK_MTU

MTU of the containers' network. Below 1500 so the server also works when the host goes through a VPN tunnel. Lower it for tunnels with less room; the network is recreated on docker compose down && docker compose up -d.

TCG_NETWORK_MTU=1420

TCG_PUBLIC_URL

Public URL where the site is reached. SSO redirect URIs (<url>/api/auth/oidc/<provider>/callback) and the SSO logout return address derive from it: set it when SSO is enabled.

TCG_PUBLIC_URL=http://localhost:5173

TCG_COOKIE_SECURE

Session cookies are only sent over HTTPS. Set to false when the web UI is served over plain HTTP (local testing, LAN without TLS), otherwise web sign-in does not stick. The Android app uses tokens and is not affected.

TCG_COOKIE_SECURE=true

TCG_ACCESS_TOKEN_TTL_SECONDS

Lifetime of access tokens (seconds) and of refresh tokens, i.e. how long a session stays signed in without activity (seconds).

TCG_ACCESS_TOKEN_TTL_SECONDS=3600

TCG_REFRESH_TOKEN_TTL_SECONDS

TCG_REFRESH_TOKEN_TTL_SECONDS=7776000

TCG_FORWARDED_ALLOW_IPS

Reverse proxies (comma-separated addresses or networks, * for any) whose X-Forwarded-For / X-Forwarded-Proto headers are trusted. Set it to your proxy's address as the container sees it (often the Docker network gateway, e.g. 172.18.0.1) when Zukan runs behind one, so sign-in limits count real client addresses. Others cannot fake them.

TCG_FORWARDED_ALLOW_IPS=127.0.0.1

TCG_LOGIN_MAX_FAILURES

Sign-in limits (0: none). Failed password sign-ins allowed per account name and per client address within the window (seconds), then refresh and SSO requests per address and minute. Past them the API answers 429 with Retry-After; counters reset on restart.

TCG_LOGIN_MAX_FAILURES=10

TCG_LOGIN_IP_MAX_FAILURES

TCG_LOGIN_IP_MAX_FAILURES=50

TCG_LOGIN_WINDOW_SECONDS

TCG_LOGIN_WINDOW_SECONDS=900

TCG_AUTH_REQUESTS_PER_MINUTE

TCG_AUTH_REQUESTS_PER_MINUTE=60

TCG_MAX_REQUEST_BYTES

Largest request accepted (bytes); CSV imports are up to 5 MB.

TCG_MAX_REQUEST_BYTES=6291456

TCG_DEFAULT_ADMIN_USERNAME

Administrator created on start while the database has no user, so the first sign-in works. Change the password after signing in. Empty password: not created.

TCG_DEFAULT_ADMIN_USERNAME=admin

TCG_DEFAULT_ADMIN_PASSWORD

TCG_DEFAULT_ADMIN_PASSWORD=changeme

TCG_LOCAL_LOGIN_ENABLED

Username/password sign-in. Turn off once SSO works, keeping an administrator able to sign in through SSO (recovery: set back to true and run zukan create-admin). With it off and one SSO provider, the login page goes straight to the provider. Local and SSO accounts are separate: an SSO login never joins a local account. With it off, administrators cannot create local accounts (SSO accounts are created at first sign-in).

TCG_LOCAL_LOGIN_ENABLED=true

TCG_ADMIN_PASSWORD

Password for zukan create-admin NAME (create or reset an administrator) without the interactive prompt. Pass it on the command line rather than keeping it here: docker compose exec -e TCG_ADMIN_PASSWORD=... api zukan create-admin NAME

TCG_ADMIN_PASSWORD=

TCG_OIDC_PROVIDERS

SSO providers, as a JSON list of names (lowercase letters, digits, - and _). A name appears in URLs (/api/auth/oidc/<name>/callback). Empty: no SSO button.

TCG_OIDC_PROVIDERS=["home"]

TCG_OIDC_HOME_DISPLAY_NAME

Each provider is then configured by TCG_OIDC_<NAME>_* variables, <NAME> being the name in uppercase with - replaced by _. Example for "home":

Login button label (default: the name).

TCG_OIDC_HOME_DISPLAY_NAME=Home SSO

TCG_OIDC_HOME_ISSUER

Issuer URL (mandatory), discovered through <issuer>/.well-known/openid-configuration.

TCG_OIDC_HOME_ISSUER=https://sso.example.com/realms/home

TCG_OIDC_HOME_CLIENT_ID

Confidential client used by the web UI (mandatory) and its secret.

TCG_OIDC_HOME_CLIENT_ID=tcg-web

TCG_OIDC_HOME_CLIENT_SECRET

TCG_OIDC_HOME_CLIENT_SECRET=change-me

TCG_OIDC_HOME_MOBILE_CLIENT_ID

Public client (PKCE) used by the Android app. Unset: no SSO in the app.

TCG_OIDC_HOME_MOBILE_CLIENT_ID=tcg-android

TCG_OIDC_HOME_SCOPES

Requested scopes, JSON list. Authelia also needs "groups".

TCG_OIDC_HOME_SCOPES=["openid","profile","email"]

TCG_OIDC_HOME_USERNAME_CLAIM

Claims holding the username and the groups.

TCG_OIDC_HOME_USERNAME_CLAIM=preferred_username

TCG_OIDC_HOME_GROUPS_CLAIM

TCG_OIDC_HOME_GROUPS_CLAIM=groups

TCG_OIDC_HOME_ADMIN_GROUPS

Members of these groups are administrators, others users, synced on each SSO login. Empty: roles are managed in Zukan.

TCG_OIDC_HOME_ADMIN_GROUPS=[]

TCG_OIDC_HOME_ALLOWED_GROUPS

Only members of these groups may sign in. Empty: everybody the IdP accepts.

TCG_OIDC_HOME_ALLOWED_GROUPS=[]

TCG_OIDC_HOME_AUTO_CREATE_USERS

Create an account on first SSO sign-in. Off: only SSO accounts created before sign in.

TCG_OIDC_HOME_AUTO_CREATE_USERS=true

TCG_OIDC_HOME_ACCOUNT_URL

Page where users change their password and email at the provider, opened by the "Edit my profile" button in Zukan's settings (no button when unset and not guessed). Default: guessed for Keycloak (<issuer>/account) and Authentik (/if/user/#/settings); other providers need it.

TCG_OIDC_HOME_ACCOUNT_URL=https://sso.example.com/realms/home/account

TCG_CATALOG_INTERVAL_HOURS

Administrators set when the worker runs on the Data sync page (every few hours, or at set times on chosen days). The values below are the defaults until a schedule is saved there.

Hours between catalog refreshes (large downloads, new sets are rare). After an upgrade that changes how a game's catalog is stored, that catalog is refreshed at once.

TCG_CATALOG_INTERVAL_HOURS=168

TCG_SYNC_INTERVAL_HOURS

Hours between price and owned card image refreshes.

TCG_SYNC_INTERVAL_HOURS=24

TCG_REFRESH_INTERVAL_HOURS

Hours between refreshes of outdated cards (missed by the last catalog refresh, or with missing image or details), fetched again one by one. Opening such a card in the web UI also fetches it again (at most once an hour).

TCG_REFRESH_INTERVAL_HOURS=48

TCG_WORKER_GAMES

Games to refresh, as a JSON list (e.g. ["mtg","sorcery"]). Empty: every installed game. Game ids: mtg, pokemon, pokemon-asia, pokemon-china, sorcery, lorcana, riftbound, yugioh, yugioh-ocg. "pokemon" or "yugioh" listed without the other games of its family means all of them (one game before 0.17.0).

TCG_WORKER_GAMES=[]

TCG_SCHEDULE_TIMEZONE

Default time zone of the Data sync page's set times (IANA name).

TCG_SCHEDULE_TIMEZONE=UTC

TCG_USER_AGENT

User-Agent sent to card data APIs and SSO providers. Default: Zukan/<server version>.

TCG_USER_AGENT=Zukan/0.20.1

TCG_ANDROID_RELEASE_REPO

GitHub repository whose Android releases (android-vX.Y.Z) the web UI offers for download and the app's update check reads. Empty: no download link, no update check.

TCG_ANDROID_RELEASE_REPO=FoxOps/zukan

TCG_MAINTENANCE_ENABLED

Maintenance page while a catalog refresh slows the server down: when API answers take longer than TCG_MAINTENANCE_LATENCY_MS (median over 20 s) during a catalog refresh, users see a maintenance page (the API answers them 503) until answers are fast again for 30 s. Administrators keep access, with a notice. Runs started on the Data sync page are run by the worker, never by the API.

TCG_MAINTENANCE_ENABLED=true

TCG_MAINTENANCE_LATENCY_MS

TCG_MAINTENANCE_LATENCY_MS=500

TCG_MTG_CATALOG_BULK

MTG catalog: all_cards (~400 MB download, translated names needed to scan non-English cards) or default_cards (~80 MB, English names only).

TCG_MTG_CATALOG_BULK=all_cards

TCG_POKEMON_LANGUAGES

Pokémon is three games, one per publisher and market: Pokémon TCG (international: English, French, German, Italian, Spanish, Portuguese), Pokémon TCG Asia (Japanese, Korean, Traditional Chinese, Thai, Indonesian) and Pokémon TCG China (Simplified Chinese). A set listed in several languages is fetched in full once (English, else Japanese, Traditional Chinese, Thai, Indonesian); the others add names and images. TCGdex languages of the international game:

TCG_POKEMON_LANGUAGES=["en","fr","de","it","es","pt"]

TCG_POKEMON_ASIA_LANGUAGES

TCGdex languages of the Asia game (~40k cards fetched one by one). Unset: the Asian languages of TCG_POKEMON_LANGUAGES (it held both before 0.17.0), else these. TCGdex also has "ko" and "zh-cn", nearly empty: Korean and Simplified Chinese come from the datasets below.

TCG_POKEMON_ASIA_LANGUAGES=["ja","zh-tw","th","id"]

TCG_POKEMON_KOREAN

Korean cards (Asia game) from github.com/type-null/PTCG-database (~33 MB per catalog sync) and mainland China cards (China game) from github.com/duanxr/PTCG-CHS-Datasets (~10 MB; non-commercial use only). false skips them.

TCG_POKEMON_KOREAN=true

TCG_POKEMON_CHINESE

TCG_POKEMON_CHINESE=true

TCG_POKEMON_OFFICIAL_JP

Pictures of Japanese printings TCGdex has none for (its newest sets), from the official Japanese card search (pokemon-card.com), at most this many requests a day, one a second. Its card list is kept under TCG_IMAGE_DIR/cache; the first fill takes a few days. false skips it.

TCG_POKEMON_OFFICIAL_JP=true

TCG_POKEMON_OFFICIAL_JP_DAILY_REQUESTS

TCG_POKEMON_OFFICIAL_JP_DAILY_REQUESTS=300

TCG_POKEMON_OFFICIAL_ASIA

Thai, Indonesian and Traditional Chinese cards from the official Asian card search (asia.pokemon-card.com) and Korean cards from Pokémon Korea's (pokemoncard.co.kr), for the Asia game: names, numbers and pictures that join the Japanese printings (Korean, Traditional Chinese) or make sets of their own (Thai and Indonesian products). At most this many requests a day per language, one a second; what was read is kept under TCG_IMAGE_DIR/cache, the first fill takes a few days. false skips them.

TCG_POKEMON_OFFICIAL_ASIA=true

TCG_POKEMON_OFFICIAL_ASIA_DAILY_REQUESTS

TCG_POKEMON_OFFICIAL_ASIA_DAILY_REQUESTS=500

TCG_YUGIOH_LANGUAGES

Yu-Gi-Oh! is two games: the TCG (Europe, the Americas, Oceania) and the OCG (Japan and Asia). Both read YGOPRODeck and YGOJSON (~57 MB per catalog sync, downloaded once for both when they sync within an hour). Languages of the TCG game: English, French, German, Italian and Portuguese names from YGOPRODeck, Spanish ones from YGOJSON.

TCG_YUGIOH_LANGUAGES=["en","fr","de","it","pt","es"]

TCG_YUGIOH_OCG_LANGUAGES

Languages of the OCG game (~85k printings): Japanese, Korean, Chinese and Asian English ("-AE" codes) sets from YGOJSON. Unset: the Asian languages of TCG_YUGIOH_LANGUAGES (it held both before 0.17.0) and "en", else these.

TCG_YUGIOH_OCG_LANGUAGES=["ja","ko","zh-hans","zh-hant","en"]

TCG_YUGIOH_KONAMI_DAILY_REQUESTS

Pages of the official Konami card database read a day (one a second) for the Japanese and Korean names and printings (OCG game) of cards newer than YGOJSON. Each card is read once (kept under TCG_IMAGE_DIR/cache). 0: never.

TCG_YUGIOH_KONAMI_DAILY_REQUESTS=300

TCG_APITOKEN_GAMES_RFB_RIOT

Tokens of external APIs (card data, prices) that need one. Names follow TCG_APITOKEN_<SOURCE TYPE>[<CATEGORY>]<API NAME> with SOURCE TYPE GAMES (card data) or PRICES (markets), CATEGORY the game for game sources (MTG, PKM, SOR, RFB, YGO, LOR), none for sources covering several games, e.g. TCG_APITOKEN_GAMES_MTG_SCRYFALL, TCG_APITOKEN_PRICES_CARDNEXUS. Scryfall, MTGJSON, TCGdex, Sorcery, Riftcodex, Riot's card gallery, YGOPRODeck, YGOJSON, the Konami database, LorcanaJSON, tcgcsv and Cardmarket files need no key. Without a key, the sources below are skipped.

Riftbound: Riot's content API (official). Needs a key Riot approved for a Riftbound product (developer.riotgames.com); development keys are refused. Without one, the Riftbound catalog comes from Riftcodex (riftcodex.com). How to get one: docs/riftbound.md.

TCG_APITOKEN_GAMES_RFB_RIOT=

TCG_APITOKEN_GAMES_TCGAPIEU

Card databases filling what each game's own sources miss (pictures, rarities, artists, TCGplayer ids), only for sets with gaps:

  • tcg-api.eu (Pokémon, Magic, Lorcana; 10 000 requests a day on the free tier)
TCG_APITOKEN_GAMES_TCGAPIEU=

TCG_TCGAPI_EU_DAILY_REQUESTS

TCG_TCGAPI_EU_DAILY_REQUESTS=9000

TCG_APITOKEN_GAMES_APITCG

  • apitcg.com (Magic, Pokémon, Riftbound, Yu-Gi-Oh!, Lorcana; 1000 requests a month on the free tier: it runs after every other source and skips sets it found nothing for, 30 days)
TCG_APITOKEN_GAMES_APITCG=

TCG_APITCG_MONTHLY_REQUESTS

TCG_APITCG_MONTHLY_REQUESTS=900

TCG_APITOKEN_PRICES_CARDNEXUS

CardNexus (cardnexus.com → Settings → API Keys; Magic, Pokémon, Yu-Gi-Oh!, Sorcery, Lorcana, Riftbound): daily files of its catalog and prices, one API request per game and run, the files kept under TCG_IMAGE_DIR/cache. Adds the CardNexus price source (lowest listing, EUR) and Cardmarket / TCGplayer prices no other source has; in the catalog job, fills pictures, rarities and market ids and adds printings a set lacks. CardNexus has no Pokémon China, Korean, Thai or Indonesian cards (Japanese only, as "Pokemon Japan"). Pokémon and Pokémon Asia printings left without a Cardmarket price by TCGdex get one from Cardmarket's public price guide file (no key needed).

TCG_APITOKEN_PRICES_CARDNEXUS=

TCG_CARDNEXUS_DAILY_REQUESTS

TCG_CARDNEXUS_DAILY_REQUESTS=1000

TCG_DATABASE_URL

Database (compose builds it from POSTGRES_PASSWORD).

TCG_DATABASE_URL=postgresql+asyncpg://tcg:tcg@localhost:5432/tcg

TCG_WEB_DIR

Built web UI, card image cache, and folder card images are imported from (image: /app/web, /data/images, /data/import). The image folder holds one folder per game: cards/, sets/, symbols/ and rarity/ (own pictograms there replace Zukan's, see README "Image folders").

TCG_WEB_DIR=../web/dist

TCG_IMAGE_DIR

TCG_IMAGE_DIR=data/images

TCG_IMPORT_DIR

TCG_IMPORT_DIR=data/import

Source and interpretation

64 documented assignments in .env.example · Core defaults · OIDC defaults.

The generic OIDC names use HOME as an example. Replace that segment with your configured provider prefix, not with a literal angle-bracket placeholder. Values such as change-me, example hostnames and client IDs must be replaced. External-provider free-tier limits quoted in source comments can change; the shown budget is Zukan configuration, not a guarantee from that provider.

Clone this wiki locally