Omgevingsvariabelen uitgelegd: .env-bestanden, geheimen en configuratie goed gedaan
Omgevingsvariabelen laten dezelfde code draaien op een laptop, in CI en in productie met verschillende instellingen. Dit is hoe ze zich echt gedragen, met de verrassingen die we tegenkwamen tijdens het testen ervan.
- Omgevingsvariabelen zijn sleutelwaarde-instellingen die een proces ontvangt van zijn omgeving.
- Ze houden configuratie, zoals poorten en database-URL’s, buiten uw code.
- Waarden zijn altijd strings, dus ‘false’ is waar, tenzij je het ontleedt.
- Leg nooit
.envbestanden met echte geheimen vast en valideer de vereiste instellingen wanneer de app start.
Uw app heeft een database-URL nodig. Op je laptop is het een lokale database, in CI een wegwerpdatabase en in productie een beheerd cluster. Het hard coderen van een van deze betekent dat de code voor elke omgeving moet worden gewijzigd. Omgevingsvariabelen lossen dit op. De code luidt DATABASE_URL en elke omgeving levert zijn eigen waarde.
Dit idee staat centraal in de Twelve-Factor App-methode, die zegt dat configuraties die variëren tussen implementaties, in de omgeving thuishoren. We hebben het onderstaande gedrag getest met Python 3.13 op Windows. Elke getoonde uitvoer is afkomstig van die run.
Wat zijn omgevingsvariabelen?
Elk lopend proces heeft een reeks benoemde tekstwaarden die de omgeving worden genoemd. Bekende voorbeelden zijn PATH, dat de shell vertelt waar programma’s te vinden zijn, en HOME of USERPROFILE. Uw eigen app kan hetzelfde mechanisme gebruiken voor instellingen als APP_PORT, LOG_LEVEL of DATABASE_URL.
Omgevingsvariabelen instellen
| Schelp | Voor deze sessie | Voor één opdracht |
|---|---|---|
| Bash / zsh | exporteer APP_PORT=8080 |
APP_PORT=8080 python-app.py |
| PowerShell | $env:APP_PORT = "8080" |
Stel het in, voer het uit en verwijder het |
| Windows-cmd | stel APP_PORT=8080 in |
Niet ingebouwd |
Deze duren alleen voor de huidige terminalsessie. Om ze permanent te maken, voegt u ze toe aan een shell-profiel zoals ~/.bashrc, of gebruikt u in Windows de systeemomgevingsinstellingen of setx. Voor applicaties is het meestal beter om ze tijdens de implementatie aan te bieden in plaats van ze voor het hele systeem in te stellen.
Ze in code lezen
import os
port = os.environ.get("APP_PORT", "8000") # default if missing
print(port) # 8000
db = os.environ["DATABASE_URL_MISSING_DEMO"]
# KeyError: 'DATABASE_URL_MISSING_DEMO'
Gebruik .get() met een standaardwaarde voor optionele instellingen. Gebruik vierkante haakjes, die omhoog staan KeyError, voor instellingen waar de app niet zonder kan. Andere talen zijn vergelijkbaar: process.env.APP_PORT in Node.js, System.getenv("APP_PORT") in Java en os.Getenv("APP_PORT") in Go.
Alles is een touwtje
Dit vangt mensen in elke taal op. We hebben een poort ingesteld en er vervolgens “1” aan toegevoegd:
os.environ["APP_PORT"] = "8080"
port = os.environ["APP_PORT"]
print(type(port).__name__, repr(port), port + "1")
# str '8080' 80801
os.environ["APP_PORT"] = 8080
# TypeError: str expected, not int
De waarde kwam terug als tekst en de toevoeging produceerde 80801, niet 8081. Expliciet converteren, zoals in int(os.environ["APP_PORT"]). Booleans zijn erger:
os.environ["DEBUG"] = "false"
print(bool(os.environ["DEBUG"])) # True
Elke niet-lege string is waar in Python, dus "false" werd True. Debug-modus zou in productie zijn ingeschakeld. Booleans opzettelijk analyseren:
def env_bool(name, default=False):
value = os.environ.get(name)
if value is None:
return default
return value.strip().lower() in {"1", "true", "yes", "on"}
env_bool("DEBUG") # False
env_bool("NOT_SET", default=True) # True
Hoe processen deze erven
Een onderliggend proces ontvangt een kopie van de bovenliggende omgeving. We zijn onderliggende Python-processen gestart om dit te zien:
child sees: 8080 inherited # default: full copy of the parent's environment
child sees: 9000 None # given a custom env: only what we passed
parent sees child var: None # a child's changes never flow back
Er volgen drie praktijklessen. Variabelen die u exporteert, zijn zichtbaar voor elk programma dat u start, inclusief scripts die u niet heeft geschreven, wat van belang is voor geheimen. Het doorgeven van een aangepaste omgeving vervangt het geheel, dus neem alles op wat het kind nodig heeft. En een script kan de variabelen van de bovenliggende shell niet wijzigen. Daarom vragen tools je om een bestand te source in plaats van het uit te voeren.
.env-bestanden
Het met de hand typen van veel variabelen wordt vervelend, daarom bewaren de meeste projecten lokale ontwikkelingswaarden in een .env bestand:
# local development only
APP_PORT=8080
DATABASE_URL="postgres://dev:devonly@localhost:5432/shop"
FEATURE_SEARCH=true
Bibliotheken zoals python-dotenv en dotenv voor Node.js laden dit bestand bij het opstarten. Node.js 20.6 en hoger kunnen er ook een native laden met --env-file. Om het idee te begrijpen, hebben we een kleine parser geschreven, die het bovenstaande bestand omzet in:
{'APP_PORT': '8080',
'DATABASE_URL': 'postgres://dev:devonly@localhost:5432/shop',
'FEATURE_SEARCH': 'true'}
Gebruik een echte bibliotheek in de praktijk. Het verwerkt citaten, ontsnappingen, commentaar en meerregelige waarden die onze 10-regelige versie negeert. Standaard overschrijven de meeste dotenv-laders variabelen die al zijn ingesteld niet, zodat echte omgevingswaarden de overhand krijgen op het bestand. Dat is meestal wat je wilt.
Tip: Voeg .env tot .gitignore toe vóór je eerste commit, en commit in plaats daarvan een .env.example met elke variabelenaam en veilige plaatsaanduidingswaarden. Nieuwe ontwikkelaars kopiëren het en vullen hun eigen waarden in.
Valideer de configuratie bij het opstarten
Een ontbrekende variabele zou de app onmiddellijk moeten stoppen met een duidelijke boodschap, en niet een uur later voor een verwarrende fout moeten zorgen. We hebben tijdens het laden gecontroleerd op de vereiste instellingen:
REQUIRED = ["DATABASE_URL", "SECRET_KEY"]
def check_required(env):
missing = [k for k in REQUIRED if not env.get(k)]
if missing:
raise SystemExit(f"Missing required settings: {', '.join(missing)}")
# Missing required settings: SECRET_KEY
Getypte configuratiebibliotheken gaan verder. Pydantic-instellingen in Python, zod of envalid in Node.js, en de configuratie-eigenschappen van Spring in Java converteren allemaal typen en rapporteren elk probleem in één keer.
Omgevingsvariabelen en geheimen
Omgevingsvariabelen zijn de gebruikelijke manier om geheimen zoals API-sleutels aan een app door te geven, en ze zijn veel beter dan ze hard te coderen. Ze hebben wel zwakke punten die ze moeten weten:
- Kindprocessen erven deze, zoals uit onze test bleek.
- Ze kunnen verschijnen in crashrapporten, foutopsporingspagina’s en logboeken die de omgeving dumpen.
- Op sommige systemen kunnen andere processen die door dezelfde gebruiker worden uitgevoerd, deze lezen.
Voor productie bewaren veel teams geheimen in een speciale manager, zoals HashiCorp Vault, AWS Secrets Manager, Azure Key Vault of Google Secret Manager, en injecteren ze deze tijdens runtime. Sommigen geven er de voorkeur aan om geheimen als bestanden te koppelen in plaats van als variabelen. Hoe dan ook, print nooit de configuratie in logs, en roteer geen enkel geheim dat ooit aan Git is vastgelegd, zelfs nadat je het uit de geschiedenis hebt verwijderd.
In Docker en CI
# Docker (not run for this article)
docker run -e APP_PORT=8080 --env-file .env.production myapp
# GitHub Actions (not run for this article)
env:
APP_PORT: 8080
API_KEY: ${{ secrets.API_KEY }}
CI-platforms slaan geheimen versleuteld op en maskeren deze in logboeken. Vermijd het bakken van geheimen in Docker-afbeeldingen met ENV in het Docker-bestand, omdat deze leesbaar blijven in de afbeeldingslagen. Zie onze Docker Compose-tutorial en GitHub Actions CI/CD-handleiding voor volledige instellingen.
Beste praktijken
- Gebruik duidelijke namen in hoofdletters met een voorvoegsel, zoals
SHOP_DB_URL. - Lees alle configuraties in één module, converteer daar typen en valideer bij het opstarten.
- Geef alleen veilige standaardwaarden op voor niet-gevoelige instellingen.
- Houd
.envbuiten Git en onderhoud een.env.example. - Gebruik een geheimenmanager in productie voor gevoelige waarden.
- Documenteer elke variabele, het type en een voorbeeldwaarde.
Debuggen van problemen met omgevingsvariabelen
Wanneer een app zich gedraagt alsof een instelling ontbreekt, voer dan de volgende controles uit:
- Is deze op de juiste plaats ingesteld? Een variabele die naar het ene terminaltabblad wordt geëxporteerd, bestaat niet op een ander terminaltabblad of in een service die door het systeem is gestart.
- Is de app opnieuw opgestart? Een actief proces behoudt de omgeving waarmee het is gestart. Het achteraf wijzigen van een variabele heeft tot de herstart geen effect.
- Is de naam exact? Namen zijn hoofdlettergevoelig op Linux en macOS, dus
App_PortenAPP_PORTzijn verschillende variabelen. Windows behandelt ze als hetzelfde. - Heeft een .env-bestand voorrang of wordt het genegeerd? Controleer of uw lader de bestaande waarden overschrijft en welk bestand daadwerkelijk wordt gelezen.
- Zijn er losse aanhalingstekens of spaties? Een waarde die wordt gekopieerd met een spatie of extra aanhalingstekens kan niet worden geparseerd of niet overeenkomen, op manieren die moeilijk te herkennen zijn.
Om te zien wat een proces werkelijk heeft opgeleverd, drukt u de namen van de variabelen af, en nooit de waarden van geheimen. Op Linux en macOS geeft printenv de huidige shell-omgeving weer, en in PowerShell Get-ChildItem Env: wordt hetzelfde gedaan. Binnenin een container laten env tot en met docker exec de weergave van de container zien, die vaak verschilt van die van uw eigen shell.
Nog een laatste tip: log bij het opstarten één regel in waarin wordt vermeld welke optionele instellingen hun standaardwaarden gebruiken. Het kost niets, en het antwoordt vooral: “Waarom doet het dat?” meteen vragen.
Tot slot
Omgevingsvariabelen zijn eenvoudig, maar ze verbergen een paar valkuilen: alles is een string, kinderen erven ze en er kunnen geheimen doorheen lekken. Beschouw ze als input voor uw app. Lees ze op één plek, converteer en valideer ze in een vroeg stadium, en houd echte geheimen buiten de bestanden die vastgelegd worden.
Veelgestelde vragen
Wat is een omgevingsvariabele?
Het is een benoemde tekstwaarde die een proces ontvangt van zijn omgeving, zoals APP_PORT=8080. Apps gebruiken ze voor instellingen die tussen omgevingen veranderen.
Wat is een .env-bestand?
Een tekstbestand met KEY=value-regels, geladen bij het opstarten door een bibliotheek zoals python-dotenv. Het wordt voornamelijk gebruikt voor lokale ontwikkeling en mag niet met echte geheimen worden gepleegd.
Waarom is mijn omgevingsvariabele een string?
Besturingssystemen slaan omgevingswaarden op als tekst. Converteer getallen met int() en ontleed booleans expliciet, omdat de string “false” als waar telt.
Zijn omgevingsvariabelen veilig voor geheimen?
Ze zijn beter dan hard-coding, maar onderliggende processen nemen ze over en logboeken kunnen ze blootleggen. Productiesystemen maken vaak gebruik van een speciale geheimenmanager.
Kan een script de variabelen van de bovenliggende shell wijzigen?
Nee. Een onderliggend proces krijgt een kopie van de omgeving, zodat de wijzigingen niet terugvloeien. Om variabelen in de huidige shell in te stellen, moet u in plaats daarvan het bestand sourcen.
Blijf leren
Hulp nodig met testen, WordPress of SEO?
We helpen teams betere software te leveren en hun sites te laten groeien. Vertel ons waar u mee bezig bent en wij stellen een duidelijke volgende stap voor.
Meer handleidingen over testen, AI en moderne ontwikkeling
Genoten van dit artikel? Kies een onderwerp en ga direct naar onze nieuwste tutorials.
AI-hallucinaties uitgelegd: waarom taalmodellen dingen verzinnen en hoe ze te verminderen
AI-hallucinaties uitgelegd: wat ze zijn, waarom taalmodellen ze produceren, veelvoorkomende typen, hoe je ze kunt verminderen met aarding en aanwijz…
Lees artikel →DevOpsOmgevingsvariabelen uitgelegd: .env-bestanden, geheimen en configuratie goed gedaan
Omgevingsvariabelen uitgelegd met Python-voorbeelden die we hebben uitgevoerd: ze lezen en instellen, waarom elke waarde een string is, .env-bestand…
Lees artikel →SoftwareontwikkelingAPI-snelheidsbeperking uitgelegd: algoritmen, headers en afhandeling van 429-fouten
API-snelheidsbeperking uitgelegd met een simulatie die we hebben uitgevoerd: token-bucket, vast venster, schuifvenster, 429 reacties, headers van sn…
Lees artikel →TestautomatiseringSpotten in Python: unittest.mock, patch en de fouten die tests doen liegen
Spotten in Python met unittest.mock, getest: Mock en return_value, side_effect, patchen waar een naam wordt gebruikt, spec en autospec, spotdatums e…
Lees artikel →SoftwaretestenCodedekking uitgelegd: lijn versus filiaaldekking en wat 100% werkelijk betekent
Codedekking uitgelegd met een echte dekking.py-run: dekking van lijnen, vertakkingen en condities, een rapport lezen, waarom 100% dekking nog steeds…
Lees artikel →Databases & SQLDELETE versus TRUNCATE versus DROP in SQL: verschillen, terugdraaien en wanneer elk te gebruiken
DELETE versus TRUNCATE versus DROP uitgelegd met geteste SQL: wat elk verwijdert, terugdraaien, triggers, identiteitsreset, snelheid en hoe PostgreS…
Lees artikel →Databases & SQLSQL-weergaven uitgelegd: creëer, gebruik en vermijd de valkuilen, met voorbeelden
SQL-weergaven uitgelegd met query's die we hebben uitgevoerd: een weergave maken, live resultaten, kolommen verbergen, prestaties, bijwerkbare en ge…
Lees artikel →SoftwareontwikkelingTypeScript-interface versus type: echte verschillen, getest met de compiler
TypeScript-interface versus type, gecontroleerd met de echte compiler: wat beide kunnen doen, wat slechts één kan doen, samenvoegen van declaraties,…
Lees artikel →SoftwareontwikkelingPython *args en **kwargs uitgelegd: flexibele functies met duidelijke voorbeelden
Python *args en **kwargs uitgelegd met code die we hebben uitgevoerd: in- en uitpakken, parametervolgorde, argumenten die alleen trefwoorden en posi…
Lees artikel →SoftwareontwikkelingPython Lambda-functies: syntaxis, echt gebruik en wanneer u ze niet moet gebruiken
Python lambda-functies uitgelegd met code die we hebben uitgevoerd: syntaxis, sorteren met sleutel, kaart en filter, sluitingen, de laat-bindende va…
Lees artikel →Kunstmatige intelligentieWat zijn AI-agenten? Hoe ze werken, met een eenvoudige agentloop die u kunt uitvoeren
Wat zijn AI-agenten? Een duidelijke gids voor de agentenlus, tools, geheugen en vangrails, met een kleine Python-lus die we hebben uitgevoerd, echte…
Lees artikel →DevOpsMonitoring versus waarneembaarheid: het echte verschil, met logboeken, statistieken en sporen
Monitoring versus waarneembaarheid uitgelegd: bekende en onbekende fouten, logboeken, statistieken en sporen, een walkthrough voor afrekenincidenten…
Lees artikel →DevOpsDocker-volumes versus bindingsmounts: bewaar containergegevens op de juiste manier
Docker-volumes versus bind-mounts uitgelegd: waarom containergegevens verdwijnen, benoemde volumes, bind-mounts, tmpfs, Compose-voorbeelden, back-up…
Lees artikel →TestautomatiseringTDD versus BDD: belangrijkste verschillen, één functie die op beide manieren is gebouwd en wanneer deze moet worden gebruikt
TDD versus BDD uitgelegd met één kortingsfunctie die in beide richtingen is gebouwd: rood-groen-refactor met code die we hebben uitgevoerd, gegeven-…
Lees artikel →SoftwareontwikkelingGit merge vs rebase: verschillen, voorbeelden en wanneer je wat gebruikt
Git merge vs rebase uitgelegd met echte logoutput, een vergelijkingstabel, de gouden regel van rebase en een veilige teamwerkwijze.
Lees artikel →SoftwareontwikkelingPython list comprehension: syntaxis, voorbeelden en wanneer je het vermijdt
Python list comprehension uitgelegd: syntaxis, filteren, if-else, geneste lussen, gemeten tijden en wanneer een gewone for-lus beter is.
Lees artikel →Databases & SQLSQL JOIN uitgelegd met voorbeelden (Inner, Left, Right, Full)
Begrijp SQL joins stap voor stap met voorbeeldtabellen en queries: INNER, LEFT, RIGHT, FULL, CROSS en SELF, plus veelgemaakte fouten en prestatietips.
Lees artikel →