Zmienne środowiskowe nie docierają do kontenera
Zmienne środowiskowe, które nie działają w kontenerze, zwykle zawodzą z jednego z pięciu powodów: różnicy między build time a runtime, bundlowania frontendu, cudzysłowów, momentu restartu lub niewłaściwego zakresu. Sprawdź je w tej kolejności.
Ustawiasz zmienną. Widzisz ją w panelu. Aplikacja informuje, że ma wartość undefined. Zmienne środowiskowe, które nie działają w kontenerze, to jeden z najczęstszych problemów z konfiguracją hostowanych aplikacji i niemal zawsze wynika z jednej z pięciu konkretnych przyczyn.
Poniżej wymieniono je w kolejności, która pozwala najszybciej znaleźć problem.
1. Build time i runtime to dwa różne światy
Ta przyczyna odpowiada za więcej takich problemów niż pozostałe cztery razem wzięte, a jednocześnie jest najmniej intuicyjna.
Zmienne ustawione dla usługi istnieją, gdy kontener działa. Wszystko, co wykonuje Dockerfile, dzieje się wcześniej, w osobnym środowisku. Krok RUN nie może odczytać zmiennej runtime, ponieważ w tym momencie nie ma jeszcze 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"]
Jeśli rzeczywiście potrzebujesz wartości podczas budowania, musisz przekazać ją jako argument build — to inny mechanizm, o odmiennych właściwościach związanych z bezpieczeństwem:
ARG BUILD_VERSION
RUN echo "Building $BUILD_VERSION"
Nigdy nie przekazuj w ten sposób sekretu. Argumenty build są zapisywane w historii warstw obrazu. Każdy, kto może pobrać obraz, może je odczytać.
2. Zmienne frontendu są wbudowywane, a nie odczytywane
Jeśli frontend wyświetla undefined na produkcji, to niemal na pewno jest przyczyną.
Przeglądarka nie ma środowiska. Gdy używasz import.meta.env.VITE_API_URL lub process.env.NEXT_PUBLIC_API_URL, bundler podstawia dosłowny ciąg znaków podczas budowania. W przeglądarce nie odbywa się żadne wyszukiwanie — wartość została wkompilowana.
Wiążą się z tym trzy konsekwencje, które często zaskakują:
- Zmiana zmiennej nic nie da, dopóki nie wykonasz ponownego builda. Stara wartość znajduje się już w pliku JavaScript.
- Prefiks jest obowiązkowy. Vite udostępnia tylko zmienne
VITE_, a Next.js tylkoNEXT_PUBLIC_. Zmienna bez prefiksu jest celowo ukrywana. - Wszystko udostępnione w ten sposób jest publiczne. Znajduje się w pliku serwowanym każdemu użytkownikowi. Nigdy nie umieszczaj sekretu za
NEXT_PUBLIC_, niezależnie od tego, co sugeruje nazwa.
Z tego samego powodu gotowego obrazu nie można skonfigurować w ten sposób po fakcie. Jeśli obraz został zbudowany gdzie indziej, a wartości wkompilowano podczas budowania, ustawienie zmiennych dla usługi niczego nie zmieni — ciągi znaków już znajdują się w bundlu.
3. Cudzysłowy
Wartości zawierające znaki specjalne mogą zostać zniekształcone w sposób powodujący niejasne błędy zamiast oczywistych komunikatów.
# 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
Problem powodują między innymi: # (komentarz), $ (rozwijanie), spacje (dzielenie argumentów), ! (rozwijanie historii w interaktywnym bashu) oraz znaki nowego wiersza — które pojawiają się dokładnie w jednym częstym przypadku: kluczach prywatnych.
Największy problem stanowią wartości wielowierszowe. Klucz PEM wklejony do jednoliniowego pola traci znaki nowego wiersza i powoduje błąd parsowania, który nie wspomina o nowych wierszach. Zakoduj go w Base64 i dekoduj w aplikacji:
dockup env set "PRIVATE_KEY_B64=$(base64 -i key.pem)" my-project/my-api
4. Nie wykonano restartu
Proces odczytuje zmienne środowiskowe podczas uruchamiania. Ich zmiana wpływa na następny proces, a nie na ten, który obecnie działa.
Większość platform obsługuje to, automatycznie wykonując ponowny deployment po zmianie konfiguracji, ale nie wszystkie. Częściowa zmiana — ustawienie trzech zmiennych, ponowny deployment, a następnie ustawienie czwartej — może pozostawić jedną z nich niezmienioną.
dockup env list my-project/my-api --json # what is configured
dockup restart my-project/my-api # make the process re-read it
Rozstrzygające sprawdzenie polega na odczytaniu zmiennej wewnątrz działającego kontenera, a nie z panelu.
dockup exec "printenv | sort" my-project/my-api
Jeśli zmienna znajduje się na tej liście, a aplikacja nadal zgłasza undefined, problem leży w kodzie. Jeśli jej tam nie ma, problem dotyczy konfiguracji. Jedno polecenie dzieli obszar poszukiwań na pół.
5. Niewłaściwy zakres
Zmienne zwykle mają określony zakres — usługę, środowisko lub projekt. Zmienna ustawiona na produkcji nie jest widoczna w środowisku preview. Zmienna ustawiona dla innej usługi w tym samym projekcie również nie będzie widoczna.
To typowa przyczyna sytuacji, w której coś działa w jednym miejscu, a w innym nie, mimo identycznego kodu.
Kolejność diagnozowania
# 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
Za każdym razem zacznij od kroku 1. Zmienia on niejednoznaczny problem w jeden z dwóch jednoznacznych przypadków.
Sekrety
Niezależnie od platformy warto stosować dwie zasady.
Oznaczaj sekrety jako sekrety. W Dockup zmienna oznaczona jako sekret jest maskowana na listach i w odpowiedziach API — dockup env list wyświetla ******** zamiast wartości. To ważniejsze, niż mogłoby się wydawać, ponieważ najczęściej dane uwierzytelniające wyciekają nie w wyniku ataku, lecz przez zrzut ekranu, zgłoszenie do pomocy technicznej lub wpis w logach.
Nie umieszczaj ich w argumentach build ani w bundlach frontendu. Oba miejsca są czytelne dla każdego, kto uzyska artefakt. Praktyczna zasada: jeśli sekret trafia do pliku, który dystrybuujesz, nie jest już sekretem.
Najczęściej zadawane pytania
Dlaczego moja zmienna środowiskowa jest niezdefiniowana podczas budowania? Ponieważ build i runtime to osobne środowiska. Zmienne runtime nie istnieją podczas budowania obrazu. Jeśli naprawdę potrzebujesz wartości podczas budowania, użyj argumentu build — ale nigdy nie przekazuj w ten sposób sekretu.
Dlaczego mój frontend nie widzi zmiennej?
Bundlery podstawiają wartość podczas budowania i udostępniają tylko nazwy z prefiksem — VITE_, NEXT_PUBLIC_. Zmiana zmiennej wymaga ponownego builda, a wszystko udostępnione w ten sposób jest publicznie dostępne.
Czy po zmianie zmiennej muszę wykonać restart?
Tak. Działający proces odczytał już swoje środowisko. Większość platform automatycznie wykonuje ponowny deployment po zmianie; zweryfikuj to za pomocą printenv wewnątrz kontenera, zamiast ufać panelowi.
Jak przekazać wartość wielowierszową, taką jak klucz prywatny? Zakoduj ją w Base64, ustaw zakodowany ciąg, a następnie dekoduj go w aplikacji. Jednoliniowe pola zmiennych środowiskowych usuwają znaki nowego wiersza i powodują błędy parsowania, które o nich nie wspominają.
