Шаблонизация конфигураций с envsubst и heredoc
Создавайте конфигурацию во время выполнения из переменных окружения с помощью envsubst и heredoc в кавычках
«Шаблонизация конфигураций с envsubst и heredoc» — бесплатный урок Linux Command Line & Bash Scripting Mastery на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Linux Command Line & Bash Scripting Mastery, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Linux Command Line & Bash Scripting Mastery содержит 4 уроков всего.
Почему важно создавать конфигурацию по шаблону во время выполнения
В рабочих процессах DevOps и контейнеризации файлы конфигурации, такие как nginx.conf, prometheus.yml и docker-compose.yml, часто должны изменяться в зависимости от среды — тестовой, производственной или DR. Жёстко заданные значения приводят к расхождениям конфигураций и раскрытию секретов.
Решение — создание конфигурации по шаблону во время выполнения: поставьте шаблон с заполнителями, а затем при запуске подставьте реальные значения из переменных окружения. Это сохраняет неизменность образа и позволяет проверять конфигурацию.
- Секреты не встраиваются в образы
- Один и тот же артефакт проходит через разные среды
- Конфигурация создаётся непосредственно перед запуском процесса
В Bash эту задачу упрощают два взаимодополняющих инструмента: envsubst и документированные строки heredoc в одинарных кавычках.
envsubst: генератор конфигурации в одну строку
envsubst — небольшая утилита GNU, которая читает стандартный ввод, заменяет заполнители $VARIABLE и ${VARIABLE} значениями из текущего окружения и записывает результат в стандартный вывод.
Она входит в пакет gettext и доступна практически в каждом дистрибутиве Linux и базовом образе Docker.
- Работает с любым текстовым форматом: NGINX, YAML, TOML, JSON, INI
- Не вычисляет синтаксис оболочки, а только заменяет ссылки на переменные
- Безопасна: не выполняет команды внутри шаблона
#!/usr/bin/env bash
# Install check (usually already present)
which envsubst || apt-get install -y gettext-base
# Minimal demo
export APP_PORT=8080
export APP_HOST=api.example.com
echo 'server { listen ${APP_PORT}; server_name ${APP_HOST}; }' | envsubst
# Output: server { listen 8080; server_name api.example.com; }Выборочная подстановка переменных
По умолчанию envsubst заменяет каждый найденный $VAR. Это может повредить переменные NGINX, такие как $uri или $host: это настоящие директивы NGINX, а не переменные окружения.
Передайте явный список переменных первым аргументом, чтобы ограничить замену только указанными именами:
envsubst '$VAR1 $VAR2'Аргумент представляет собой строку в одинарных кавычках (поэтому оболочка не раскрывает её), содержащую имена переменных, которые нужно заменить, разделённые пробелами или переводами строк.
#!/usr/bin/env bash
export APP_PORT=8080
export APP_HOST=api.example.com
# NGINX template contains both our vars AND nginx vars ($uri, $host)
TEMPLATE='server {
listen ${APP_PORT};
server_name ${APP_HOST};
location / {
proxy_set_header Host $host;
proxy_pass http://backend$uri;
}
}'
# Only substitute APP_PORT and APP_HOST — leave $host and $uri untouched
echo "$TEMPLATE" | envsubst '${APP_PORT} ${APP_HOST}'
Файлы шаблонов на диске
Для настоящих конфигураций храните шаблон в виде файла, например nginx.conf.template, рядом с Dockerfile. При запуске контейнера выполните envsubst, чтобы создать итоговый файл конфигурации до запуска службы.
Это канонический шаблон, используемый официальным образом NGINX.
#!/usr/bin/env bash
# File: nginx.conf.template
# (In practice this lives on disk; we write it here for demo purposes)
cat > /tmp/nginx.conf.template << 'TMPL'
server {
listen ${NGINX_PORT};
server_name ${SERVER_NAME};
root /var/www/${APP_ENV};
location / {
proxy_pass http://app:${APP_PORT};
}
}
TMPL
export NGINX_PORT=80
export SERVER_NAME=myapp.example.com
export APP_ENV=production
export APP_PORT=3000
# Generate final config
envsubst '${NGINX_PORT} ${SERVER_NAME} ${APP_ENV} ${APP_PORT}' \
< /tmp/nginx.conf.template \
> /tmp/nginx.conf
cat /tmp/nginx.confЦитируемые многострочные блоки: встроенные шаблоны без временного файла
Цитируемый многострочный блок (с использованием << 'EOF' и одинарных кавычек вокруг разделителя) не позволяет оболочке раскрывать переменные или выполнять подстановки команд внутри блока. Содержимое обрабатывается как литеральная строка.
Это делает многострочные блоки идеальным способом записать шаблон непосредственно в скрипте и сразу передать его через envsubst — промежуточный файл не нужен.
<< EOF(без кавычек) — оболочка немедленно раскрывает$VAR<< 'EOF'(в кавычках) — содержимое является литеральным; раскрытие откладывается доenvsubst
#!/usr/bin/env bash
export DB_HOST=postgres.internal
export DB_PORT=5432
export DB_NAME=myapp_prod
# Quoted heredoc: shell does NOT expand $DB_HOST etc. yet
envsubst << 'EOF'
[database]
host = ${DB_HOST}
port = ${DB_PORT}
dbname = ${DB_NAME}
EOF
# Output uses actual env var values — expansion done by envsubst, not the shellСочетание многострочных блоков с перенаправлением вывода
Передайте цитируемый многострочный блок через envsubst и перенаправьте результат в файл в рамках одного выражения. Это наиболее чистый идиоматичный способ создавать конфигурационные файлы в скрипте запуска.
Используйте выборочную подстановку ('${VAR1} ${VAR2}'), если целевой формат (Prometheus, NGINX и т. д.) использует собственный синтаксис $variable, который нужно защитить.
#!/usr/bin/env bash
# entrypoint.sh — Docker container entrypoint
set -euo pipefail
export PROM_PORT=${PROM_PORT:-9090}
export SCRAPE_INTERVAL=${SCRAPE_INTERVAL:-15s}
export TARGET_HOST=${TARGET_HOST:-localhost:8080}
envsubst '${PROM_PORT} ${SCRAPE_INTERVAL} ${TARGET_HOST}' << 'EOF' > /etc/prometheus/prometheus.yml
global:
scrape_interval: ${SCRAPE_INTERVAL}
evaluation_interval: ${SCRAPE_INTERVAL}
scrape_configs:
- job_name: 'app'
static_configs:
- targets: ['${TARGET_HOST}']
EOF
echo "[entrypoint] Prometheus config written on port ${PROM_PORT}"
exec prometheus --config.file=/etc/prometheus/prometheus.yml --web.listen-address=":${PROM_PORT}"Значения по умолчанию и проверка перед подстановкой
Никогда не полагайтесь на то, что все необходимые переменные заданы. Используйте раскрытие параметров Bash, чтобы задать значения по умолчанию или немедленно завершить выполнение с ошибкой:
${VAR:-default}— использоватьdefault, еслиVARне задана или пуста${VAR:?error message}— прервать выполнение с ошибкой, еслиVARне задана или пуста
Задайте эти переменные перед вызовом envsubst, чтобы шаблон всегда получал конкретное значение или скрипт заранее останавливался с понятным сообщением.
#!/usr/bin/env bash
set -euo pipefail
# Required — abort if missing
: "${DATABASE_URL:?DATABASE_URL must be set}"
: "${SECRET_KEY:?SECRET_KEY must be set}"
# Optional with defaults
export APP_PORT=${APP_PORT:-8000}
export LOG_LEVEL=${LOG_LEVEL:-info}
export WORKERS=${WORKERS:-4}
envsubst '${DATABASE_URL} ${SECRET_KEY} ${APP_PORT} ${LOG_LEVEL} ${WORKERS}' \
< /app/config/app.conf.template \
> /app/config/app.conf
echo "[init] Config generated — port=${APP_PORT} workers=${WORKERS} log=${LOG_LEVEL}"Создание конфигураций из нескольких разделов с помощью нескольких многострочных блоков
Для сложных конфигураций, состоящих из логических разделов, можно создавать каждый раздел независимо и объединять их либо использовать один многострочный блок, охватывающий весь файл. Оба подхода работают — выбирайте, исходя из удобства чтения.
Если разделы включаются условно (например, блок TLS добавляется только при заданном пути к сертификату), вариант с несколькими многострочными блоками и блоками if получается более понятным.
#!/usr/bin/env bash
set -euo pipefail
export APP_HOST=${APP_HOST:-localhost}
export APP_PORT=${APP_PORT:-8080}
export TLS_CERT=${TLS_CERT:-}
export TLS_KEY=${TLS_KEY:-}
CONFIG_FILE=/tmp/app.conf
# Base section
envsubst '${APP_HOST} ${APP_PORT}' << 'BASE' > "$CONFIG_FILE"
[server]
host = ${APP_HOST}
port = ${APP_PORT}
BASE
# Conditional TLS section — only appended when cert is provided
if [[ -n "$TLS_CERT" && -n "$TLS_KEY" ]]; then
envsubst '${TLS_CERT} ${TLS_KEY}' << 'TLS' >> "$CONFIG_FILE"
[tls]
cert_file = ${TLS_CERT}
key_file = ${TLS_KEY}
TLS
echo "[init] TLS enabled"
else
echo "[init] TLS disabled (no cert/key provided)"
fi
cat "$CONFIG_FILE"Шаблон точки входа Docker
Рекомендуемый шаблон точки входа Docker использует скрипт оболочки (docker-entrypoint.sh) для создания конфигураций при запуске, а затем передаёт управление главному процессу с помощью exec. Использование exec заменяет процесс оболочки процессом службы, поэтому сигналы (SIGTERM, SIGINT) напрямую доходят до службы — это важно для корректного завершения работы.
Файлы шаблонов добавляются в образ во время сборки; values внедряются во время выполнения с помощью docker run -e или Kubernetes env: / envFrom:.
#!/usr/bin/env bash
# docker-entrypoint.sh
set -euo pipefail
# Validate required env vars
for var in DATABASE_URL REDIS_URL SECRET_KEY; do
: "${!var:?$var is required}"
done
export APP_PORT=${APP_PORT:-8000}
export WORKERS=${WORKERS:-$(nproc)}
echo "[entrypoint] Generating configuration..."
envsubst '${DATABASE_URL} ${REDIS_URL} ${SECRET_KEY} ${APP_PORT} ${WORKERS}' \
< /app/config/settings.toml.template \
> /app/config/settings.toml
echo "[entrypoint] Starting server on port ${APP_PORT} with ${WORKERS} workers"
exec gunicorn app:application \
--bind "0.0.0.0:${APP_PORT}" \
--workers "${WORKERS}"Шаблон Kubernetes с ConfigMap и envsubst
В Kubernetes переменные окружения внедряются с помощью env: или envFrom: в спецификации Pod. Точка входа контейнера вызывает envsubst, чтобы сформировать конфигурации до запуска процесса, — отдельный ConfigMap для каждой среды не нужен.
Благодаря этому зависящие от среды значения хранятся в Kubernetes секретах и ConfigMaps (для нечувствительных данных), а шаблон конфигурации — в образе. Один образ — множество сред.
- Сборка:
COPY nginx.conf.template /etc/nginx/templates/ - Во время выполнения: точка входа запускает
envsubstи записывает результат в/etc/nginx/nginx.conf - K8s внедряет:
APP_PORT,BACKEND_HOSTиз Secret/ConfigMap
Отладка envsubst: поиск отсутствующих или нераскрытых переменных
Если сгенерированная конфигурация содержит литерал ${VAR} вместо значения, переменная не была экспортирована или не была включена в список подстановки. Для отладки используйте следующие приёмы:
printenv | sort— вывести список всех экспортированных переменных- Сравнить заполнители шаблона с экспортированными переменными с помощью
grep - Запустить
envsubstи найти в выводе оставшиеся шаблоны${ - Использовать
set -uв вызывающем скрипте, чтобы ссылки на неустановленные переменные в коде Bash немедленно прерывали выполнение
#!/usr/bin/env bash
set -euo pipefail
TEMPLATE=/tmp/app.conf.template
OUTPUT=/tmp/app.conf
# Write a demo template
cat > "$TEMPLATE" << 'EOF'
host=${DB_HOST}
port=${DB_PORT}
name=${DB_NAME}
EOF
export DB_HOST=db.internal
export DB_PORT=5432
# DB_NAME intentionally left unset
envsubst < "$TEMPLATE" > "$OUTPUT"
# Detect unresolved placeholders
if grep -qE '\$\{[A-Z_]+\}' "$OUTPUT"; then
echo "ERROR: unresolved placeholders found:"
grep -oE '\$\{[A-Z_]+\}' "$OUTPUT" | sort -u
exit 1
fi
echo "Config OK:"
cat "$OUTPUT"Проверка знаний: выборочная подстановка envsubst
Рассмотрите шаблон конфигурации NGINX, содержащий как переменную приложения ${APP_PORT}, так и встроенную переменную NGINX $uri. Выполните следующую команду:
envsubst < nginx.conf.template > nginx.conf
Каков результат?
Итоги урока: создание шаблонов конфигураций с помощью envsubst и многострочных блоков
Теперь у вас есть набор инструментов промышленного уровня для создания конфигураций во время выполнения в Bash:
- envsubst заменяет заполнители
${VAR}в любом текстовом файле значениями из текущего окружения — без написания скриптов и специального экранирования - Выборочная подстановка (
envsubst '${VAR1} ${VAR2}') защищает встроенные переменные в NGINX, Prometheus и подобных инструментах от случайной замены - Цитируемые многострочные блоки (
<< 'EOF') откладывают раскрытие оболочкой, поэтому содержимое шаблона без изменений передаётся вenvsubst— временные файлы не нужны - Проверяйте данные перед подстановкой: используйте
${VAR:?message}, чтобы прерывать выполнение при отсутствии обязательных переменных, и${VAR:-default}для необязательных - Шаблон точки входа Docker: создавайте конфигурации при запуске контейнера, а затем используйте
execдля службы, чтобы сигналы обрабатывались правильно - Отлаживайте нераскрытые заполнители, находя в выводе оставшиеся шаблоны
${до запуска процесса
Эти шаблоны сохраняют образы контейнеров неизменяемыми, не допускают попадания секретов в систему контроля версий и обеспечивают единообразие конфигураций во всех средах.
Часто задаваемые вопросы
Урок «Шаблонизация конфигураций с envsubst и heredoc» бесплатный?
Да — полный текст урока «Шаблонизация конфигураций с envsubst и heredoc» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Linux Command Line & Bash Scripting Mastery, подпишись на CoddyKit PRO. Курс Linux Command Line & Bash Scripting Mastery содержит 4 уроков всего.
Чему я научусь в уроке «Шаблонизация конфигураций с envsubst и heredoc»?
Создавайте конфигурацию во время выполнения из переменных окружения с помощью envsubst и heredoc в кавычках Ты практикуешь Linux Command Line & Bash Scripting Mastery с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Linux Command Line & Bash Scripting Mastery?
Предыдущий опыт не требуется. Linux Command Line & Bash Scripting Mastery на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Шаблонизация конфигураций с envsubst и heredoc»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Linux Command Line & Bash Scripting Mastery?
Да. Каждый урок Linux Command Line & Bash Scripting Mastery включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Компактные Dockerfile и точки входа Shell
- Шаблонизация конфигураций с envsubst и heredoc
- Создание облачных ресурсов через CLI и jq
- Проверки состояния, барьеры готовности и циклы ожидания