---
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.

## Procedure (S2 → S5)

Work through in order. **Every variable goes through the trace, explanation, task-analysis, report, and description steps 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 "<VAR_NAME>" <path-to-dify-repo>/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 "<VAR_NAME>" <path-to-dify-repo>/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).

### Step 3 (S3): Find the operator's task

For every variable a change adds or alters, run the pipeline's task analysis at the depth that change sets. The operator arrives with a goal rather than a variable name, so the lead text and rows must answer what the goal needs: prerequisites, which file a setting takes effect in, how to confirm it works. For a single variable the walk is usually short: Step 2 already covers what breaks when it is empty or wrong, which leaves what to set up first and how to confirm it took effect. A group that sets up something new, such as a runtime backend or an exporter, needs the whole walk.

### Step 4 (S4 contribution): Report

The S4 scope report presents: the plain-language explanations, the task analysis, and the pinned ref. The pipeline's S4 gate applies.

### Step 5 (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 6 (S5): Edit the documentation

Edit `en/self-host/deploy/configuration/environments.mdx` following [Document Structure](#document-structure). The `zh/` and `ja/` copies follow at the pipeline's S6, after the owner freezes the English.

## 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 <path-to-dify-repo>/docker/.env.example \
  --env-example <path-to-dify-repo>/docker/envs \
  --compose <path-to-dify-repo>/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 (<n>) ===` 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: <n>` 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 arrive two ways, neither reading linearly: scanning for a specific variable while configuring a deployment, or setting up something new, such as a runtime backend or an exporter, and looking for everything it needs. They need to know what each variable does, when to change it, and what breaks if they get it wrong.
