Indeks dziennikaDockup / notatka terenowa
Note / env-vars-not-reaching-container

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 tylko NEXT_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ą.