Omgevingsvariabelen uitgelegd: .env-bestanden, geheimen en configuratie goed gedaan

  1. Home
  2. Blog
  3. DevOps

DevOps

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.

8 min leestijd
Omgevingsvariabelen uitgelegd: .env-bestanden, geheimen en configuratie goed gedaan

Belangrijkste punten

  • 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 .env bestanden 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 .env buiten 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:

  1. 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.
  2. 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.
  3. Is de naam exact? Namen zijn hoofdlettergevoelig op Linux en macOS, dus App_Port en APP_PORT zijn verschillende variabelen. Windows behandelt ze als hetzelfde.
  4. Heeft een .env-bestand voorrang of wordt het genegeerd? Controleer of uw lader de bestaande waarden overschrijft en welk bestand daadwerkelijk wordt gelezen.
  5. 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

Werk met Stack Quora

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.

Blijf lezen

Meer handleidingen over testen, AI en moderne ontwikkeling

Genoten van dit artikel? Kies een onderwerp en ga direct naar onze nieuwste tutorials.

Kunstmatige intelligentie

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 →
DevOps

Omgevingsvariabelen 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 →
Softwareontwikkeling

API-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 →
Testautomatisering

Spotten 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 →
Softwaretesten

Codedekking 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 & SQL

DELETE 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 & SQL

SQL-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 →
Softwareontwikkeling

TypeScript-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 →
Softwareontwikkeling

Python *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 →
Softwareontwikkeling

Python 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 intelligentie

Wat 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 →
DevOps

Monitoring 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 →
DevOps

Docker-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 →
Testautomatisering

TDD 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 →
Softwareontwikkeling

Git 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 →
Softwareontwikkeling

Python 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 & SQL

SQL 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 →