Подстановка переменных окружения
Значения в YAML-конфигурации могут ссылаться на переменные окружения через синтаксис ${...}. Интерполяция запускается на этапе загрузки файла, до слияния слоёв и до типизированного доступа.
Синтаксис
${VAR} → значение переменной окружения VAR; если не задана — ConfigError
${VAR:-default} → значение VAR или литерал "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}"
Семантика
-
Когда запускается интерполяция — в момент загрузки файла (а не на каждом чтении). Если env-переменная меняется после
Config.load(), конфигурация не обновляется автоматически; для горячей перезагрузки нуженConfigSource.watch. -
Результат всегда строка —
${NUM}даёт строку. Приведение типа (getInt,getBool) происходит при чтении, не во время интерполяции:database:pool_size: "${DB_POOL_SIZE:-20}" # строка "20" после интерполяции- Python
- TypeScript
- Go
config.getInt("database.pool_size") # 20 — приводится на летуconfig.getString("database.pool_size") # "20" — остаётся строкойconfig.getInt("database.pool_size"); // 20config.getString("database.pool_size"); // "20"cfg.GetInt("database.pool_size") // 20cfg.GetString("database.pool_size") // "20" -
Правила приведения типов (строгие):
int:^-?\d+$.bool:true|false|yes|no|1|0(без учёта регистра).float: стандартный парсер чисел с плавающей точкой.- Если строка не подходит под тип →
ConfigError(type_mismatch).
-
Значения по умолчанию — литеральные строки — вложенные
${...}внутри части default не интерполируются. Это сохраняет парсер простым:database:# OK:host: "${DB_HOST:-localhost}"# Не работает (default — литерал):password: "${DB_PASSWORD:-${FALLBACK_PASSWORD}}" # default будет литералом "${FALLBACK_PASSWORD}"Если нужен двухступенчатый резерв, используй внешний скрипт / shell-exec или обработай это в коде приложения.
-
Экранирование литерального
$—$$→$. Полезно, когда нужен буквально символ$:billing:price_prefix: "$$" # литерал "$"
Отсутствующая 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):
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: через механизм секретов CI-системы (GitHub Actions Secrets / Gitea Secrets).
dagstack/config не читает .env-файлы сам — это ответственность того, кто запускает процесс (Python python-dotenv, Node dotenv, direnv и так далее). К моменту старта приложения переменные уже доступны в os.environ / process.env.
Подстановка между слоями
Интерполяция env запускается независимо для каждого слоя-файла. Результат: разные слои могут ссылаться на разные env-переменные, и слияние работает уже над интерполированными значениями.
database:
password: "${DB_PASSWORD}"
database:
password: "${DB_PASSWORD_PRODUCTION}" # отдельная env-переменная в prod
При DAGSTACK_ENV=production:
- Загружается базовый слой →
${DB_PASSWORD}→"local-pw"из окружения. - Загружается production-слой →
${DB_PASSWORD_PRODUCTION}→"prod-pw". - Слияние: значение 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
# ✗ Секрет с placeholder-значением по умолчанию:
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-переменная или сборка URL в коде приложения:
database:
url: "${DATABASE_URL}"
# Собирай URL из частей в приложении, а не в YAML.
См. также
- Секреты — env как основной канал для секретов.
- Слои конфигурации — подстановка env внутри каждого слоя.
- ADR-0001 §2 Синтаксис интерполяции env — нормативный контракт.