Променливите на средата не достигат до контейнера
Променливите на средата, които не работят в контейнер, обикновено се провалят по една от пет причини: време на build спрямо runtime, bundling на frontend, кавички, момент на рестартиране или грешен scope. Проверете ги в този ред.
Задали сте променливата. В dashboard-а я виждате. Приложението показва undefined. Променливите на средата, които не работят в контейнер, са един от най-често срещаните конфигурационни проблеми при хостване на приложения и почти винаги причината е една от пет конкретни.
Те са изброени в реда, който най-бързо открива проблема.
1. Build time и runtime са два различни свята
Това причинява повече такива проблеми от останалите четири причини, взети заедно, а и е най-трудното за интуитивно разбиране.
Променливите, зададени за вашия service, съществуват, когато контейнерът работи. Всичко, което прави вашият Dockerfile, се случва по-рано, в отделна среда. Стъпка RUN не може да види runtime променлива, защото в този момент няма runtime.
# 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"]
Ако наистина ви е необходима стойност по време на build, тя трябва да бъде подадена като build argument — различен механизъм с различни характеристики по отношение на сигурността:
ARG BUILD_VERSION
RUN echo "Building $BUILD_VERSION"
Никога не подавайте секрет по този начин. Build аргументите се записват в историята на layer-ите на image-а. Всеки, който може да изтегли image-а, може да ги прочете.
2. Frontend променливите се вграждат, а не се прочитат
Ако вашият frontend показва undefined в production, почти сигурно това е причината.
Браузърът няма environment. Когато напишете import.meta.env.VITE_API_URL или process.env.NEXT_PUBLIC_API_URL, bundler-ът заменя стойността с литерален string по време на build. В браузъра не се извършва lookup — стойността вече е компилирана в него.
Три последствия, които често създават проблеми:
- Промяната на променливата няма да направи нищо, докато не направите нов build. Старата стойност се намира вътре в JavaScript файла.
- Префиксът е задължителен. Vite предоставя само
VITE_, а Next.js — самоNEXT_PUBLIC_. Променлива без префикса умишлено не се предоставя. - Всичко, изложено по този начин, е публично. То се намира във файл, който предоставяте на всеки. Никога не поставяйте секрет зад
NEXT_PUBLIC_, независимо какво подсказва името.
Това е и причината предварително създаден image да не може да бъде конфигуриран по този начин впоследствие. Ако image-ът е създаден другаде със стойностите, компилирани в него, задаването на променливи за service-а няма да промени нищо — string-овете вече са в 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
Символите, които причиняват това, са: # (коментар), $ (разширяване), интервали (разделяне на аргументи), ! (разширяване на history в интерактивен bash) и нови редове — които се появяват в един често срещан случай: private keys.
Стойностите на няколко реда създават най-много проблеми. PEM key, поставен в поле за един ред, пристига с премахнати нови редове и води до parse error, който не споменава нищо за новите редове. Кодирайте го с Base64 и го декодирайте в приложението:
dockup env set "PRIVATE_KEY_B64=$(base64 -i key.pem)" my-project/my-api
4. Не сте рестартирали
Променливите на средата се прочитат от процеса при стартирането му. Промяната им засяга следващия процес, а не този, който работи в момента.
Повечето платформи решават това чрез автоматично redeploy-ване при промяна на конфигурацията, но не всички го правят, а частична промяна — задавате три променливи, redeploy-вате, задавате четвърта — оставя една от тях извън процеса.
dockup env list my-project/my-api --json # what is configured
dockup restart my-project/my-api # make the process re-read it
Проверката, която дава окончателен отговор: прочетете променливата от работещия контейнер, а не от dashboard-а.
dockup exec "printenv | sort" my-project/my-api
Ако променливата присъства в този output, а приложението ви все още показва undefined, проблемът е в кода. Ако не присъства, проблемът е в конфигурацията. Тази една команда разделя пространството за търсене на две.
5. Грешен scope
Променливите обикновено са ограничени до определен scope — service, environment или project. Променлива, зададена за production, не се вижда в preview environment. Също така променлива, зададена за друг service в същия project, няма да бъде видима.
Това е обичайната причина нещо да работи на едно място, но не и на друго при идентичен код.
Ред на диагностика
# 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 променлива, маркирана като secret, се маскира в списъците и API отговорите — dockup env list показва ********, а не стойността. Това е по-важно, отколкото изглежда, защото най-честият начин, по който изтича credential, не е атака, а screenshot, support ticket или ред в log.
Дръжте ги извън build аргументите и frontend bundle-ите. И двете могат да бъдат прочетени от всеки, който получи artefact-а. Практическото правило е: ако се окаже във файл, който разпространявате, вече не е секрет.
Често задавани въпроси
Защо променливата на средата е undefined по време на build? Защото build и runtime са отделни среди. Runtime променливите не съществуват, докато image-ът се създава. Използвайте build argument, ако наистина ви е необходима стойност по време на build — но никога секрет.
Защо frontend-ът ми не вижда променливата?
Bundler-ите заменят стойността по време на build и предоставят само имена с префикс — VITE_, NEXT_PUBLIC_. Промяната на променливата изисква нов build, а всичко, изложено по този начин, може да бъде прочетено публично.
Трябва ли да рестартирам след промяна на променлива?
Да. Работещият процес вече е прочел своята environment. Повечето платформи автоматично правят redeploy при промяна; проверете с printenv от контейнера, вместо да разчитате на dashboard-а.
Как да подам стойност на няколко реда, например private key? Кодирайте я с Base64, задайте кодираната стойност и я декодирайте в приложението. Полетата за променливи на един ред премахват новите редове и водят до parse errors, които не споменават нови редове.
