Docker Compose съел доллар в команде: различаем интерполяцию и shell
В контейнере задана переменная GREETING, но команда печатает пустую строку. В файле Compose написано echo "$GREETING", значение видно в разделе окружения, и кажется, что приложение его игнорирует. Между файлом и процессом здесь работают два разных механизма: Compose подставляет значения при чтении конфигурации, а оболочка может подставить их позже внутри контейнера.
Когда Compose встречает подходящее имя после доллара, он ищет значение в своём окружении и источниках интерполяции. Раздел environment сервиса задаёт окружение будущего процесса и сам по себе не служит источником для такой подстановки. Поэтому имя может разрешиться в пустую строку ещё до создания контейнера. Смотреть только на переменные запущенного приложения в этот момент уже поздно.
Выбираем, на каком этапе нужен доллар
Для значения, которое должен вычислить Compose, используйте обычную запись вроде ${IMAGE_TAG}. Для обращения к переменной будущего процесса запишите двойной доллар: $$GREETING. Он защищает знак от интерполяции Compose. Затем нужен процесс, который действительно умеет раскрывать переменные: в примере явно запускается /bin/sh -c.
Массив команды сам по себе не включает shell. Если передать echo и строку с долларом напрямую программе, она получит аргумент как текст. Поэтому исправление состоит из двух согласованных частей: сохранить знак при чтении конфигурации и поручить подстановку оболочке в контейнере. Ни кавычки YAML, ни наличие переменной в окружении не заменяют второй шаг.
Минимальный файл с поздней подстановкой
Сохраните следующий файл как compose.yaml в отдельном каталоге. Здесь одноразовый сервис печатает приветствие, заданное в собственном окружении. Значение не зависит от переменной с таким же именем на машине разработчика. Команда задаётся массивом, а сценарий оболочки остаётся одним аргументом после -c.
services:
demo:
image: alpine:3.22
environment:
GREETING: Hello from container
command:
- /bin/sh
- -c
- 'printf "%s\n" "$$GREETING"'
Перед запуском выполните docker compose -f compose.yaml config --format json. Команда покажет разобранную модель проекта и позволяет найти ошибку без старта контейнера. Для сравнения дополнительно используйте docker compose -f compose.yaml config --no-interpolate: так видно исходные выражения. При обычном выводе Compose может сохранять экранированный доллар для представления команды, поэтому количество знаков в напечатанном YAML нельзя само по себе считать выводом shell.
Когда доступен Docker Engine, команда docker compose -f compose.yaml run --rm demo должна вывести Hello from container. Удобно проверить случай, когда на хосте переменная с таким именем отсутствует, и случай, когда она имеет другое значение. Результат контейнерной команды должен совпасть: поздняя подстановка читает окружение сервиса.
Почему замена кавычек часто не помогает
Кавычки участвуют сразу в нескольких языках: YAML, описании аргументов и сценарии shell. Одиночные кавычки в файле Compose не следует считать универсальным способом выключить интерполяцию всех значений конфигурации. Для этого случая используйте предназначенный механизм $$, описанный в правилах интерполяции Compose. Так намерение остаётся видимым следующему разработчику.
Если приложение умеет читать окружение самостоятельно, проще передать команду без оболочки и вообще не вставлять переменную в аргументы. Например, сервер может взять порт из настройки при запуске. Оболочка нужна, когда вы сознательно используете её функции: подстановку, конвейер или несколько операций. Для длинной команды вынесите сценарий в файл: тогда меньше шансов перепутать уровни кавычек и легче проверить аргументы.
Отдельно просмотрите итоговый раздел environment. Если там уже неверное значение, разбирать shell рано: сначала выясните приоритет источников. Эту соседнюю задачу разбирает статья о переменной Compose, которая не обновилась. В текущем случае значение верное, а теряется именно обращение к нему в команде. Это различие подсказывает, какую часть конфигурации исправлять.
Собираем воспроизводимый запуск
Для команд с несколькими переменными разберите каждую отдельно: тег образа обычно выбирается на хосте, а адрес рабочей директории может вычисляться внутри контейнера. Их допустимо смешивать в одном проекте, если намерение явно видно по записи. При проверке попросите коллегу назвать источник каждого значения до запуска: неоднозначные места сразу станут заметны.
В платном курсе «Docker и Docker Compose: контейнеризация приложений» — откройте программу вы отработаете команды запуска и окружение, затем соедините приложение с PostgreSQL, настроите готовность сервисов и хранение данных. Следующая задача после исправления доллара — передать проект коллеге так, чтобы образ, команда и настройки давали тот же результат на другой машине. Разбор конфигурации и диагностика по состоянию контейнеров входят в подтверждённые материалы курса.
Продолжить на практике
- Docker и Docker Compose: контейнеризация приложений ↗
Упакуйте приложение в Docker и запустите его с PostgreSQL через Compose. Образы, сети, тома, диагностика поломок, обновление версий и восстановление данных — с практикой и итоговым проектом.
