--- name: dify-docs-env-vars description: > Rule pack for the environment variable reference — en/self-host/deploy/configuration/environments.mdx. Carries the tracing procedure, description rules, verifier, and document structure. Loaded by dify-docs-write; not an entry point. --- # Dify Environment Variable Documentation Not an entry point — run under `dify-docs-write`; the procedure below implements its stages for `en/self-host/deploy/configuration/environments.mdx`. Read `references/style-overrides.md` (in this skill directory — env-var-specific style rules and description anti-patterns) together with this pack. Use the ref pinned at S1; cite it in the S4 scope report. ## Procedure (S2 → S6) Work through in order. **Every variable goes through steps 1–4 without exception** — do not skip a variable because it seems "obvious". ### Step 1 (S2): Trace each variable in the codebase When using subagents for tracing, assign 3–5 related variables per agent. Tracing depth depends on variable type: | Variable type | Depth | |---|---| | Python config vars (defined in `api/configs/`) | Full trace (below). | | Frontend vars (mapped in `web/docker/entrypoint.sh`) | Trace the Docker-to-`NEXT_PUBLIC_*` mapping in `entrypoint.sh`; verify the default in both `docker/.env.example` and `web/.env.example`; run `grep -rn "" /api/` — any match means the var is dual-purpose and needs a full trace. | | Docker/container service vars (only in `docker-compose.yaml`) | `grep -rn "" /api/` must return no matches; then document from `.env.example` comments. | | Plugin daemon vars (`PLUGIN_*` not in `api/configs/`) | Document from `.env.example` comments. | Full trace: 1. Find the definition in `api/configs/` — Pydantic field type, default, description, and any `validation_alias` (fallback) settings. 2. Find every usage — grep both the env var name and the Python attribute (`dify_config.VARIABLE_NAME`); read the surrounding code. 3. Determine behavior when empty vs set — trace fallback chains; identify what breaks. ### Step 2 (S2): Write a plain-language explanation Cover: what the variable does in practical terms; the specific features that depend on it (name them); what happens if left empty; what happens if set; key code file paths (no line numbers — they shift). This explanation goes into the S4 scope report. ### Step 3 (S5): Write the user-facing description - Lead with the practical impact, not the technical mechanism - Name the features that require the variable (e.g., "Required for the Human Input node") - Explain what breaks if misconfigured (e.g., "If empty, email links will be broken") - Mention fallback behavior if any (e.g., "falls back to `CONSOLE_API_URL`") - Include relationships with other variables when relevant - Apply every rule in `references/style-overrides.md` ### Step 4 (S4 contribution): Report The S4 scope report presents: the plain-language explanations, the proposed descriptions, and the pinned ref. The pipeline's S4 gate applies. ### Step 5 (S5/S6): Edit the documentation Edit `en/self-host/deploy/configuration/environments.mdx` following [Document Structure](#document-structure). Update the `zh/` and `ja/` copies in the same pass, per `tools/translate/formatting-zh.md`, `tools/translate/formatting-ja.md`, and `writing-guides/glossary.md`. ## S7 verifiers ### Run the verifier The canonical command scans BOTH env sources — never pass only one: ```bash python3 .claude/skills/dify-docs-env-vars/verify-env-docs.py \ --env-example /docker/.env.example \ --env-example /docker/envs \ --compose /docker \ --docs en/self-host/deploy/configuration/environments.mdx ``` `--compose` reads the `${VAR}` references in `docker/docker-compose*.y*ml`. A variable a compose file consumes with no `.env.example` entry is invisible to every other check — `EXPOSE_WEAVIATE_GRPC_PORT` went undocumented for eleven months that way — so the script lists them under `=== IN COMPOSE BUT NOT IN ANY .env.example () ===` and treats them as source variables from then on. `--compare-rev` reads the compose files at both refs on its own. `docker-compose.pytest.ports.yaml` is skipped in both modes and the skip is printed: it publishes vector-store ports for the integration tests, and nothing a deployment reads. `--env-example` is repeatable; a directory argument is globbed `**/*.env.example` recursively. The script first prints the list of files it parsed — confirm it shows `docker/.env.example` plus the files under `docker/envs/`, then the compose file count. A single-source run under-scans and produces false "extra in docs" results. Output contract: on a fully clean doc the last line is `ALL CHECKS PASSED — documentation matches .env.example` and the script exits 0; otherwise it prints `TOTAL ISSUES: ` with per-category counts and exits 1. **Cadence.** The full command above is the baseline audit, and the baseline was cleared at dify tag `1.17.1`. A release pass runs `--compare-rev` — it diffs both the `.env.example` files and the compose references between two refs, so a clean baseline stays clean incrementally. Re-run the full command whenever this script, the ignore list's location, or the `.env.example` layout changes, and after any pass that touched more than a handful of variables; it must end on `ALL CHECKS PASSED` or every remaining line must be accounted for in the ignore list. Run it against the `zh` and `ja` pages too — the parser understands `默认值:`, `デフォルト値:` and `(空)`, so their counts are as meaningful as English's. Pass bar for every task: the full command ends on `ALL CHECKS PASSED`, or every remaining line is accounted for in the ignore list with a reason. **Missing from docs** stopped being standing backlog when the baseline was cleared at tag `1.17.1`: a nonzero count now means a variable arrived since, and it is documented or ignored before the task ends. If the script prints `WARNING: ignore list not found`, it stops with exit status 2 and prints no counts: fix the path and re-run. ### Update the ignore list if needed The verifier filters out variables listed in the registry's `guides/env-ignored-vars.md`. That list stays in the private registry because its entries name unreleased work. The verifier finds it through `$DIFY_DOCS_REGISTRY` or a sibling clone of this repo; pass `--ignored PATH` to point somewhere else. When you: - Remove a variable from the docs as Cloud-only → add it under **Cloud-only (SaaS)**. - Skip documenting an experimental or internal flag → add it under **Experimental / internal**. - Document a supported variable whose `.env.example` entry is commented out (`#FOO=bar`) → add it under **Verifier false positives**. This bucket is **only** for vars present in `.env.example` in commented form; see [Source of Truth](#source-of-truth) for vars absent entirely. Every entry must include a source reference (PR, commit, or audit date). ## Source of Truth After Dify PR #31586, the supported self-host knob surface is split across: - `docker/.env.example` — essential startup values - `docker/envs/**/*.env.example` — categorized optional vars (core-services, databases, infrastructure, security, vectorstores, middleware) The verifier reads both — always use the canonical verifier command above, which passes both sources. | Var location | Action | |---|---| | In any `.env.example` file, uncommented | Document. | | In any `.env.example` file, commented (`#FOO=bar`) | Document; add to **Verifier false positives** in `env-ignored-vars.md` (the verifier can't parse defaults from comments). | | Only in `api/configs/` Pydantic, not in any `.env.example` | **Don't document.** Upstream-deferred; file a PR adding it to the appropriate `.env.example` file first. | | In `.env.example` and still parsed, but upstream-deprecated with a replacement | Keep the row; lead the description with the deprecation and the replacement: "Deprecated; use `X`." Deprecated means still parsed — a removed var never gets a Deprecated label. | | Removed from `.env.example` because the code no longer reads it | **Remove from docs — no tombstone rows** (a documented row implies the var still takes effect). If a successor variable replaced it, add one clause to the successor's description so the old names stay findable via search: "Replaces the former `EDITION`, ignored from 1.17.0 onward." With no successor, remove without trace; upgrader discoverability belongs in upstream Dify release notes. | **The verifier's "extra in docs" signal is not an escape hatch. Never suppress it for Pydantic-only vars via `env-ignored-vars.md`.** ## Document Structure The doc groups variables by subsystem, broadly following the `docker/.env.example` and `docker/envs/**` layout (Common Variables, Server Configuration, Web Frontend Service, Database Service, and so on). Match an existing `##` section for a new variable; don't invent one. If a variable genuinely fits no section, raise it with the user rather than guessing. | Element | Use for | |---|---| | Tables | Groups of related, straightforward variables (connection settings, credentials, tuning knobs). | | Individual headings | Important variables needing explanation — enum-type selectors (`STORAGE_TYPE`, `VECTOR_STORE`) or variables where the "why" matters (`SECRET_KEY`, `FILES_URL`). | | Tabs | Frontend variables where Docker and source deployments use different names. Tabs cannot sit inside table cells, so tabbed variables need individual headings. | | Accordions | Provider-specific configuration (storage backends, vector databases, mail providers) — users only need one provider. | ## Reader Persona Same audience as `en/self-host/deploy/` documentation (see the `dify-docs-guides` pack): DevOps engineers and system administrators deploying Dify. Assume strong infrastructure knowledge. Readers are actively configuring a deployment and scanning for a specific variable, not reading linearly. They need to know what each variable does, when to change it, and what breaks if they get it wrong.