config.yaml, in the environment
variables that set those settings, and in provider environment variables:
.env
files need no quoting. Docker
Compose interpolates ${...} in its own files, so write $${file:...} there;
Compose passes ${file:...} on to GoModel.
Syntax
A reference is${<scheme>:<reference>}. It may sit inside a larger value, and
one value may hold several:
Schemes
file reads Docker and Kubernetes secret mounts directly, so no entrypoint
script is needed to copy them into environment variables. The path must be a
regular file or a symlink to one, as those mounts are; a FIFO or device is
refused, because GoModel reads the file again on every reload.
env differs from the plain ${NAME} form in two ways: it is strict, and it
works in environment variables, not only in config.yaml. The plain form is
unchanged: ${NAME} and ${NAME:-default} are expanded in config.yaml, and
an unset ${NAME} is left in place, which drops a provider whose key it is.
External secret managers (HashiCorp Vault, AWS Secrets Manager, GCP Secret
Manager, Azure Key Vault) plug in as further schemes with
GoModel Pro.
Failures stop startup
A reference that cannot be resolved stops the gateway with an error naming the setting and the scheme, never the value:OPENAI_API_KEY_2: secret reference ${file:...}.
A scheme GoModel does not know is not repeated in the error, because it is
text taken from the value; check the named setting for a typo.
A failed reload keeps the running
configuration. GoModel never starts with, or sends upstream, an unresolved
reference.
Each reference has 30 seconds to resolve. One that takes longer, such as a
file on a stalled network mount, fails with context deadline exceeded
instead of holding up startup, a reload, or a dashboard save.
Only values in use are resolved: a provider value in config.yaml that an
environment variable overrides, a provider environment variable that GoModel
ignores (for example OPENAI_MODELS when several openai providers exist and
none is named openai), or a provider skipped for missing credentials, is not
looked up.
Rotation
GoModel does not pollenv or file references. After rotating one, reload:
gomodel --reload or SIGHUP.
Extensions that watch a secret manager, such as GoModel Pro vaults, tell
GoModel when a value changes. GoModel then re-resolves every reference and:
- swaps provider API keys in place when only they changed. Requests in flight finish on the key they started with. Sessions pinned to a removed key move to another one; most other sessions keep their key.
- reloads the configuration, as
SIGHUPdoes, when anything else inconfig.yamlor the environment changed (a DSN, the master key, a proxy URL, a service-account JSON). A failed reload keeps the running configuration and is retried on the next change. References saved from the dashboard never reload the configuration; see Dashboard-managed credentials. - keeps every current value when a reference cannot be re-resolved, and logs a warning naming the setting.
Dashboard-managed credentials
Provider credentials, MCP server headers, and guardrail secret fields saved from the dashboard or the admin API accept references too, in the same syntax:- The reference is stored as typed; GoModel resolves it when it builds the provider, MCP connection, or guardrail.
- A reference is not a secret, so the dashboard and the admin API show a
value made only of references unmasked; an MCP header may also start with
an auth scheme (
Bearer ${env:TOKEN}), and a proxy URL is shown when its password is a reference. Literal secrets, and values mixing literal text with a reference, stay masked, and sending the mask back keeps the stored value. - A reference that cannot be resolved is rejected on save with a
400that names the field and the scheme. - Only the master key can add or change a reference. A reference resolves on
the gateway host, so it can read any environment variable or file the
gateway can, the master key included. Any other credential, such as a
managed key with dashboard access, gets a
403(secret_reference_requires_master_key) naming the field. It can still save literal secrets, send the mask back, and re-save an entity whose references are unchanged. Without a master key, dashboard credentials save references as before. - For the same reason, only the master key can change where an entity that
holds a reference sends it: an MCP server’s
url, a provider’stype,base_url,backend, orproxy_url, or a guardrail’s type or service URL (such as Presidio’sanalyzer_url). Other credentials get the same403naming the field, and can still edit every other field. - Each resolver call carries a field path for auditing, such as
provider_credentials.openai.api_keys[0],mcp_servers.github.headers.Authorization, orguardrail_definitions.pii.config.api_key. - When a referenced value rotates, only the affected entity is reinstalled: a provider’s keys are swapped in place, any other provider change rebuilds that provider, an MCP server is reconnected, and a guardrail is rebuilt. If the reference cannot be resolved, the entity keeps its current value.
config.yaml are stored the same way: a
reference in a plugin’s secret field is saved as written and resolved when
the guardrail is built, so the database never holds the secret.
If a stored reference cannot be resolved at startup, the provider credential
or MCP server is logged and skipped, and the rest of the gateway starts. On a
reload, a provider credential or MCP server that is serving is never dropped
this way, nor because a key now resolves to an empty value: the reload fails,
naming the entity and field, and the running configuration, that entity
included, keeps serving. Fix or delete the entity, then reload
again. One that was already skipped is skipped again and does not block the
reload.
Guardrails load as a set and fail closed: startup fails, a reload keeps the
running configuration, and a periodic refresh keeps the guardrails it has.
Values saved before references existed are read the same way, so a stored
literal containing ${scheme:...} is now treated as a reference. Re-save it
with $${ to keep it literal.
An extension can register a secret writer. A literal typed in the dashboard
is then stored in the secret manager and only the reference is saved, and the
writer’s secrets are deleted when their entity or field is. A save that still
holds a written reference its entity no longer stores, such as a form opened
before someone else replaced that secret, is rejected: reload and save again.
Core GoModel ships no writer.
Literal ${
Write $${ for a literal ${. pa$${x} becomes pa${x}.
Limits
envandfilereferences are resolved when the configuration is loaded or reloaded; see Rotation.- They apply to string settings. A boolean or number setting cannot be a
reference, and an environment variable that GoModel parses as a number,
boolean, duration, or list (
LOGGING_ENABLED,HTTP_TIMEOUT,ENABLED_PASSTHROUGH_PROVIDERS, …) stops startup if it holds one. JSON variables accept references inside their string values. - Provider environment variables are the exception:
OPENAI_MODELS,OPENAI_MODEL_FILTER_*, andOPENAI_SESSION_STICKY_KEYSare resolved before they are parsed. - In dashboard-managed entities, only the fields listed in Dashboard-managed credentials accept references.