# Configuration reference

> Environment variables and runtime paths: `EXECUTOR_DATA_DIR`, `EXECUTOR_SCOPE_DIR`, `EXECUTOR_WEB_BASE_URL`, secret keys, bootstrap admin, ports, and client-specific overrides.

- Repository: RhysSullivan/executor
- GitHub: https://github.com/RhysSullivan/executor
- Human docs: https://grok-wiki.com/public/docs/rhyssullivan-executor-564383868052
- Complete Markdown: https://grok-wiki.com/public/docs/rhyssullivan-executor-564383868052/llms-full.txt

## Source Files

- `apps/docs/hosted/docker.mdx`
- `apps/cli/src/daemon-state.ts`
- `apps/cli/src/local-server-manifest.ts`
- `apps/cli/src/service.ts`
- `packages/core/sdk/src/public-origin.ts`
- `apps/host-selfhost/.env.example`
- `RUNNING.md`

---

---
title: "Configuration reference"
description: "Environment variables and runtime paths: `EXECUTOR_DATA_DIR`, `EXECUTOR_SCOPE_DIR`, `EXECUTOR_WEB_BASE_URL`, secret keys, bootstrap admin, ports, and client-specific overrides."
---

Executor configuration is split across three layers: environment variables (runtime behavior), on-disk paths under a data directory (persistence and discovery), and per-workspace scope (integrations and plugins). Local CLI, desktop, self-hosted Docker, and Cloudflare Workers each read overlapping variables with different defaults. This page catalogs the variables and files that govern ports, auth, secrets, bootstrap, and client overrides.

```mermaid
flowchart TB
  subgraph env["Environment variables"]
    DATA["EXECUTOR_DATA_DIR"]
    SCOPE["EXECUTOR_SCOPE_DIR"]
    ORIGIN["EXECUTOR_WEB_BASE_URL"]
    SECRETS["BETTER_AUTH_SECRET / EXECUTOR_SECRET_KEY"]
  end
  subgraph disk["Data directory (default ~/.executor)"]
    DB["data.db"]
    CTRL["server-control/"]
    AUTH["auth.json"]
    MANIFEST["server.json"]
    PROFILES["server-connections.json"]
    DAEMON["daemon-*.json"]
  end
  subgraph scope["Scope directory (default cwd)"]
    JSONC["executor.jsonc"]
  end
  DATA --> disk
  SCOPE --> scope
  ORIGIN --> OAuth["OAuth / MCP / connect URLs"]
  SECRETS --> CTRL
```

## Data directory

`EXECUTOR_DATA_DIR` is the root for all local persistence. When unset, runtimes default to `~/.executor` (CLI, desktop, local daemon) or `.executor-selfhost` under the process working directory (self-host dev server).

<ParamField body="EXECUTOR_DATA_DIR" type="string">
Absolute path to the executor data root. Holds the SQLite database, server-control files, daemon records, and generated key files.
</ParamField>

### On-disk layout

:::files
~/.executor/                          # default when EXECUTOR_DATA_DIR is unset
├── data.db                           # SQLite store (local CLI / desktop)
├── secret.key                        # self-host: generated encryption key (mode 0600)
├── auth-secret.key                   # self-host: generated session secret (mode 0600)
├── server-connections.json           # CLI saved server profiles
├── server-control/
│   ├── auth.json                     # local bearer token (mode 0600)
│   ├── server.json                   # active server manifest (mode 0600)
│   └── startup.lock                  # startup mutex
├── daemon-localhost-4788.json        # per-host/port daemon record
├── daemon-active-localhost-<hash>.json  # scope-active daemon pointer
└── logs/
    ├── daemon.log                    # supervised service stdout
    └── daemon.error.log              # supervised service stderr
:::

