Перейти к основному содержимому

Подстановка env-переменных

Значения в YAML-конфиге могут ссылаться на переменные окружения через синтаксис ${...}. Интерполяция выполняется при загрузке файла, до слияния слоёв и до типизированного доступа.

Синтаксис

${VAR} → значение env-переменной VAR; если не задана — ConfigError
${VAR:-default} → значение VAR, или literal "default" если VAR не задана / пустая

Примеры:

database:
host: "${DB_HOST:-localhost}"
port: "${DB_PORT:-5432}"
password: "${DB_PASSWORD}"
pool_size: "${DB_POOL_SIZE:-20}" # всё ещё строка, число приводится к типу позже

cache:
url: "${REDIS_URL:-redis://localhost:6379/0}"

Правила семантики

  1. Время интерполяциипри загрузке файла (не при каждом чтении). Меняется env-переменная после Config.load() → конфиг не обновляется автоматически; для hot-reload нужен ConfigSource.watch.

  2. Строковый тип после интерполяции${NUM} возвращает строку. Type coercion (getInt, getBool) происходит при чтении, не при интерполяции:

    database:
    pool_size: "${DB_POOL_SIZE:-20}" # строка "20" после интерполяции
    config.getInt("database.pool_size") # 20 — приводится к типу на лету
    config.getString("database.pool_size") # "20" — остаётся строкой
  3. Правила приведения типов (строгие):

    • int: ^-?\d+$.
    • bool: true|false|yes|no|1|0 (case-insensitive).
    • float: стандартный парсер чисел с плавающей точкой.
    • Если строка не соответствует типу → ConfigError(type_mismatch).
  4. Default-значения — literal string — вложенные ${...} в default-части не интерполируются. Это оставляет парсер простым:

    database:
    # ОК:
    host: "${DB_HOST:-localhost}"
    # НЕ работает (default literal):
    password: "${DB_PASSWORD:-${FALLBACK_PASSWORD}}" # default будет "${FALLBACK_PASSWORD}" как строка

    Если нужен двухэтапный резервный вариант — используйте внешний скрипт / shell-exec, или логику в приложении.

  5. Escape literal $$$$. Полезно, когда литерально нужен символ $:

    billing:
    price_prefix: "$$" # literal "$"

Отсутствующая env без default

database:
password: "${DB_PASSWORD}" # без default

Если DB_PASSWORD не задана в окружении, Config.load выбрасывает ConfigError:

ConfigError(
path="database.password",
reason="env_unresolved",
details="Environment variable DB_PASSWORD is not set and no default provided",
)

Приложение получает явную ошибку на старте — пароль не подставляется пустой строкой, что привело бы к рантайм-проблемам («authentication failed»).

Где писать env-переменные

Локальная разработка: .env-файл, подхватываемый инструментом (direnv / dotenv):

.env (gitignored)
DB_HOST=localhost
DB_PASSWORD=local-dev-pw
REDIS_URL=redis://localhost:6379/0
DAGSTACK_ENV=

Docker / production: через env: в docker-compose.yml / kubernetes/deployment.yaml.

CI: через secrets-mechanism CI-системы (GitHub Actions Secrets / Gitea Secrets).

dagstack/config не читает .env-файлы сам — это ответственность того, кто запускает процесс (Python python-dotenv, Node dotenv, direnv, и т.п.). К моменту запуска приложения нужные переменные уже в os.environ / process.env.

Подстановка в разных слоях

Env-интерполяция применяется к каждому файловому слою независимо. Результат: разные слои могут ссылаться на разные env-переменные, merge работает на уже интерполированных значениях.

app-config.yaml (base)
database:
password: "${DB_PASSWORD}"
app-config.production.yaml
database:
password: "${DB_PASSWORD_PRODUCTION}" # отдельная env-переменная в prod

Для DAGSTACK_ENV=production:

  1. Загружается base → ${DB_PASSWORD}"local-pw" из окружения.
  2. Загружается production → ${DB_PASSWORD_PRODUCTION}"prod-pw".
  3. Merge: значение production побеждает → password: "prod-pw".

Типичные анти-паттерны

# ✗ Слишком много магии — приложение зависит от специфических env:
feature_flags:
new_ui_enabled: "${NEW_UI_ENABLED:-true}" # зачем env-переопределение для каждого флага?

# ✓ Лучше — базовое значение в YAML, переопределение через app-config.${ENV}.yaml:
feature_flags:
new_ui_enabled: true
# ✗ Секрет с default-значением-заглушкой:
database:
password: "${DB_PASSWORD:-admin123}" # в prod без env — попадает default (опасно!)

# ✓ Без default — явная ошибка на старте если забыли env:
database:
password: "${DB_PASSWORD}"
# ✗ Default содержит env-переменную (не будет интерполирован):
database:
url: "${DATABASE_URL:-postgresql://user:${DB_PASSWORD}@localhost/dev}"

# ✓ Использовать отдельную env или логику в приложении:
database:
url: "${DATABASE_URL}"
# Сборка url'а из частей — в коде приложения, не в YAML.

См. также