Індекс журналуDockup / польова нотатка
Note / env-vars-not-reaching-container

Змінні середовища не потрапляють у контейнер

Змінні середовища, які не працюють у контейнері, зазвичай не працюють з однієї з п’яти причин: час складання замість часу виконання, пакування frontend, лапки, час перезапуску або неправильна область видимості. Перевірте їх саме в такому порядку.

Ви встановили змінну. На дашборді вона відображається. Застосунок повідомляє, що вона має значення undefined. Змінні середовища, які не працюють у контейнері, — одна з найпоширеніших проблем конфігурації під час хостингу застосунків, і майже завжди причина полягає в одній із п’яти конкретних речей.

Нижче вони наведені в порядку, який дає змогу найшвидше знайти проблему.

1. Час складання й час виконання — це різні світи

Це спричиняє більше таких проблем, ніж інші чотири причини разом, і саме це найважче зрозуміти інтуїтивно.

Змінні, задані для вашого сервісу, існують, коли контейнер запускається. Усе, що виконує ваш Dockerfile, відбувається раніше, в окремому середовищі. Крок RUN не може побачити змінну часу виконання, тому що в цей момент часу виконання ще немає.

# This is empty during build. Always.
RUN echo $DATABASE_URL

# This is available at runtime, because it is the running process reading it
CMD ["node", "server.js"]

Якщо вам справді потрібне значення під час складання, його потрібно передати як аргумент складання — це інший механізм з іншими властивостями безпеки:

ARG BUILD_VERSION
RUN echo "Building $BUILD_VERSION"

Ніколи не передавайте так секрети. Аргументи складання записуються в історію шарів образу. Будь-хто, хто може завантажити образ, зможе їх прочитати.

2. Змінні frontend вбудовуються, а не зчитуються

Якщо ваш frontend повідомляє undefined у production, майже напевно причина саме в цьому.

У браузера немає середовища. Коли ви пишете import.meta.env.VITE_API_URL або process.env.NEXT_PUBLIC_API_URL, bundler підставляє буквальний рядок під час складання. У браузері не відбувається жодного пошуку — значення вже було скомпільоване.

Ось три наслідки, які часто застають зненацька:

  • Зміна змінної нічого не дає, доки ви не виконаєте повторне складання. Старе значення вже міститься у JavaScript-файлі.
  • Префікс є обов’язковим. Vite відкриває доступ лише до VITE_, а Next.js — лише до NEXT_PUBLIC_. Змінна без префікса навмисно не передається.
  • Усе, що відкривається таким способом, є публічним. Воно міститься у файлі, який ви надаєте будь-кому. Ніколи не розміщуйте секрет за NEXT_PUBLIC_, попри те, що може підказувати назва.

Саме тому попередньо зібраний образ не можна налаштувати таким способом постфактум. Якщо образ було зібрано в іншому місці зі вже вбудованими значеннями, встановлення змінних для сервісу нічого не змінить — рядки вже містяться у bundle.

3. Лапки

Значення зі спеціальними символами можуть спотворюватися так, що замість очевидної помилки ви отримуєте заплутані повідомлення.

# The shell eats everything after #
dockup env set DB_PASS=p@ss#word my-project/my-api

# Quote it
dockup env set 'DB_PASS=p@ss#word' my-project/my-api

Проблеми спричиняють такі символи: # (коментар), $ (підстановка), пробіли (розділення аргументів), ! (підстановка з історії в інтерактивному bash) і символи нового рядка — вони з’являються в одному поширеному випадку: у приватних ключах.

Багаторядкові значення створюють найбільше проблем. PEM-ключ, вставлений у однорядкове поле, надходить без символів нового рядка й спричиняє помилку синтаксичного аналізу, у якій нічого не сказано про нові рядки. Закодуйте його в Base64 і декодуйте в застосунку:

dockup env set "PRIVATE_KEY_B64=$(base64 -i key.pem)" my-project/my-api

4. Ви не перезапустили процес

Процес зчитує змінні середовища під час запуску. Їхня зміна впливає на наступний процес, а не на той, який уже працює.

Більшість платформ автоматично виконує повторне розгортання після зміни конфігурації, але не всі. Крім того, часткова зміна — встановити три змінні, виконати повторне розгортання, а потім встановити четверту — залишає одну змінну поза процесом оновлення.

dockup env list my-project/my-api --json   # what is configured
dockup restart my-project/my-api            # make the process re-read it

Перевірка, яка остаточно все прояснює: прочитайте змінну всередині запущеного контейнера, а не на дашборді.

dockup exec "printenv | sort" my-project/my-api

Якщо змінна є у цьому виводі, а застосунок і далі повідомляє undefined, проблема у вашому коді. Якщо її немає у виводі, проблема в конфігурації. Одна ця команда ділить область пошуку навпіл.

5. Неправильна область видимості

Змінні зазвичай мають певну область видимості — сервіс, середовище або проєкт. Змінна, встановлена для production, не буде видимою у preview-середовищі. Так само змінна, встановлена для іншого сервісу в тому самому проєкті, також не буде доступною.

Це найчастіша причина, коли щось працює в одному місці, але не працює в іншому за ідентичного коду.

Порядок діагностики

# 1. Is it actually in the container's environment?
dockup exec "printenv | sort" my-project/my-api

# 2. Is it configured on the service you think it is?
dockup env list my-project/my-api --json

# 3. Is the running process older than the change?
dockup status my-project/my-api --json

Завжди починайте з кроку 1. Він перетворює нечітку проблему на одну з двох однозначних.

Окремо про секрети

Незалежно від платформи варто дотримуватися двох правил.

Позначайте секрети як секрети. У Dockup змінна, позначена як секрет, маскується у списках і відповідях API — dockup env list показує ********, а не її значення. Це важливіше, ніж може здаватися, адже найпоширеніший спосіб витоку облікових даних — не атака, а скриншот, звернення до служби підтримки або рядок у журналі.

Не зберігайте їх в аргументах складання та frontend bundle. Обидва способи дають змогу прочитати секрет будь-кому, хто отримає артефакт. Просте правило: якщо значення потрапляє у файл, який ви поширюєте, це більше не секрет.

Поширені запитання

Чому моя змінна середовища має значення undefined під час складання? Тому що складання й виконання відбуваються в окремих середовищах. Змінних часу виконання не існує, поки образ ще збирається. Використовуйте аргумент складання, якщо вам справді потрібне значення під час складання, — але ніколи не передавайте так секрет.

Чому мій frontend не бачить змінну? Bundler підставляє значення під час складання й відкриває доступ лише до змінних із префіксами — VITE_, NEXT_PUBLIC_. Щоб зміна змінної набула чинності, потрібно виконати повторне складання, а все, що відкривається таким способом, можна прочитати публічно.

Чи потрібно перезапускати процес після зміни змінної? Так. Запущений процес уже зчитав своє середовище. Більшість платформ автоматично виконує повторне розгортання після зміни; перевірте це за допомогою printenv усередині контейнера, а не покладайтеся на дашборд.

Як передати багаторядкове значення, наприклад приватний ключ? Закодуйте його в Base64, встановіть закодований рядок і декодуйте його в застосунку. Однорядкові поля для змінних середовища видаляють символи нового рядка, через що виникають помилки синтаксичного аналізу без згадки про нові рядки.