EnvironmentFile в systemd: почему переменная работает в терминале, но не в сервисе

В терминале команда source app.env задаёт путь к конфигурации, и приложение стартует. Тот же файл подключают через EnvironmentFile, но сервис пытается открыть путь с буквальным знаком доллара. Причина в том, что systemd читает файл назначений переменных, а не выполняет сценарий оболочки.

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

Уберём неоднозначные подстановки

Файл, рассчитанный на shell, мог содержать APP_CONFIG=$APP_ROOT/config.yaml. EnvironmentFile не раскрывает здесь APP_ROOT. Запишите значение целиком в /etc/myapp/app.env:

APP_ROOT=/opt/myapp
APP_CONFIG=/etc/myapp/config.yaml
APP_LABEL="production api"

Файл содержит назначения по строкам без export и команд. Кавычки помогают оформить значение с пробелом, но правила разбора не полностью совпадают с shell. Сохраните UTF-8 и не добавляйте исполняемую логику. Конструкция $(command) останется содержимым значения, а не способом получить результат команды.

Подключим окружение к правильному unit

[Service]
EnvironmentFile=/etc/myapp/app.env
ExecStart=/opt/myapp/bin/server --config ${APP_CONFIG}

Это фрагмент service unit. В ExecStart systemd поддерживает собственную подстановку переменных окружения; форма ${APP_CONFIG} передаёт значение как один аргумент. Эта возможность не означает, что EnvironmentFile стал shell-скриптом. Путь к исполняемому файлу и полная команда должны оставаться корректными для правил ExecStart.

Если перед путём EnvironmentFile поставить минус, отсутствие файла будет игнорироваться. Это удобно для действительно необязательного дополнения, но может скрыть опечатку в обязательной production-конфигурации. Выберите поведение осознанно, чтобы сервис не запускался с неожиданными defaults.

Проверим, что читает manager

systemctl cat myapp.service
systemctl show myapp.service -p EnvironmentFiles -p ExecStart -p User
journalctl -u myapp.service -b --since '-10 min' --no-pager

Сопоставьте основной unit и drop-in. Другой файл может переопределять переменную или команду. Если указано несколько environment-файлов, порядок тоже важен: поздние значения могут перекрывать ранние. Само наличие строки в одном найденном файле не доказывает, что процесс получил её.

Изменение unit требует перечитать конфигурацию manager штатной процедурой. Изменение содержимого EnvironmentFile обычно не требует daemon-reload само по себе, но уже запущенный процесс не получает новые значения автоматически. Их читает следующий запуск; reload приложения помогает только если именно приложение умеет перечитать нужный источник.

Убедимся в результате без дампа секретов

Для диагностики выберите одно несекретное значение, например путь APP_CONFIG или режим работы, и подтвердите его в сообщении приложения при старте. Полный вывод окружения в общий журнал часто раскрывает лишние данные. Если нужен специальный диагностический запуск, ограничьте его тестовым сервисом и конкретным разрешённым полем.

Окружение процесса не заменяет права на файл конфигурации. Значение может быть правильным, а открытие завершаться Permission denied из-за пользователя unit, каталогов или sandbox-настроек. После подтверждения строки пути проверьте доступ именно из контекста сервиса.

Для секретов оцените механизм credentials systemd или предусмотренное приложением защищённое хранилище. Обычные environment variables распространяются дочерним процессам и не предназначены для любых секретов автоматически. Здесь важно выбрать способ доставки данных, соответствующий их назначению, а не переносить весь личный профиль shell в service.

После исправления выполните согласованный перезапуск в тестовой среде и реальный запрос приложения. Проверьте, что используется нужная конфигурация и повторный запуск даёт тот же результат. Так переменные становятся воспроизводимой частью развёртывания, а не зависимостью от открытого терминала администратора.

Ещё по теме

Продолжить изучение

Хотите перейти от отдельных команд к сопровождению Linux-серверов? Работа с системой, скриптами и сервисами входит в программу «Linux и Bash для DevOps-инженеров».

Продолжить на практике

  • Linux и Bash для DevOps-инженеров ↗

    Linux для работы с серверами: процессы, права, systemd, сеть, диски и логи. Автоматизируйте повседневные задачи на Bash и разберите типичные сбои сервиса — от занятого порта до переполненного диска.