0Pricing
DevOps Bootcamp · Урок

Шаблонизация конфигураций с envsubst и heredoc

Создавайте конфигурацию во время выполнения из переменных окружения с помощью envsubst и heredoc в кавычках

«Шаблонизация конфигураций с envsubst и heredoc» — бесплатный урок DevOps Bootcamp на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DevOps Bootcamp, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DevOps Bootcamp содержит 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) и разблокировать остальной курс DevOps Bootcamp, подпишись на CoddyKit PRO. Курс DevOps Bootcamp содержит 4 уроков всего.

Чему я научусь в уроке «Шаблонизация конфигураций с envsubst и heredoc»?

Создавайте конфигурацию во время выполнения из переменных окружения с помощью envsubst и heredoc в кавычках Ты практикуешь DevOps Bootcamp с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать DevOps Bootcamp?

Предыдущий опыт не требуется. DevOps Bootcamp на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Шаблонизация конфигураций с envsubst и heredoc»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке DevOps Bootcamp?

Да. Каждый урок DevOps Bootcamp включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Компактные Dockerfile и точки входа Shell
  2. Шаблонизация конфигураций с envsubst и heredoc
  3. Создание облачных ресурсов через CLI и jq
  4. Проверки состояния, барьеры готовности и циклы ожидания
← Назад к DevOps Bootcamp