| File | Purpose |
| --- | --- |
| `data.db` | SQLite database for integrations, connections, policies, and executions |
| `server-control/auth.json` | Stable bearer token for local single-user auth; minted on first boot |
| `server-control/server.json` | Live server manifest: origin, port, PID, bearer token copy, owner metadata |
| `server-connections.json` | Named CLI server profiles (`executor server profile`) |
| `daemon-*.json` | Daemon process records and scope pointers for auto-start |
| `secret.key` / `auth-secret.key` | Self-host generated keys when env secrets are unset |

<Note>
The bearer token in `auth.json` is the source of truth for local auth. `server.json` carries a copy for discovery; rotating auth requires updating both the file and any MCP client configs.
</Note>

## Scope directory

`EXECUTOR_SCOPE_DIR` pins the workspace Executor binds to. It controls tenant identity, plugin loading from `executor.jsonc`, and daemon scope isolation.

<ParamField body="EXECUTOR_SCOPE_DIR" type="string">
Absolute path to the workspace root. When unset, runtimes use `process.cwd()`. The OS supervised service defaults this to `EXECUTOR_DATA_DIR` when unset.
</ParamField>

Scope identity is derived as:

- With `EXECUTOR_SCOPE_DIR` set: `scope:<resolved-path>`
- Without it: `cwd:<resolved-cwd>`

Daemon pointers and start locks are keyed by this scope ID, so two terminals in different project folders can each run an isolated daemon against the same data directory.

Per-scope plugin config lives at `<EXECUTOR_SCOPE_DIR>/executor.jsonc` (or `<cwd>/executor.jsonc`). Static plugins from `executor.config.ts` win over duplicate entries in `executor.jsonc`.

## Public origin

`EXECUTOR_WEB_BASE_URL` pins the public URL Executor uses for server-side outbound links: OAuth `redirect_uri`, MCP OAuth metadata, connect/approval URLs, and email links. This value must come from a trusted operator or platform source, never from request `Host` headers.

Resolution order (via `resolvePublicOrigin`):

1. Explicit `EXECUTOR_WEB_BASE_URL`
2. Platform-injected origin (`RAILWAY_PUBLIC_DOMAIN`, `RENDER_EXTERNAL_URL`, `VERCEL_URL`, `FLY_APP_NAME`, `WEBSITE_HOSTNAME`, `CF_PAGES_URL`, and related vars)
3. Fallback `http://localhost:<port>` with a one-time startup warning (except in `development` / `test`)

<Warning>
Set `EXECUTOR_WEB_BASE_URL` to the exact URL you load in the browser (scheme, host, port). A mismatch causes browser logins to fail with an invalid-origin error. Behind TLS or a reverse proxy, use the public `https://` URL, not the internal bind address.
</Warning>

<ParamField body="EXECUTOR_WEB_BASE_URL" type="string">
Public base URL for absolute outbound links. Required behind domains, TLS, or proxies. The local CLI daemon sets this automatically from the resolved listen URL when not already defined.
</ParamField>

Cloudflare Workers use `VITE_PUBLIC_SITE_URL` for the same purpose. When unset on a Worker, the per-request origin drives links; a startup warning recommends pinning a canonical URL on production deployments.

## Ports and bind addresses

| Runtime | Default port | Bind address | Override |
| --- | --- | --- | --- |
| Local CLI daemon (`executor daemon run`) | `4788` | `127.0.0.1` (via `--hostname`) | `--port`, `--hostname` |
| OS supervised service (`executor service install`) | `4789` | `127.0.0.1` | baked into service unit at install time |
| Self-host Docker / production | `4788` | `0.0.0.0` | `PORT`, `EXECUTOR_HOST` |
| Local dev Vite (`apps/local`) | `5173` | Vite defaults | `PORT` |
| Self-host dev Vite | project-specific | `127.0.0.1` | Vite `--port` |

<ParamField body="PORT" type="integer">
HTTP listen port. Self-host reads `PORT` (default `4788`). The CLI daemon accepts `--port` (default `4788`).
</ParamField>

<ParamField body="EXECUTOR_HOST" type="string">
Bind address. Self-host Docker defaults to `0.0.0.0`. Local daemon and supervised service bind loopback (`127.0.0.1`).
</ParamField>

