Documentsapisix
YAML Environment Variable Substitution
YAML Environment Variable Substitution
Type
Topic
Status
Published
Created
Jul 9, 2026
Updated
Jul 9, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

YAML Environment Variable Substitution#

Overview#

APISIX supports injecting environment variables into YAML configuration files using the ${{VAR_NAME}} syntax. This mechanism is implemented in apisix/cli/file.lua and historically operates post-parse — the raw YAML is loaded first by lyaml, then the resulting Lua table is walked recursively to substitute and type-coerce variable values.

As of v3.17.0 (PR #13078, merged April 2026): Standalone mode (apisix.yaml) now uses a pre-parse path via resolve_conf_var_in_text(), which runs substitution on raw YAML text before yaml.load(). The post-parse path (resolve_conf_var()) remains in use for config.yaml and other non-standalone paths.


Syntax#

PatternBehavior
${{MY_VAR}}Substitutes the value of MY_VAR; fails if not set
${{MY_VAR:=default}}Substitutes MY_VAR; falls back to default if not set

The ${{...}} delimiter was chosen to avoid conflicts with Nginx's own $var and ${var} variable syntax .


Implementation#

var_sub(val) — String-level substitution#

var_sub is the core helper. It applies a gsub pattern to replace all ${{...}} placeholders in a single string:

  • Extracts the variable name and optional default via the := separator
  • Trims whitespace from the default value
  • Returns (new_val, var_used, err) — the var_used flag tells callers whether substitution occurred

resolve_conf_var(conf) — Table-level, post-parse walk#

resolve_conf_var recursively traverses the parsed config table:

  • Substitutes env vars in both keys and values
  • For string values, calls var_sub; if a variable was used, applies type coercion :
    • tonumber(new_val) ~= nil → Lua number
    • "true" / "false" → Lua boolean
    • Otherwise → remains a string
  • Exported as _M.resolve_conf_var for use by other modules

resolve_conf_var_in_text(text) — Raw-text, pre-parse substitution (standalone mode)#

Introduced in PR #13078, this function runs var_sub() on the raw YAML string before yaml.load() is called. Pre-parse substitution lets YAML's native type system determine the final type:

  • "${{VAR}}" (quoted in YAML) → always a string
  • ${{VAR}} (unquoted in YAML) → type inferred from the resolved value

This path is used for apisix.yaml in read_yaml_conf() and in apisix/core/config_yaml.lua for the runtime YAML config loader .

Reserved env var overrides#

A separate function replace_by_reserved_env_vars handles APISIX_DEPLOYMENT_ETCD_HOST, which accepts a JSON-encoded array and directly sets deployment.etcd.host. This path bypasses the ${{...}} mechanism entirely.


Type Inference Rules#

Post-parse path (config.yaml): Type coercion fires when var_used is true .

Large integer edge case: Values exceeding 15 digits risk precision loss when cast to Lua doubles (IEEE 754). PR #13078 added an exceeds_lua_precision() guard that skips tonumber() for pure-integer values longer than 15 digits, preserving them as strings. This fixes issue #12932 where values like 356002209726529540 were silently converted to scientific notation.

Pre-parse path (apisix.yaml): No Lua-side coercion needed — YAML's own parser determines the type based on quoting and content.


Call Chains#

config.yaml path :

read_yaml_conf()
  → yaml.load(user_conf_yaml) # parse raw YAML
  → resolve_conf_var(user_conf) # post-parse substitution + type coerce
  → merge_conf(default_conf, user_conf)
  → schema.validate(default_conf)
  → replace_by_reserved_env_vars() # APISIX_DEPLOYMENT_ETCD_HOST override

apisix.yaml / standalone mode (v3.17.0+) :

read_yaml_conf()
  → resolve_conf_var_in_text(raw_yaml) # pre-parse substitution on raw text
  → yaml.load(resolved_yaml) # YAML type inference

Known Issues#

Comment-line processing bug (v3.17.0 — issue #13685)#

The pre-parse substitution path has no awareness of YAML comment syntax. Any ${{VAR}} reference in a commented-out line (e.g., # ${{MY_ENV_VAR}}) is evaluated, causing a boot failure if the variable is not set :

failed to read local yaml config of apisix: failed to handle configuration: can't find environment variable MY_ENV_VAR

The existing is_empty_yaml_line helper (which matches '^%s*#') is only used to detect whether an entire config file is blank — it does not filter lines before the var_sub scan.

Workaround: Set the variable to any value, or remove the commented-out placeholder.

Affected version: v3.17.0 .


Key Source Files#

FilePurpose
apisix/cli/file.luaCore implementation: var_sub, resolve_conf_var, read_yaml_conf
apisix/core/config_yaml.luaRuntime YAML config loader; uses resolve_conf_var_in_text post-3.17.0
  • PR #13078 — Introduces pre-parse substitution for standalone mode; fixes large-integer precision loss (issue #12932)
  • PR #13503 — Release 3.17.0 (includes PR #13078 as a breaking change)
  • Issue #13685 — Comment-line processing regression introduced in v3.17.0
YAML Environment Variable Substitution | Dosu