# Environment variables

> All 38 environment variables read by the FeatherPanel backend, async runner and MCP server, plus the Docker Compose variables, with defaults and effects.

Source: https://featherpanel.com/docs/panel/environment-variables/

## Where they live

The backend loads `backend/storage/config/.env` with phpdotenv (in Docker that is the`featherpanel_config` volume at `/var/www/html/storage/config`). Values already present in the process environment win, so the variables that `docker-compose.yml` sets on the backend container take precedence over the file. Most panel behaviour is not configured here at all but in the database-backed [settings](https://featherpanel.com/docs/panel/settings/).

The list below is generated by scanning the code for `getenv`, `App::env`, `$_ENV`,`$_SERVER`, `env::var` and `process.env`. Variables that only appear in development tooling are left out.

Keep the encryption key

`DATABASE_ENCRYPTION_KEY` encrypts secrets in the database: Wings and Quilld node tokens, Proxmox API tokens, API key private keys, OIDC and LDAP secrets, and sensitive settings such as the SMTP password. If you move or restore the database, bring the `.env` with it.

## Docker Compose variables

These are read by `docker compose` from `/var/www/featherpanel/.env` (next to `docker-compose.yml`) and passed into the containers. The defaults are the values in the compose file.

| Variable | Default | Effect |
| --- | --- | --- |
| `MARIADB_ROOT_PASSWORD` | `featherpanel_root` | MariaDB root password. Note that the mysql health check in docker-compose.yml uses the literal featherpanel\_root, so changing this also requires editing the health check. |
| `MARIADB_DATABASE` | `featherpanel` | Database created on first start and passed to the backend and runner. |
| `MARIADB_USER` | `featherpanel` | Database user created on first start. |
| `MARIADB_PASSWORD` | `featherpanel_password` | Password for MARIADB\_USER. |
| `REDIS_PASSWORD` | `featherpanel_redis` | Redis password, shared by backend and runner. |
| `FRANKENPHP_WORKER` | `true` | FrankenPHP worker mode for /api/\*. Set to false for classic per-request mode (public/index.php). |
| `DOCKER_UPDATER_TOKEN` | `(empty)` | Token for the host updater, written by the installer. |
| `FEATHERPANEL_PANEL_PORT` | `4831` | Host port published by the frontendv2 container. All panel traffic enters here. |

## Backend (PHP)

| Variable | Used by | Description | Read in |
| --- | --- | --- | --- |
| `DATABASE_DATABASE` | backend, async-runner | Database name. | [`Database.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Chat/Database.php)[`Migrate.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Cli/Commands/Migrate.php) |
| `DATABASE_ENCRYPTION_KEY` | backend, async-runner | XChaCha20 key (base64) used to encrypt secrets stored in the database. Generated by php cli setup. Losing it makes encrypted values unreadable, so back it up with the database. | [`Impersonation.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Helpers/Impersonation.php)[`mod.rs`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/runner/src/config/mod.rs) |
| `DATABASE_HOST` | backend, async-runner | MariaDB/MySQL host. The setup command writes it into storage/config/.env; the Docker compose file sets it to the mysql service. Default in code: `127.0.0.1`. | [`Database.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Chat/Database.php)[`Migrate.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Cli/Commands/Migrate.php) |
| `DATABASE_PASSWORD` | backend, async-runner | Database password. | [`Database.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Chat/Database.php)[`Migrate.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Cli/Commands/Migrate.php) |
| `DATABASE_PORT` | backend, async-runner | Database port. Defaults to 3306 where a default exists. | [`Database.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Chat/Database.php)[`Migrate.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Cli/Commands/Migrate.php) |
| `DATABASE_USER` | backend, async-runner | Database user. | [`Database.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Chat/Database.php)[`Migrate.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Cli/Commands/Migrate.php) |
| `DOCKER_UPDATER_TOKEN` | backend | Shared secret for the host-side updater. The installer generates it. Without URL and token the update button answers DOCKER\_UPDATER\_NOT\_CONFIGURED. | [`SettingsController.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Controllers/Admin/SettingsController.php) |
| `DOCKER_UPDATER_URL` | backend | URL of the host-side updater the installer sets up (compose default http://host.docker.internal:9418/update). | [`SettingsController.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Controllers/Admin/SettingsController.php) |
| `FEATHERPANEL_DOCKER` | backend | Read by the issue report collector to tag reports as coming from a Docker install. | [`MythicIssueReportCollector.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Services/FeatherCloud/MythicIssueReportCollector.php) |
| `FEATHERPANEL_ROUTE_CACHE` | backend | Route cache for the Symfony router. Set to false to disable it. | [`RouteCache.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Routing/RouteCache.php) |
| `FEATHERPANEL_VERSION_CHANNEL` | backend | development or dev marks the install as a development build in the admin dashboard version card. | [`DashboardController.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Controllers/Admin/DashboardController.php) |
| `FEATHERPANEL_WORKER_MAX_MEMORY` | backend | FrankenPHP worker mode: restart a worker when it uses more memory than this (default 256M, 0 = never). Accepts K, M and G suffixes. | [`worker.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/boot/worker.php) |
| `FEATHERPANEL_WORKER_MAX_REQUESTS` | backend | FrankenPHP worker mode: restart a worker after this many requests (default 1000 in boot/worker.php, 0 = unlimited). The backend image sets 500. | [`worker.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/boot/worker.php) |
| `LOG_DIR` | backend, async-runner | Async runner log directory. Falls back to /var/www/html/storage/logs, then /app/logs, then ../backend/storage/logs. The panel log viewer reads runner.fplog from there. | [`LogHelper.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Helpers/LogHelper.php)[`main.rs`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/runner/src/main.rs) |
| `PANEL_DOCKER_DEPLOYMENT` | backend | Marks a Docker deployment. Required for the one-click Docker update button in the admin area (otherwise the API answers DOCKER\_DEPLOYMENT\_REQUIRED). Default in code: `false`. | [`SettingsController.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Controllers/Admin/SettingsController.php) |
| `REDIS_HOST` | backend, async-runner | Redis host (default 127.0.0.1). | [`TestAsyncRunner.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Cli/Commands/TestAsyncRunner.php)[`Redis.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/FastChat/Redis.php) |
| `REDIS_PASSWORD` | backend, async-runner | Redis password. The Docker compose file starts Redis with --requirepass set to this value. | [`TestAsyncRunner.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Cli/Commands/TestAsyncRunner.php)[`Redis.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/FastChat/Redis.php) |
| `REDIS_PORT` | backend | Redis port (default 6379). | [`TestAsyncRunner.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Cli/Commands/TestAsyncRunner.php)[`Redis.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/FastChat/Redis.php) |
| `TRUST_PROXY_HEADERS` | backend | Set to true (or 1) to trust X-Forwarded-For and similar headers from any peer. Without it the panel only trusts them when the direct peer is a loopback or private address. Set it when your reverse proxy runs on another public host. | [`CloudFlareRealIP.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/CloudFlare/CloudFlareRealIP.php) |
| `UMAMI_ENABLED` | backend | Set to false to hard-disable the daily anonymous statistics report, whatever the telemetry setting in the admin area says. Default in code: `true`. | [`UmamiTelemetry.php`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/backend/app/Telemetry/UmamiTelemetry.php) |

## Async runner (Rust)

| Variable | Used by | Description | Read in |
| --- | --- | --- | --- |
| `DATABASE_ENCRYPTION` | async-runner | Encryption scheme for sensitive columns. The panel writes xchacha20. The async runner also accepts this variable as a fallback for the key, which is a legacy behaviour. | [`mod.rs`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/runner/src/config/mod.rs) |
| `DATABASE_URL` | async-runner | Async runner only. Full mysql:// URL. When unset the runner builds it from DB\_\* or DATABASE\_\* variables. | [`mod.rs`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/runner/src/config/mod.rs) |
| `DB_HOST` | async-runner | Async runner only. Docker-style alias for DATABASE\_HOST. | [`mod.rs`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/runner/src/config/mod.rs) |
| `DB_NAME` | async-runner | Async runner only. Alias for DATABASE\_DATABASE. | [`mod.rs`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/runner/src/config/mod.rs) |
| `DB_PASS` | async-runner | Async runner only. Alias for DATABASE\_PASSWORD. | [`mod.rs`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/runner/src/config/mod.rs) |
| `DB_PORT` | async-runner | Async runner only. Alias for DATABASE\_PORT (default 3306). | [`mod.rs`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/runner/src/config/mod.rs) |
| `DB_USER` | async-runner | Async runner only. Alias for DATABASE\_USER. | [`mod.rs`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/runner/src/config/mod.rs) |
| `REDIS_URL` | async-runner | Async runner only. Full redis:// URL. When unset it is built from REDIS\_HOST and REDIS\_PASSWORD on port 6379. | [`mod.rs`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/runner/src/config/mod.rs) |
| `VNC_PROXY_LISTEN` | async-runner | Async runner: address of the WebSocket proxy for Proxmox VNC consoles (default 0.0.0.0:9100). The frontend container routes /vnc-proxy to it. | [`vnc_proxy.rs`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/runner/src/vnc_proxy.rs) |

## MCP server (Node.js)

| Variable | Used by | Description | Read in |
| --- | --- | --- | --- |
| `FEATHERPANEL_API_KEY` | mcp | MCP server, stdio mode: the user API key to call the panel with. Remote MCP clients send their key per request instead. | [`auth.ts`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/mcp/src/auth.ts)[`stdio.ts`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/mcp/src/stdio.ts) |
| `FEATHERPANEL_URL` | mcp | MCP server: base URL of the panel backend (default http://backend:80). | [`client.ts`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/mcp/src/client.ts)[`index.ts`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/mcp/src/index.ts) |
| `MCP_DANGEROUSLY_ALLOW_INSECURE_ISSUER_URL` | mcp | Allows an http:// OAuth issuer URL. The compose file sets it to true because the server sits behind the panel proxy on the internal network. | [`index.ts`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/mcp/src/index.ts) |
| `MCP_DATA_DIR` | mcp | Directory for the OAuth store when MCP\_OAUTH\_STORE\_PATH is unset (falls back to $HOME, then /tmp). | [`store.ts`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/mcp/src/oauth/store.ts) |
| `MCP_HOST` | mcp | MCP server bind address (default 0.0.0.0). | [`index.ts`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/mcp/src/index.ts) |
| `MCP_OAUTH_STORE_PATH` | mcp | File for MCP OAuth clients and tokens. The compose file uses /data/oauth-store.json on the mcp\_oauth\_data volume. | [`store.ts`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/mcp/src/oauth/store.ts) |
| `MCP_PORT` | mcp | MCP server port (default 3001). | [`index.ts`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/mcp/src/index.ts) |
| `MCP_PUBLIC_URL` | mcp | Public URL of the MCP server, used in OAuth metadata. Set it when the server is reached through a domain. | [`public-url.ts`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/mcp/src/oauth/public-url.ts) |
| `MCP_TRUST_PROXY` | mcp | Set to 1 or true to derive the public URL from X-Forwarded-\* headers. Only do this behind a proxy that overwrites them. | [`public-url.ts`](https://github.com/MythicalLTD/FeatherPanel/blob/develop/mcp/src/oauth/public-url.ts) |