<ParamField body="EXECUTOR_PORT" type="integer">
Desktop sidecar listen port. `0` means allocate an ephemeral port.
</ParamField>

Clients discover the live port from `server-control/server.json`, not from hard-coded defaults. The supervised service uses port `4789` by default so existing desktop MCP configs that targeted `4789` keep resolving after `executor install`.

## Secret keys

Self-host and local runtimes manage two independent secrets plus a local bearer token.

### Session secret

<ParamField body="BETTER_AUTH_SECRET" type="string">
Better Auth session secret. Minimum 32 characters when set explicitly. Alias: `AUTH_SECRET`. When unset, self-host generates and persists a random secret to `<data-dir>/auth-secret.key`.
</ParamField>

Rotating this secret signs every user out.

### Encryption master key

<ParamField body="EXECUTOR_SECRET_KEY" type="string">
Master key for the encrypted secrets provider. When unset on self-host, a random key is generated and persisted to `<data-dir>/secret.key`. On Cloudflare Workers, this is **required** (set via `wrangler secret put EXECUTOR_SECRET_KEY`, minimum 16 characters).
</ParamField>

### Local bearer token

Local and desktop servers gate `/api`, `/mcp`, and approval endpoints with a single bearer token stored in `server-control/auth.json`. The token is minted on first boot and reused across restarts. Rotate it with `executor auth rotate`; MCP clients must pick up the new value.

Supervised daemons (`EXECUTOR_SUPERVISED=1`) load the token from `auth.json` at boot. The OS service unit never embeds the secret.

## Bootstrap admin (self-host)

Headless first-run setup pre-creates the admin account instead of the in-browser setup screen.

<ParamField body="EXECUTOR_BOOTSTRAP_ADMIN_EMAIL" type="string" required>
Admin email. Must be set together with `EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD` for headless bootstrap.
</ParamField>

<ParamField body="EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD" type="string" required>
Admin password for headless bootstrap.
</ParamField>

<ParamField body="EXECUTOR_BOOTSTRAP_ADMIN_NAME" type="string">
Display name for the bootstrap admin. Default: `Admin`.
</ParamField>

<ParamField body="EXECUTOR_ORG_NAME" type="string">
Display name of the single organization. Default: `Default`.
</ParamField>

<ParamField body="EXECUTOR_ORG_SLUG" type="string">
URL slug for org-prefixed console paths (`/<slug>/policies`). Default: `default`. Must be 2-48 chars of `[a-z0-9-]` and cannot collide with reserved routes (`api`, `mcp`, `login`, etc.).
</ParamField>

## Sandbox network

<ParamField body="EXECUTOR_ALLOW_LOCAL_NETWORK" type="boolean">
When `true`, sandboxed QuickJS code may reach loopback and private network addresses. Default: `false`. Enable only when you trust the code being executed.
</ParamField>

Cloudflare Workers use `ALLOW_LOCAL_NETWORK` with the same semantics.

## CLI and client overrides

These variables configure how CLI tools, MCP clients, and desktop attach to a running instance.

### Server authentication

| Variable | Used by | Purpose |
| --- | --- | --- |
| `EXECUTOR_API_KEY` | CLI, MCP | Bearer API key for hosted Executor Cloud |
| `EXECUTOR_AUTH_TOKEN` | CLI, MCP, desktop | Bearer token for local or desktop servers |

`EXECUTOR_API_KEY` takes precedence over `EXECUTOR_AUTH_TOKEN` when both are set. Local servers publish their token in `server.json`, so env overrides are mainly for remote or scripted access.

### Client identity

| Variable | Values | Purpose |
| --- | --- | --- |
| `EXECUTOR_CLIENT` | `cli`, `desktop` | Stamped into `server.json` owner metadata and telemetry |
| `EXECUTOR_CLIENT_DIR` | path | Desktop web UI asset directory passed to the sidecar |
| `EXECUTOR_SUPERVISED` | `1` | Marks an OS-managed daemon; loads token from `auth.json`, reclaims stale `server.json` on boot |
| `EXECUTOR_SERVICE_VERSION` | semver | Installed CLI version baked into the service unit for drift detection |

