Journal-indexDockup / praktijknotitie
Note / env-vars-not-reaching-container

Omgevingsvariabelen bereiken de container niet

Omgevingsvariabelen die niet werken in een container hebben meestal een van vijf oorzaken: buildtijd versus runtime, frontend-bundeling, aanhalingstekens, het moment van herstarten of de verkeerde scope. Controleer ze in deze volgorde.

Je stelt de variabele in. Het dashboard toont hem. De applicatie zegt dat hij undefined is. Omgevingsvariabelen die niet werken in een container is een van de meest voorkomende configuratieproblemen bij het hosten van applicaties, en vrijwel altijd is een van vijf specifieke oorzaken de boosdoener.

Hieronder staan ze in de volgorde waarin je het probleem het snelst vindt.

1. Buildtijd en runtime zijn twee verschillende werelden

Dit veroorzaakt meer van dit soort problemen dan de andere vier samen, en het is de oorzaak die mensen het minst intuïtief vinden.

Variabelen die je op je service instelt, bestaan wanneer de container draait. Alles wat je Dockerfile doet, gebeurt eerder, in een aparte omgeving. Een RUN-stap kan een runtimevariabele niet zien, omdat er op dat moment nog geen runtime is.

# 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"]

Als je tijdens de build echt een waarde nodig hebt, moet je die doorgeven als buildargument — een ander mechanisme met andere beveiligingseigenschappen:

ARG BUILD_VERSION
RUN echo "Building $BUILD_VERSION"

Geef op deze manier nooit een secret door. Buildargumenten worden vastgelegd in de layergeschiedenis van de image. Iedereen die de image kan pullen, kan ze lezen.

2. Frontendvariabelen worden ingebakken, niet uitgelezen

Als je frontend in productie undefined weergeeft, is dit vrijwel zeker de reden.

Een browser heeft geen environment. Wanneer je import.meta.env.VITE_API_URL of process.env.NEXT_PUBLIC_API_URL schrijft, vervangt de bundler dit door de letterlijke waarde tijdens de build. In de browser wordt er niets opgezocht — de waarde is al meegecompileerd.

Drie gevolgen die vaak voor verrassingen zorgen:

  • Als je de variabele wijzigt, gebeurt er niets totdat je opnieuw bouwt. De oude waarde staat in het JavaScript-bestand.
  • De prefix is verplicht. Vite stelt alleen VITE_ beschikbaar en Next.js alleen NEXT_PUBLIC_. Een variabele zonder de prefix wordt bewust niet beschikbaar gemaakt.
  • Alles wat je op deze manier beschikbaar maakt, is openbaar. Het staat in een bestand dat je aan iedereen serveert. Zet nooit een secret achter NEXT_PUBLIC_, ongeacht wat de naam suggereert.

Dit is ook de reden waarom je een vooraf gebouwde image niet achteraf op deze manier kunt configureren. Als de image elders is gebouwd en de waarden daarin zijn meegecompileerd, verandert het instellen van variabelen op de service niets — de strings staan al in de bundle.

3. Aanhalingstekens

Waarden met speciale tekens worden op manieren aangepast die verwarrende fouten veroorzaken in plaats van duidelijke foutmeldingen.

# 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

Tekens die dit veroorzaken zijn: # (commentaar), $ (expansie), spaties (splitsing van argumenten), ! (history expansion in interactieve bash) en regeleinden — die precies in één veelvoorkomend geval voorkomen: private keys.

Meerdere regels in een waarde veroorzaken de meeste problemen. Als je een PEM-sleutel in een veld voor één regel plakt, worden de regeleinden verwijderd. Het resultaat is een parsefout waarin niets over regeleinden staat. Codeer de waarde met Base64 en decodeer hem in de applicatie:

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

4. Je hebt niet opnieuw gestart

Omgevingsvariabelen worden door een proces ingelezen wanneer het start. Als je ze wijzigt, heeft dat gevolgen voor het volgende proces, niet voor het proces dat nu draait.

De meeste platformen lossen dit op door automatisch opnieuw te deployen wanneer de configuratie wijzigt, maar dat geldt niet voor alle platformen. Bovendien kan een gedeeltelijke wijziging — drie variabelen instellen, opnieuw deployen en daarna een vierde instellen — ervoor zorgen dat er één achterblijft.

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

De controle die uitsluitsel geeft: lees de variabele uit binnen de draaiende container, niet vanuit het dashboard.

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

Staat de variabele in die uitvoer terwijl je app nog steeds undefined meldt, dan zit het probleem in je code. Staat hij niet in de uitvoer, dan zit het probleem in de configuratie. Met dat ene commando halveer je het aantal mogelijke oorzaken.

5. Verkeerde scope

Variabelen hebben meestal een scope — een service, environment of project. Een variabele die je in productie instelt, is niet zichtbaar in een preview-environment. Een variabele die je op een andere service binnen hetzelfde project instelt, is daar evenmin zichtbaar.

Dit is meestal de oorzaak wanneer iets op de ene plek wel werkt en op de andere niet, terwijl de code identiek is.

De diagnostische volgorde

# 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

Begin altijd met stap 1. Daarmee verander je een vaag probleem in een van twee eenduidige problemen.

Specifiek over secrets

Ongeacht het platform zijn twee gewoonten de moeite waard.

Markeer secrets als secrets. Op Dockup wordt een als secret gemarkeerde variabele afgeschermd in lijsten en API-responses — dockup env list toont ******** in plaats van de waarde. Dat is belangrijker dan het misschien klinkt, want de meest voorkomende manier waarop een inloggegeven uitlekt, is geen aanval; het is een screenshot, een supportticket of een regel in een logbestand.

Houd ze buiten buildargumenten en frontend-bundles. Beide zijn leesbaar voor iedereen die het artefact in handen krijgt. De vuistregel: als iets terechtkomt in een bestand dat je verspreidt, is het geen secret meer.

Veelgestelde vragen

Waarom is mijn omgevingsvariabele tijdens de build undefined? Omdat build en runtime aparte omgevingen zijn. Runtimevariabelen bestaan niet terwijl de image wordt gebouwd. Gebruik een buildargument als je tijdens de build echt een waarde nodig hebt — en nooit een secret.

Waarom ziet mijn frontend de variabele niet? Bundlers vervangen de waarde tijdens de build en stellen alleen namen met de juiste prefix beschikbaar — VITE_, NEXT_PUBLIC_. Als je de variabele wijzigt, moet je opnieuw bouwen. Alles wat je op deze manier beschikbaar maakt, is openbaar leesbaar.

Moet ik opnieuw starten nadat ik een variabele heb gewijzigd? Ja. Een draaiend proces heeft zijn environment al ingelezen. De meeste platformen deployen automatisch opnieuw wanneer er iets wijzigt; controleer dit met printenv in de container in plaats van het dashboard te vertrouwen.

Hoe geef ik een waarde met meerdere regels door, zoals een private key? Codeer de waarde met Base64, stel de gecodeerde string in en decodeer hem in de applicatie. Velden voor omgevingsvariabelen op één regel verwijderen regeleinden en veroorzaken parsefouten waarin niet wordt vermeld dat er regeleinden ontbreken.