> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Environment Variables & Secrets

> Manage configuration, credentials, and sensitive data securely with environment variables and encrypted secrets

## Stop Hardcoding Credentials

Every IoT deployment needs connection strings, API keys, and passwords -- but embedding them directly in your LoT code or route definitions creates security risks and makes your configuration impossible to reuse across environments. The Coreflux Broker solves this with built-in environment variable and secrets management.

* **Environment variables** store non-sensitive configuration (hostnames, ports, URLs) in a plain-text `.env` file
* **Secrets** store sensitive credentials (passwords, API keys, tokens) encrypted at rest with **AES-256-GCM**

Both are accessible from LoT Actions, Models, Routes, and broker CLI commands using a unified syntax -- no external tools or services required.

<Tip>
  **Think of it like a vault and a notebook.** Environment variables are the notebook -- quick to read, easy to update, visible if someone opens the file. Secrets are the vault -- locked with a key, encrypted on disk, only decrypted when the broker needs them at runtime.
</Tip>

<Info>
  Environment variables and secrets management is available from **Coreflux Broker v1.9.3** and above.
</Info>

## When to Use Each

| Scenario                  | Use                      | Why                                                |
| ------------------------- | ------------------------ | -------------------------------------------------- |
| Database host, port, name | **Environment variable** | Non-sensitive, changes per environment             |
| API base URLs             | **Environment variable** | Configuration that varies between dev/staging/prod |
| Feature flags             | **Environment variable** | Simple on/off toggles                              |
| Database passwords        | **Secret**               | Must not be readable on disk                       |
| API keys and tokens       | **Secret**               | Sensitive credentials                              |
| TLS passphrases           | **Secret**               | Security-critical values                           |

***

## In This Page

