> ## Documentation Index
> Fetch the complete documentation index at: https://gomodel-fix-headers-altering.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# config.yaml

> When to use config.yaml, when not to, and how it interacts with environment variables and Docker.

The goal in GoModel is to make `config.yaml` optional as much as possible.
Prefer environment variables for normal deployments, CI, containers, and secret
injection.

Use `config.yaml` when you need structure that env vars cannot express cleanly,
especially:

* per-provider resilience overrides
* custom provider instance names that do not fit the generated
  `<provider-type>-<suffix>` env naming
* richer reviewable provider model lists, especially when using allowlist mode
* reviewable budget limits by `user_path`
* larger nested config that is easier to review in one file

For multiple provider instances, env vars support
`<PROVIDER>_<SUFFIX>_API_KEY`, `<PROVIDER>_<SUFFIX>_BASE_URL`, and
`<PROVIDER>_<SUFFIX>_MODELS`. The suffix becomes a hyphenated provider name:
`OPENAI_EAST_API_KEY` registers `openai-east`, and
`OLLAMA_A_BASE_URL` registers `ollama-a`. Azure also supports
`<PROVIDER>_<SUFFIX>_API_VERSION`.

Google Vertex AI follows the same suffix-to-name rule (`VERTEX_US_PROJECT`
registers `vertex-us`). Vertex project, location, and other Vertex settings
must use the same suffix for each instance: for example, pair
`VERTEX_US_PROJECT` with `VERTEX_US_LOCATION`, not the generic
`VERTEX_PROJECT` or `VERTEX_LOCATION`. `VERTEX_AUTH_TYPE` defaults to
`gcp_adc`.

Configured provider model lists can stay in env via `<PROVIDER>_MODELS`, for
example `OPENROUTER_MODELS`, `ORACLE_MODELS`, `AZURE_MODELS`, or `VLLM_MODELS`.
Set `CONFIGURED_PROVIDER_MODELS_MODE=fallback` (default) to use those lists only
when upstream `/models` fails or is empty, or `allowlist` to expose only the
configured models for providers that define a list and skip their upstream
`/models` calls.

## Priority Order

Effective precedence is:

1. environment variables
2. optional `config/config.yaml` or `config.yaml`
3. built-in defaults from code

`.env` is not a separate priority layer. It is just a convenient way to load
environment variables before startup.

Each layer is applied on top of the previous one, so a provider declared in
`config.yaml` and overlaid by env vars still counts as coming from the file.

## Startup Validation

Declarative config is parsed strictly: an unknown key is a startup error, not a
silently ignored one. This catches typos (`prot:` instead of `port:`) and, more
importantly, misindented sections — see the gotcha below.

This applies to `config.yaml` and to the env vars that declare the same
structures as JSON: `VIRTUAL_MODELS`, `SET_RATE_LIMIT_*`, and `SET_BUDGET_*`.
Because env entries override YAML entries, a typo in one of them would otherwise
silently win over a correct YAML entry.

The default is strict because a dropped `providers`, `rate_limits`, `budgets`,
or `guardrails` entry silently changes routing, cost, or security — a cost cap
that never applies looks exactly like a cost cap that does.

Set `CONFIG_STRICT=false` to downgrade unknown keys to warnings and boot without
them. This is for rolling a binary back under a newer config file, not for
everyday use:

```text theme={null}
WARN  unknown config key ignored; it has no effect path=config.yaml line=23 field=uranium-geryon-9b
INFO  providers resolved total=1 from_config_file=0 from_env=1
```

`CONFIG_STRICT` relaxes which *keys* are accepted, never whether a *value* makes
sense. A malformed value (`port: [9999, 8080]`) aborts startup in either mode.
Entity-level validation also still applies: a virtual model whose target names a
provider that does not exist fails startup even when the key that would have
declared that provider was ignored with a warning.

To confirm which layer supplied your providers, GoModel logs one line at boot:

```json theme={null}
{"msg":"providers resolved","total":5,"from_config_file":4,"from_env":1,
 "config_file_providers":["plutonium-qwen-dflash","uranium-gemma-embedding"],
 "env_providers":["opencode-go"]}
```

`from_config_file: 0` while a file is loaded means the file contributed no
providers. GoModel also logs `config file loaded` with the resolved path, or
`no config file found` with the paths it searched.

## Current Schema

The current source of truth lives in the main codebase:

* [config/config.go](https://github.com/ENTERPILOT/GoModel/blob/main/config/config.go)
* [config/config.example.yaml](https://github.com/ENTERPILOT/GoModel/blob/main/config/config.example.yaml)

For deployments mounted below a domain root, set `server.base_path` or
`BASE_PATH`. For example, `BASE_PATH=/g` serves the gateway at `/g/v1/...`,
`/g/admin/...`, and `/g/health`.

To change the inbound user path header, set `server.user_path_header` or
`USER_PATH_HEADER`. The default remains `X-GoModel-User-Path`.

## Docker

GoModel reads `config/config.yaml` first, then `config.yaml`.

With `docker run`, you can keep a host file named `config.yml` and mount it to
the in-container path GoModel expects:

```bash theme={null}
docker run --rm -p 8080:8080 \
  -v "$PWD/config.yml:/app/config/config.yaml:ro" \
  enterpilot/gomodel:latest
```

With Docker Compose:

```yaml theme={null}
services:
  gomodel:
    image: enterpilot/gomodel:latest
    ports:
      - "8080:8080"
    volumes:
      - ./config.yml:/app/config/config.yaml:ro
```

<Tip>
  If env vars and YAML both define the same setting, the environment variable
  wins.
</Tip>

If the mounted path exists but cannot be read — most often a directory
bind-mounted where a file was expected — startup fails rather than silently
falling back to defaults.

## Gotchas

### Unresolved `${VAR}` placeholders drop the provider

If `${OPENAI_API_KEY}` appears in YAML but the env var is not actually set, the
literal string `${OPENAI_API_KEY}` ends up as the API key value. The credential
filter detects the `${` substring and removes the provider entirely. This also
applies if `${VAR}` appears in the middle of the value, such as
`prefix-${OPENAI_API_KEY}`. Always verify your env vars are exported before
starting the process, or supply a default: `${OPENAI_API_KEY:-}`.

### A misindented `providers:` section is rejected

YAML reads this as an empty `providers:` section plus two unrelated top-level
keys, not as two providers:

```yaml theme={null}
providers:
uranium-geryon-9b:          # not indented under providers:
  type: vllm
  base_url: "http://uranium-geryon-9b:8000/v1"
uranium-gemma-embedding:    # likewise
  type: vllm
  base_url: "http://uranium-gemma-embedding:8000/v1"
```

Strict parsing rejects it at startup, naming every offending line:

```text theme={null}
failed to parse config.yaml: line 2: field uranium-geryon-9b not found; line 5: field uranium-gemma-embedding not found
```

Indent provider entries two spaces under `providers:`. Before strict parsing,
this booted with zero YAML providers and no error. With `CONFIG_STRICT=false` it
boots with zero YAML providers and one warning per unindented key.

### Per-provider resilience can only come from YAML

The env-var override walk skips `map` fields. `RETRY_MAX_RETRIES` changes the
global default for all providers but cannot target a single provider.
Per-provider tuning requires a `providers.<name>.resilience:` block in
`config.yaml`.

### Partial YAML leaves the rest at defaults

YAML is unmarshalled onto a struct that has already been populated with
built-in defaults. Only fields that appear in the file are written. Omitting
`max_backoff` from `resilience.retry` leaves it at its built-in default; you do
not need to repeat defaults you are happy with.
