---
question: Does an environment variable win over .env for a config secret?
kind: task
via_web: na
via_cli: available
via_python: na
via_api: na
via_config: available
ref_cli: abi config validate
ref_config: "{{ secret.NAME }}"
prereq: The secret in `.env` or another secret adapter. See [Add a secret](/user-guide/build/secrets/add-a-secret).
next:
  - /capabilities/build/secrets/rotate-a-secret
  - /capabilities/get-started/configuration
---

# Use a secret in config

A config file names a secret as `{{ secret.NAME }}` and never holds its value. The engine renders those references as a template when it starts, then reads the result as YAML. Config files are committed; `.env` is not.

## Web UI

Not applicable. Nexus does not edit config files. Super admins can set the value a reference reads under **System** › **Data** › **Secrets**: see [Add a secret](/user-guide/build/secrets/add-a-secret?via=web). Use the Config steps on this page.

## CLI

### Check every reference resolves

```bash
abi config validate
```

It renders the config the engine would read. In a terminal, each reference no adapter can answer is asked for and saved to `.env`; without a terminal it fails with `Secret 'NAME' not found and no TTY available to prompt`. `--configuration-file config.local.yaml` checks another environment's file.

### Look at the result

```bash
abi config render
```

It prints the whole rendered config, secret values included. Read it in your terminal; do not paste or log it.

## Python

Not applicable. A module receives the rendered value through its `config:` block, as a field of its `Configuration` class. To read a secret at run time instead, call `secret.get`: see [Add a secret](/user-guide/build/secrets/add-a-secret?via=python).

## REST API

Not applicable. Config is rendered inside the engine, at start; no route renders it. Use the Config steps on this page.

## Config

### Keep the value in `.env`

```bash title=".env"
PARTNER_API_TOKEN=the-value
```

### Reference it

```yaml title="config.yaml"
modules:
  - module: my_module
    enabled: true
    config:
      partner_api_token: "{{ secret.PARTNER_API_TOKEN }}"
```

`partner_api_token` is a field of your module's `Configuration` class. A new project already references `OPENROUTER_API_KEY` this way, in the `naas_abi_marketplace.ai.openrouter` entry: edit that entry rather than adding a second one.

Quote the reference. The value is pasted in as text before the YAML is read, so a value that contains a quote or a line break can still break the file. A reference can sit inside a longer string: `"postgresql://{{ secret.POSTGRES_USER }}:{{ secret.POSTGRES_PASSWORD }}@postgres:5432/abi"`.

### Pick the file per environment

The engine reads `config.<ENV>.yaml` when `ENV` names a file that exists, else `config.yaml`. `ENV` comes from the process environment, or from `.env`. Each file can point its `dotenv` adapter at its own secrets file:

```yaml title="config.remote.yaml"
services:
  secret:
    secret_adapters:
      - adapter: dotenv
        config:
          path: .env.remote
```

The files are on [Environment files](/user-guide/get-started/configuration#environment-files).

### Restart

The engine renders config once, at start. Restart it after you add or change a reference.

## How a reference resolves

1. A process environment variable with that name wins.
2. Then the secret adapters, in order: the first that has the key.
3. When none has it, a command run in a terminal asks for the value and saves it to the adapters. Without a terminal, loading the config fails.

Two exceptions:

- **The `services.secret` block** is rendered before the secret service exists. A reference there reads only the process environment and the first `dotenv` file.
- **Comment lines** that start with `#` are left as they are, so a commented-out reference never asks for a value. A comment after a value on the same line is rendered.