### Plugin and native binding overrides

| Variable | Purpose |
| --- | --- |
| `EXECUTOR_KEYCHAIN_SERVICE_NAME` | Keychain / Credential Manager service name (default: `executor`) |
| `EXECUTOR_KEYRING_NATIVE_PATH` | Absolute path to the `@napi-rs/keyring` `.node` binding in compiled binaries |
| `EXECUTOR_LIBSQL_NATIVE_PATH` | Absolute path to the libSQL native binding in compiled binaries |
| `EXECUTOR_BIN_PATH` | Override which `executor` binary the CLI launcher spawns |
| `EXECUTOR_DESKTOP_SETTINGS_DIR` | Isolated desktop settings directory (tests) |

### Observability (optional)

Passed through to supervised service units when set at install time:

| Variable | Purpose |
| --- | --- |
| `EXECUTOR_SENTRY_DSN` | Sentry crash reporting DSN |
| `EXECUTOR_SENTRY_RELEASE` | Sentry release tag |
| `EXECUTOR_SENTRY_ENVIRONMENT` | Sentry environment (default: `production`) |
| `EXECUTOR_RUN_ID` | Correlates events across desktop main and sidecar processes |
| `EXECUTOR_MCP_DEBUG` | Set to `true` for verbose MCP session logging |

## Self-host environment reference

All self-host variables are optional; a bare `docker compose up` boots a working instance.

| Variable | Default | Purpose |
| --- | --- | --- |
| `PORT` | `4788` | HTTP listen port |
| `EXECUTOR_HOST` | `0.0.0.0` (Docker), `127.0.0.1` (dev) | Bind address |
| `EXECUTOR_DATA_DIR` | `/data` (Docker), `.executor-selfhost` (dev) | Data root |
| `EXECUTOR_DB_PATH` | `<data-dir>/data.db` | SQLite file path |
| `EXECUTOR_WEB_BASE_URL` | platform-detected or `http://localhost:<port>` | Public browser URL |
| `BETTER_AUTH_SECRET` | generated in data dir | Session secret |
| `EXECUTOR_SECRET_KEY` | generated in data dir | Encryption master key |
| `EXECUTOR_BOOTSTRAP_ADMIN_EMAIL` | unset | Headless admin email |
| `EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD` | unset | Headless admin password |
| `EXECUTOR_BOOTSTRAP_ADMIN_NAME` | `Admin` | Admin display name |
| `EXECUTOR_ORG_NAME` | `Default` | Organization display name |
| `EXECUTOR_ORG_SLUG` | `default` | Organization URL slug |
| `EXECUTOR_ALLOW_LOCAL_NETWORK` | `false` | Sandbox loopback/private access |

<CodeGroup>

```bash Docker run
docker run -d \
  --name executor-selfhost \
  -p 4788:4788 \
  -v executor-data:/data \
  -e EXECUTOR_WEB_BASE_URL=https://executor.example.com \
  -e EXECUTOR_BOOTSTRAP_ADMIN_EMAIL=you@example.com \
  -e EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD=change-me-strong \
  ghcr.io/rhyssullivan/executor-selfhost:latest
```

```bash Local dev
export EXECUTOR_DATA_DIR=/tmp/my-executor
export EXECUTOR_WEB_BASE_URL=http://localhost:4788
export BETTER_AUTH_SECRET=$(openssl rand -hex 32)
cd apps/host-selfhost && bun run dev
```

</CodeGroup>

## Cloudflare Workers environment

Cloudflare hosts receive configuration through Worker bindings and `wrangler.jsonc` vars, not `process.env`.

