> ## Documentation Index
> Fetch the complete documentation index at: https://www.halite-app.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Environment variables and in-app settings.

Halite is configured through environment variables at startup and through in-app settings stored in the database. This page covers both. For a guided walkthrough of the in-app Settings page, see [Settings](/features/settings).

## Environment variables

Set these in your `.env` file (which `docker compose` loads automatically) or pass them directly to the container.

### Database

<ParamField body="DATABASE_URL" type="string" required>
  SQLAlchemy async database URL.

  For `compose.yml` (Postgres): `postgresql+asyncpg://halite:halite@db:5432/halite`

  For `compose.sqlite.yml` (homelab): `sqlite+aiosqlite:////data/halite.db`
</ParamField>

### Sessions

<ParamField body="COOKIE_SECRET" type="string" required>
  Signing key for session cookies. Must be at least 32 characters. Generate one with `./scripts/gen-bootstrap-secret.sh`.
</ParamField>

<ParamField body="SESSION_TTL_MINUTES" type="integer" default="480">
  How long a session remains valid after the last request, in minutes. Defaults to 480 (8 hours).
</ParamField>

<ParamField body="COOKIE_SECURE" type="boolean" default="true">
  Controls the `Secure` flag on the session cookie.

  * `false` — cookie is sent over plain HTTP (use for local Docker testing at `http://localhost:8080`)
  * `true` — cookie is only sent over HTTPS (use in production behind a TLS proxy)

  The `compose.yml` profile defaults this to `true`; `compose.sqlite.yml` defaults it to `false`.
</ParamField>

<ParamField body="COOKIE_NAME" type="string" default="halite_session">
  Name of the session cookie.
</ParamField>

### Networking

<ParamField body="LISTEN_HOST" type="string" default="0.0.0.0">
  The host address uvicorn binds to.

  <Note>
    The shipped Docker images launch uvicorn with a hardcoded `--host 0.0.0.0 --port 8080`, so this setting has no effect in the provided compose setups. To change the host port, remap the `ports:` entry in your compose file (e.g. `"9000:8080"`).
  </Note>
</ParamField>

<ParamField body="LISTEN_PORT" type="integer" default="8080">
  The port uvicorn listens on.

  <Note>
    The shipped Docker images launch uvicorn with a hardcoded `--host 0.0.0.0 --port 8080`, so this setting has no effect in the provided compose setups. To change the host port, remap the `ports:` entry in your compose file (e.g. `"9000:8080"`).
  </Note>
</ParamField>

<ParamField body="TRUSTED_PROXIES" type="string" default="">
  Recognized by the settings loader but not currently wired into request handling. Reserved for future use.
</ParamField>

### Logging

<ParamField body="LOG_LEVEL" type="string" default="info">
  Logging verbosity. Standard Python log level names: `debug`, `info`, `warning`, `error`, `critical`.
</ParamField>

### Audit

<ParamField body="AUDIT_AUDIT_READS" type="boolean" default="false">
  Recognized by the settings loader but not currently wired into request handling. Reserved for future use — intended to enable recording of read-only requests in the audit log, but the flag is not consumed anywhere in the current codebase.
</ParamField>

### SPA serving

<ParamField body="HALITE_STATIC_DIR" type="string">
  Filesystem path to the built React SPA (`dist/`). When set, the backend serves the SPA from this directory. When unset, SPA serving is disabled (useful if you serve the frontend separately).

  The provided Docker image sets this to `/app/frontend/dist` at build time.
</ParamField>

## In-app settings (database-backed)

These settings are stored in a singleton `AppSettings` row in the database and managed on the [Settings](/features/settings) page. They are **not** environment variables — changing them does not require a container restart.

### Salt-API connection

| Setting                       | Description                                                | Default |
| ----------------------------- | ---------------------------------------------------------- | ------- |
| `salt_api_url`                | URL of your `salt-api` (`rest_cherrypy`) endpoint          | —       |
| `salt_api_username`           | Username for Salt-API authentication                       | —       |
| `salt_api_password_encrypted` | Salt-API password, encrypted at rest using `COOKIE_SECRET` | —       |
| `salt_api_verify`             | Whether to verify the Salt-API TLS certificate             | `true`  |
| `salt_api_eauth`              | eauth backend (e.g. `pam`, `ldap`)                         | `"pam"` |

<Note>
  The Salt-API connection is configured entirely through the in-app Settings page. The `SALT_API_*` lines in `.env.example` are stale placeholders not read by current builds.
</Note>

### Inventory refresh

| Setting                             | Description                                                                                                    | Default |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------- |
| `inventory_refresh_minutes`         | Run a background `refresh_packages(target='*')` every N minutes. `0` disables it; manual refreshes still work. | `0`     |
| `inventory_refresh_initial_delay_s` | Seconds before the first scheduled refresh fires after startup                                                 | `30`    |

### Pollers

| Setting                                  | Description                                                | Default |
| ---------------------------------------- | ---------------------------------------------------------- | ------- |
| `fleet_poll_interval_seconds`            | Interval for fleet-state polling. `0` disables the poller. | `0`     |
| `jobs_poll_interval_seconds`             | Interval for jobs polling. `0` disables the poller.        | `0`     |
| `minion_state_keys_interval_seconds`     | Interval for minion accepted-keys refresh                  | `300`   |
| `minion_state_presence_interval_seconds` | Interval for minion presence refresh                       | `60`    |
| `minion_state_grains_interval_seconds`   | Interval for minion grains refresh                         | `300`   |
| `minion_state_initial_delay_seconds`     | Startup delay before the first minion-state poll           | `10`    |

### Logging format

| Setting      | Description                                                       | Default  |
| ------------ | ----------------------------------------------------------------- | -------- |
| `log_format` | Log output format: `json` (structured) or `text` (human-readable) | `"json"` |

<Note>
  The application startup currently calls `setup_logging` with a hardcoded `"json"` value, so current builds always emit JSON logs regardless of the `log_format` setting. The `LOG_FORMAT` entry in `.env.example` is stale and not read by the application.
</Note>

## Bootstrap admin

On first boot, when no users exist in the database, Halite automatically creates a default admin account:

* **Username:** `admin`
* **Password:** `changeme`
* **Forced password change:** Yes — you are required to set a new password on first login.

This account is created unconditionally from values hardcoded in `bootstrap.py`. The `BOOTSTRAP_ADMIN_USERNAME` and `BOOTSTRAP_ADMIN_PASSWORD` lines in `.env.example` are stale and not read by the application.

<Warning>
  Change the bootstrap admin password on first login before exposing Halite to a network.
</Warning>

## Stale `.env.example` entries

The following entries appear in `.env.example` but are **not read** by the application in current builds:

| Entry                                                             | Actual location                  |
| ----------------------------------------------------------------- | -------------------------------- |
| `BOOTSTRAP_ADMIN_USERNAME` / `BOOTSTRAP_ADMIN_PASSWORD`           | Hardcoded in `bootstrap.py`      |
| `SALT_API_*`                                                      | In-app Settings page (DB-backed) |
| `INVENTORY_REFRESH_MINUTES` / `INVENTORY_REFRESH_INITIAL_DELAY_S` | In-app Settings page (DB-backed) |
| `LOG_FORMAT`                                                      | In-app Settings page (DB-backed) |