| Section                                         | Description                                            |
| ----------------------------------------------- | ------------------------------------------------------ |
| [Config Root](#config-root)                     | How the broker resolves its configuration directory    |
| [Environment Variables](#environment-variables) | Plain-text configuration management                    |
| [Secrets](#secrets)                             | Encrypted credential storage                           |
| [Using in LoT](#using-in-lot)                   | GET ENV, GET SECRET, KEEP, DELETE syntax               |
| [Using in Routes](#using-in-routes)             | Referencing env vars and secrets in route definitions  |
| [CLI Commands](#cli-commands)                   | Managing env vars and secrets via broker commands      |
| [Deployment](#deployment)                       | Windows, Linux, Docker, and Kubernetes setup           |
| [Resolution Reference](#resolution-reference)   | Quick-reference tables for resolution order and syntax |

***

## Config Root

The broker builds its configuration root directory from two parts:

```
Config root = {CONFIG_PATH or AppDirectory} + /Coreflux
```

* If the `CONFIG_PATH` environment variable is set, it is used as the base path
* Otherwise, the application directory is used (e.g. `/app` in Docker, the executable's folder on Windows/Linux)
* `/Coreflux` is always appended automatically

For example, with `CONFIG_PATH=/data`, the config root becomes `/data/Coreflux`.

### File Layout

The environment and secrets files live alongside the broker's internal data under the config root:

```
{config_root}/
  .env                # Plain-text environment variables (key=value)
  secrets.json        # AES-256-GCM encrypted secrets
  secret.key          # Optional: encryption key (base64, 32 bytes)
  bin/                # Internal broker data (managed automatically)
  log/                # Log files
  stats/              # Statistics
```

***

## Environment Variables

### Storage

Environment variables are stored in plain text in `{config_root}/.env` using standard key=value format:

```env theme={null}
DB_HOST=postgres.example.com
DB_PORT=5432
API_BASE_URL=https://api.example.com
MQTT_BRIDGE_HOST=remote-broker.company.com
```

### Resolution Order

When the broker resolves an environment variable (via `GET ENV`, route config, or CLI), it checks multiple sources in this order:

| Priority | Source                | Description                                                 |
| -------- | --------------------- | ----------------------------------------------------------- |
| 1        | `.env` file (managed) | Set via `-setEnv` or `KEEP ENV`. Persisted to disk.         |
| 2        | Process environment   | Docker `environment:`, Kubernetes `env:`, or shell exports. |
| 3        | User environment      | OS user-level environment variables (non-Docker).           |
| 4        | Machine environment   | OS machine-level environment variables (non-Docker).        |

If not found in any source, an empty string is returned.

<Note>
  This fallback chain means Docker `environment:` variables work even without a `.env` file -- the broker picks them up from the process environment automatically.
</Note>

***

## Secrets

### How Secrets Work

Secrets are encrypted at rest using **AES-256-GCM** and stored in `{config_root}/secrets.json`. Each secret entry contains the initialization vector, the encrypted value with authentication tag, and a creation timestamp:

```json theme={null}
{
  "DB_PASSWORD": {
    "IV": "base64...",
    "Value": "base64(ciphertext):base64(tag)",
    "Created": "2026-02-08T12:00:00Z"
  }
}
```

Secret values are only decrypted at runtime when accessed via `GET SECRET` or route configuration. They are never logged or displayed -- even the `-listSecrets` command only shows secret names, never values.

### Encryption Key

The broker resolves the encryption key in this order:

| Priority | Source                          | Notes                                                                                                   |
| -------- | ------------------------------- | ------------------------------------------------------------------------------------------------------- |
| 1        | `COREFLUX_SECRET_KEY` env var   | Best for Docker/Kubernetes. Base64-encoded, 32 bytes.                                                   |
| 2        | `{config_root}/secret.key` file | Good for mounted volumes. Base64-encoded, 32 bytes.                                                     |
| 3        | Derived from machine GUID       | Auto-generated fallback. Machine-specific -- secrets become unreadable if moved to a different machine. |

#### Generating a Key

Use OpenSSL to generate a cryptographically secure 32-byte key:

```bash theme={null}
openssl rand -base64 32
# Example output: K7x2mN8pQ1rT5uW3yB6dF9hJ0kL4nP7sV2xZ8cG1eA=
```

<Warning>
  If you use the derived key fallback (no explicit key), your secrets are tied to the specific machine. Moving the `secrets.json` file to another machine or container will make the secrets unreadable. Always use an explicit key for portable deployments.
</Warning>

***

## Using in LoT

Environment variables and secrets can be read, written, and deleted from within LoT Actions, Models, and route configurations.

### Reading Values

Use `GET ENV` to read an environment variable and `GET SECRET` to read an encrypted secret (decrypted at runtime):

```lot theme={null}
-- Read configuration from environment
SET "myHost" WITH (GET ENV "DB_HOST")
SET "myPort" WITH (GET ENV "DB_PORT")

-- Read credentials from encrypted secrets
SET "dbPass" WITH (GET SECRET "DB_PASSWORD")
SET "apiKey" WITH (GET SECRET "API_KEY")
```

### Persisting Values

Use `KEEP ENV` to save a new environment variable to the `.env` file, and `KEEP SECRET` to encrypt and store a new secret:

```lot theme={null}
-- Save an environment variable (persisted to .env)
KEEP ENV "LAST_RUN" WITH TIMESTAMP "UTC"

-- Save an encrypted secret (persisted to secrets.json)
KEEP SECRET "API_KEY" WITH "sk-abc123"
```

### Deleting Values

Use `DELETE ENV` and `DELETE SECRET` to remove values:

```lot theme={null}
-- Remove an environment variable
DELETE ENV "OLD_CONFIG"

-- Remove a secret
DELETE SECRET "OLD_API_KEY"
```

### Complete Example

This action reads database configuration from environment variables and credentials from secrets on broker startup, then reports the status:

```lot theme={null}
DEFINE ACTION ConnectToDatabase
ON START DO
    -- Read connection settings from environment
    SET "host" WITH (GET ENV "DB_HOST")
    SET "port" WITH (GET ENV "DB_PORT")
    SET "dbName" WITH (GET ENV "DB_NAME")

    -- Read credentials from encrypted secrets
    SET "user" WITH (GET SECRET "DB_USER")
    SET "pass" WITH (GET SECRET "DB_PASSWORD")

    IF {host} == EMPTY THEN
        PUBLISH TOPIC "system/errors" WITH "DB_HOST not configured"
    ELSE
        PUBLISH TOPIC "system/status" WITH "Database configured: " + {host} + ":" + {port}

    -- Track when we last initialized
    KEEP ENV "LAST_DB_INIT" WITH TIMESTAMP "UTC"
```

***

## Using in Routes

Environment variables and secrets can be referenced directly in route definitions, keeping connection details out of your LoT code.

### Standard Syntax

Use `GET ENV` and `GET SECRET` in route configuration blocks to inject values at runtime:

```lot theme={null}
DEFINE ROUTE MyDB WITH TYPE POSTGRESQL
    ADD SQL_CONFIG
        WITH SERVER GET ENV "DB_HOST"
        WITH PORT 5432
        WITH DATABASE GET ENV "DB_NAME"
        WITH USERNAME GET ENV "DB_USER"
        WITH PASSWORD GET SECRET "DB_PASSWORD"
```

### Legacy Prefix Syntax

The `ENV:` and `SECRET:` prefix syntax is also supported for backward compatibility in route `ADD CONFIG` sections:

```lot theme={null}
WITH SERVER ENV:DB_HOST
WITH PASSWORD SECRET:DB_PASSWORD
```

### Event Query Placeholders

In route event queries (e.g. `WITH QUERY`), use curly-brace placeholders that are resolved at execution time:

```lot theme={null}
ADD EVENT StoreReading
    WITH SOURCE_TOPIC "sensors/#"
    WITH QUERY "INSERT INTO readings (host, value) VALUES ('{env.DB_HOST}', '{payload}')"
```

| Placeholder     | Resolves To                |
| --------------- | -------------------------- |
| `{env.NAME}`    | Environment variable value |
| `{secret.NAME}` | Decrypted secret value     |

***

## CLI Commands

Manage environment variables and secrets at runtime by publishing commands to `$SYS/Coreflux/Command` using any MQTT client (such as MQTT Explorer).

### Environment Variable Commands

| Command              | Description                                            |
| -------------------- | ------------------------------------------------------ |
| `-setEnv NAME=value` | Set an environment variable (persisted to `.env` file) |
| `-removeEnv NAME`    | Remove an environment variable                         |
| `-listEnv`           | List all managed environment variables                 |

Publish these messages to `$SYS/Coreflux/Command` to manage environment variables:

```bash theme={null}
# Set an environment variable
-setEnv DB_HOST=postgres.example.com

# List all environment variables
-listEnv

# Remove an environment variable
-removeEnv OLD_CONFIG
```

### Secret Commands

| Command                 | Description                                              |
| ----------------------- | -------------------------------------------------------- |
| `-setSecret NAME=value` | Set a secret (encrypted and persisted to `secrets.json`) |
| `-removeSecret NAME`    | Remove a secret                                          |
| `-listSecrets`          | List secret names (values are never shown)               |

Publish these messages to `$SYS/Coreflux/Command` to manage secrets:

```bash theme={null}
# Set a secret
-setSecret DB_PASSWORD=my_secure_password

# List secret names
-listSecrets

# Remove a secret
-removeSecret OLD_API_KEY
```

<Note>
  Subscribe to `$SYS/Coreflux/Command/Output` to receive confirmation responses from commands.
</Note>

***

## Deployment

Set up environment variables and secrets across different deployment targets.

<Tabs>
  <Tab title="Windows">
    On Windows, the config root defaults to the broker's executable directory + `\Coreflux` unless `CONFIG_PATH` is set.

    <Steps>
      <Step title="Locate the Config Directory">
        If the broker is installed at `C:\Coreflux\`, the config root is `C:\Coreflux\Coreflux\`.

        To use a custom path, set the `CONFIG_PATH` system environment variable:

        ```powershell theme={null}
        [Environment]::SetEnvironmentVariable("CONFIG_PATH", "C:\CorefluxData", "Machine")
        ```

        The config root becomes `C:\CorefluxData\Coreflux\`.
      </Step>

      <Step title="Create the .env File">
        Create a `.env` file in the config root with your environment variables:

        ```powershell theme={null}
        @"
        DB_HOST=postgres.example.com
        DB_PORT=5432
        API_BASE_URL=https://api.example.com
        "@ | Out-File -FilePath "C:\CorefluxData\Coreflux\.env" -Encoding UTF8
        ```
      </Step>

      <Step title="Generate an Encryption Key">
        Generate and save a `secret.key` file for encrypting secrets:

        ```powershell theme={null}
        openssl rand -base64 32 > "C:\CorefluxData\Coreflux\secret.key"
        ```
      </Step>

      <Step title="Set Secrets After Start">
        Start the broker, then set secrets using any MQTT client (such as MQTT Explorer). Publish to `$SYS/Coreflux/Command`:

        ```
        -setSecret DB_PASSWORD=my_secure_password
        ```

        <Check>
          Subscribe to `$SYS/Coreflux/Command/Output` to confirm the secret was stored.
        </Check>
      </Step>
    </Steps>
  </Tab>

  <Tab title="Linux">
    On Linux, set `CONFIG_PATH` in the systemd service file or shell environment.

    <Steps>
      <Step title="Create the Config Directory">
        Create the directory and set ownership to the broker's service user:

        ```bash theme={null}
        sudo mkdir -p /data/Coreflux
        sudo chown coreflux:coreflux /data/Coreflux
        ```
      </Step>

      <Step title="Create the .env File">
        Write your environment variables and restrict permissions:

        ```bash theme={null}
        cat > /data/Coreflux/.env << 'EOF'
        DB_HOST=postgres.example.com
        DB_PORT=5432
        API_BASE_URL=https://api.example.com
        EOF
        chmod 600 /data/Coreflux/.env
        ```
      </Step>

      <Step title="Generate an Encryption Key">
        Generate the key and restrict permissions:

        ```bash theme={null}
        openssl rand -base64 32 > /data/Coreflux/secret.key
        chmod 600 /data/Coreflux/secret.key
        ```
      </Step>

      <Step title="Configure the systemd Service">
        Add `CONFIG_PATH` to your service file at `/etc/systemd/system/coreflux.service`:

        ```ini theme={null}
        [Service]
        Environment=CONFIG_PATH=/data
        ExecStart=/opt/coreflux/CorefluxMQTTBroker
        ```

        Then reload and restart:

        ```bash theme={null}
        sudo systemctl daemon-reload
        sudo systemctl restart coreflux
        ```
      </Step>

      <Step title="Set Secrets After Start">
        Using any MQTT client, publish to `$SYS/Coreflux/Command`:

        ```
        -setSecret DB_PASSWORD=my_secure_password
        ```

        <Check>
          Verify with `-listSecrets` -- the secret name should appear in the output.
        </Check>
      </Step>
    </Steps>
  </Tab>

  <Tab title="Docker Compose">
    ### Option 1: Mount a Config Volume (Recommended)

    Pre-create the config files on the host and mount them into the container.

    **1. Prepare the host directory:**

    Create the config directory, generate an encryption key, and write your environment variables:

    ```bash theme={null}
    mkdir -p ./coreflux-data

    # Generate encryption key
    openssl rand -base64 32 > ./coreflux-data/secret.key

    # Create .env file
    cat > ./coreflux-data/.env << 'EOF'
    DB_HOST=postgres.example.com
    DB_PORT=5432
    API_BASE_URL=https://api.example.com
    EOF
    ```

    **2. Docker Compose file:**

    Mount the config directory and set `CONFIG_PATH` so the broker finds its files:

    ```yaml theme={null}
    services:
      coreflux-broker:
        image: coreflux/coreflux-broker:latest
        container_name: coreflux-broker
        ports:
          - "1883:1883"
          - "8883:8883"
          - "5000:5000"
        environment:
          - CONFIG_PATH=/data
        volumes:
          - ./coreflux-data:/data/Coreflux
        restart: unless-stopped
    ```

    The broker resolves:

    * `CONFIG_PATH=/data` means config root is `/data/Coreflux`
    * `.env` loaded from `/data/Coreflux/.env` (your mounted `./coreflux-data/.env`)
    * `secret.key` loaded from `/data/Coreflux/secret.key` (your mounted key)
    * All config files (`bin/`, `log/`, etc.) persist in the mounted volume

    **3. Set secrets after the broker starts:**

    Using any MQTT client, publish to `$SYS/Coreflux/Command`:

    ```
    -setSecret DB_PASSWORD=my_secure_password
    ```

    ### Option 2: Pass Env Vars via Docker (No .env File)

    If you prefer not to manage a `.env` file, pass variables directly through Docker's `environment:`. The resolution fallback to process environment picks them up:

    ```yaml theme={null}
    services:
      coreflux-broker:
        image: coreflux/coreflux-broker:latest
        ports:
          - "1883:1883"
        environment:
          - CONFIG_PATH=/data
          - DB_HOST=postgres.example.com
          - DB_PORT=5432
          - API_BASE_URL=https://api.example.com
          - COREFLUX_SECRET_KEY=K7x2mN8pQ1rT5uW3yB6dF9hJ0kL4nP7sV2xZ8cG1eA=
        volumes:
          - coreflux-data:/data/Coreflux

    volumes:
      coreflux-data:
    ```

    Variables set this way:

    * Are available to LoT via `GET ENV "DB_HOST"`
    * Are available to routes via `GET ENV "DB_HOST"` and legacy `ENV:DB_HOST`
    * Are **not** persisted to `.env` (they exist only in the process environment)
    * Will be lost if the container restarts without the same `environment:` block

    <Note>
      Secrets still need a volume for `secrets.json` persistence, even when passing the encryption key via `COREFLUX_SECRET_KEY`.
    </Note>
  </Tab>

  <Tab title="Kubernetes">
    Use Kubernetes ConfigMaps for environment variables and Kubernetes Secrets for the encryption key.

    **1. ConfigMap for environment variables:**

    Store non-sensitive configuration in a ConfigMap:

    ```yaml theme={null}
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: coreflux-env
    data:
      DB_HOST: "postgres.cluster.local"
      DB_PORT: "5432"
      API_BASE_URL: "https://api.example.com"
    ```

    **2. Kubernetes Secret for the encryption key:**

    Store the encryption key in a Kubernetes Secret (base64-encoded):

    ```yaml theme={null}
    apiVersion: v1
    kind: Secret
    metadata:
      name: coreflux-secrets
    type: Opaque
    data:
      COREFLUX_SECRET_KEY: SzdsMm1OOHBRMXJUNXVXM3lCNmRGOWhKMGtMNG5QN3NWMnhaOGNHMWVBPQ==
    ```

    **3. Deployment:**

    Inject the ConfigMap as environment variables via `envFrom`, and the encryption key from the Kubernetes Secret:

    ```yaml theme={null}
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: coreflux-broker
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: coreflux-broker
      template:
        metadata:
          labels:
            app: coreflux-broker
        spec:
          containers:
            - name: broker
              image: coreflux/coreflux-broker:latest
              ports:
                - containerPort: 1883
                - containerPort: 5000
              env:
                - name: CONFIG_PATH
                  value: "/data"
                - name: COREFLUX_SECRET_KEY
                  valueFrom:
                    secretKeyRef:
                      name: coreflux-secrets
                      key: COREFLUX_SECRET_KEY
              envFrom:
                - configMapRef:
                    name: coreflux-env
              volumeMounts:
                - name: data
                  mountPath: /data/Coreflux
          volumes:
            - name: data
              persistentVolumeClaim:
                claimName: coreflux-pvc
    ```

    **4. PersistentVolumeClaim:**

    Provide persistent storage for broker data, secrets, and configuration:

    ```yaml theme={null}
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: coreflux-pvc
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi
    ```

    Apply all resources:

    ```bash theme={null}
    kubectl apply -f coreflux-configmap.yaml
    kubectl apply -f coreflux-secret.yaml
    kubectl apply -f coreflux-pvc.yaml
    kubectl apply -f coreflux-deployment.yaml
    ```

    ConfigMap values are injected as process environment variables via `envFrom`, so `GET ENV` picks them up through the fallback chain. The `COREFLUX_SECRET_KEY` from the Kubernetes Secret enables portable secret encryption.
  </Tab>
</Tabs>

***

## Best Practices

<AccordionGroup>
  <Accordion title="Use explicit encryption keys in production" icon="key">
    Never rely on the derived key fallback for production deployments. Always provide a `COREFLUX_SECRET_KEY` environment variable or a `secret.key` file. The derived key is machine-specific -- if you migrate, scale, or recreate a container, your secrets become unreadable.
  </Accordion>

  <Accordion title="Restrict file permissions" icon="lock">
    On Linux, restrict access to sensitive files to the broker's service user only:

    ```bash theme={null}
    chmod 600 /data/Coreflux/.env
    chmod 600 /data/Coreflux/secret.key
    chmod 600 /data/Coreflux/secrets.json
    ```
  </Accordion>

  <Accordion title="Prefer GET ENV over hardcoded values" icon="code">
    Even for values that seem static (like a database port), use `GET ENV` so you can change configuration without redeploying LoT code. This makes your Actions and Routes portable across dev, staging, and production.
  </Accordion>

  <Accordion title="Use mounted volumes for Docker" icon="docker">
    Always mount a persistent volume at `{CONFIG_PATH}/Coreflux` in Docker and Kubernetes. Without a volume, your `.env`, `secrets.json`, and all broker state are lost when the container stops.
  </Accordion>

  <Accordion title="Never commit secrets to version control" icon="triangle-exclamation">
    Keep `secret.key`, `secrets.json`, and `.env` files out of Git. Add them to `.gitignore` and use your deployment pipeline to inject them at runtime.
  </Accordion>
</AccordionGroup>

***

## Resolution Reference

### Where Each Syntax Reads From

| Syntax              | Where It Works                   | Resolved By                            |
| ------------------- | -------------------------------- | -------------------------------------- |
| `GET ENV "NAME"`    | Actions, Models, Route configs   | `.env` file, then process env fallback |
| `GET SECRET "NAME"` | Actions, Models, Route configs   | Decrypts from `secrets.json`           |
| `ENV:NAME`          | Route `ADD CONFIG` sections only | Same as `GET ENV`                      |
| `SECRET:NAME`       | Route `ADD CONFIG` sections only | Same as `GET SECRET`                   |
| `{env.NAME}`        | Event queries (`WITH QUERY`)     | Replaced at event execution time       |
| `{secret.NAME}`     | Event queries (`WITH QUERY`)     | Replaced at event execution time       |

### Encryption Key Resolution Order

| Priority | Source                          | Notes                                                                                      |
| -------- | ------------------------------- | ------------------------------------------------------------------------------------------ |
| 1        | `COREFLUX_SECRET_KEY` env var   | Best for Docker/Kubernetes. Base64-encoded, 32 bytes.                                      |
| 2        | `{config_root}/secret.key` file | Good for mounted volumes. Base64-encoded, 32 bytes.                                        |
| 3        | Derived from machine GUID       | Auto-generated fallback. Machine-specific -- secrets won't decrypt on a different machine. |

### GetEnv Resolution Order

| Priority | Source                | Notes                                                       |
| -------- | --------------------- | ----------------------------------------------------------- |
| 1        | `.env` file (managed) | Set via `-setEnv` or `KEEP ENV`. Persisted to disk.         |
| 2        | Process environment   | Docker `environment:`, Kubernetes `env:`, or shell exports. |
| 3        | User environment      | OS user-level env vars (non-Docker).                        |
| 4        | Machine environment   | OS machine-level env vars (non-Docker).                     |

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Broker Commands" icon="terminal" href="/mqtt-broker/commands">
    Full reference for all broker CLI commands including env and secret management.
  </Card>

  <Card title="Operations Reference" icon="gear" href="/lot-language/actions/operations">
    See GET ENV and GET SECRET in the complete LoT operations reference.
  </Card>
</CardGroup>