| Binding / variable | Required | Purpose |
| --- | --- | --- |
| `EXECUTOR_SECRET_KEY` | yes (secret) | At-rest secret encryption key |
| `ACCESS_TEAM_DOMAIN` | yes | Cloudflare Access team domain |
| `ACCESS_AUD` | yes | Access application AUD tag |
| `ADMIN_EMAILS` | no | Comma-separated admin emails |
| `SELF_HOSTED_ORG_ID` | no | Organization ID (default: `default`) |
| `SELF_HOSTED_ORG_NAME` | no | Organization name (default: `Default`) |
| `SELF_HOSTED_ORG_SLUG` | no | Organization URL slug |
| `VITE_PUBLIC_SITE_URL` | no | Pinned public origin |
| `ALLOW_LOCAL_NETWORK` | no | Sandbox local network access |
| `ENABLE_DEV_AUTH` | no | Skip Access for local `wrangler dev` only |

<Warning>
Never set `ENABLE_DEV_AUTH=true` on a deployment that is not already behind Cloudflare Access. It treats every request as a fixed admin and leaves the instance wide open.
</Warning>

## Supervised service environment

`executor service install` bakes these into the launchd plist, systemd unit, or Windows scheduled task:

| Variable | Value at install | Purpose |
| --- | --- | --- |
| `EXECUTOR_SUPERVISED` | `1` | Supervised daemon mode |
| `EXECUTOR_DATA_DIR` | resolved data dir | Pinned data root |
| `EXECUTOR_SCOPE_DIR` | `EXECUTOR_SCOPE_DIR` env or data dir | Pinned workspace scope |
| `EXECUTOR_SERVICE_VERSION` | installing CLI version | Drift detection after upgrades |
| `PATH` | installer's PATH | Tool discovery for integrations |

The service also passes through `EXECUTOR_CLIENT`, `EXECUTOR_SENTRY_*`, and `EXECUTOR_RUN_ID` when present at install time.

## Verify configuration

<Steps>
  <Step title="Check the data directory">
    Confirm which data root is active:

    ```bash
    ls -la ~/.executor/server-control/
    ```

    Expect `auth.json` and, when a server is running, `server.json` (both mode `0600`).
  </Step>
  <Step title="Read the active server manifest">
    ```bash
    cat ~/.executor/server-control/server.json
    ```

    Note `connection.origin` (live URL), `dataDir`, `scopeDir`, and `owner.client`.
  </Step>
  <Step title="Probe health">
    ```bash
    curl -s http://127.0.0.1:4788/api/health
    ```

    Replace the host and port with values from `server.json` if they differ.
  </Step>
  <Step title="Check supervised service status">
    ```bash
    executor service status
    ```

    Reports registration, running state, PID (macOS), and lingering status (Linux).
  </Step>
</Steps>

## Development-only variables

These appear in contributor workflows and e2e harnesses, not production deployments.

| Variable | Purpose |
| --- | --- |
| `EXECUTOR_DEV_VITE_PORT` | HMR client port when the CLI daemon proxies to a child Vite server |
| `EXECUTOR_VERSION` | CLI build version override |
| `EXECUTOR_PREVIEW_CDN_URL` / `EXECUTOR_PREVIEW_SHA` | Preview CLI build CDN pinning |
| `EXECUTOR_PREVIEW_TARGETS` | Comma-separated preview build targets |
| `E2E_*_PORT` / `E2E_*_URL` | E2E harness port pinning or attach-to-running |
| `__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS` | Allow Vite dev server access from tailnet hostnames |

## Related pages

<CardGroup>
  <Card title="Installation" href="/installation">
    Install the CLI, bootstrap a checkout, and verify ports and data directories.
  </Card>
  <Card title="Deploy self-hosted" href="/deploy-selfhost">
    Docker image defaults, volume mounts, bootstrap admin, and TLS requirements.
  </Card>
  <Card title="CLI reference" href="/cli-reference">
    `executor install`, `daemon`, `service`, `web`, and port flags.
  </Card>
  <Card title="Configure credentials" href="/configure-credentials">
    Credential providers, OAuth flows, and placement-based auth templates.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Port conflicts, stale manifests, OAuth origin mismatches, and recovery commands.
  </Card>
</CardGroup>
