Змінні середовища не потрапляють у контейнер
Змінні середовища, які не працюють у контейнері, зазвичай не працюють з однієї з п’яти причин: час складання замість часу виконання, пакування 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, встановіть закодований рядок і декодуйте його в застосунку. Однорядкові поля для змінних середовища видаляють символи нового рядка, через що виникають помилки синтаксичного аналізу без згадки про нові рядки.
