Erklärte Umgebungsvariablen: .env-Dateien, Geheimnisse und Konfiguration richtig gemacht

  1. Startseite
  2. Blog
  3. DevOps

DevOps

Erklärte Umgebungsvariablen: .env-Dateien, Geheimnisse und Konfiguration richtig gemacht

Umgebungsvariablen ermöglichen die Ausführung desselben Codes auf einem Laptop, in CI und in der Produktion mit unterschiedlichen Einstellungen. Hier sehen Sie, wie sie sich wirklich verhalten, mit den Überraschungen, die wir beim Testen erlebten.

8 Min. Lesezeit
Umgebungsvariablen erklärt: .env-Dateien, Geheimnisse und Konfiguration richtig gemacht

Das Wichtigste

  • Umgebungsvariablen sind Schlüsselwerteinstellungen, die ein Prozess von seiner Umgebung erhält.
  • Sie halten Konfigurationen wie Ports und Datenbank-URLs aus Ihrem Code fern.
  • Werte sind immer Zeichenfolgen, daher ist „false“ wahr, es sei denn, Sie analysieren es.
  • Übertragen Sie niemals .env Dateien mit echten Geheimnissen und überprüfen Sie die erforderlichen Einstellungen, wenn die App gestartet wird.

Ihre App benötigt eine Datenbank-URL. Auf Ihrem Laptop ist es eine lokale Datenbank, in CI eine Wegwerfdatenbank und in der Produktion ein verwalteter Cluster. Die harte Codierung einer davon bedeutet, dass der Code für jede Umgebung geändert werden muss. Umgebungsvariablen lösen dieses Problem. Der Code lautet DATABASE_URL und jede Umgebung liefert ihren eigenen Wert.

Diese Idee ist von zentraler Bedeutung für die Zwölf-Faktor-App-Methode, die besagt, dass Konfigurationen, die zwischen Bereitstellungen variieren, in die Umgebung gehören. Wir haben das folgende Verhalten mit Python 3.13 unter Windows getestet. Alle angezeigten Ausgaben stammen aus diesem Lauf.

Was sind Umgebungsvariablen?

Jeder laufende Prozess verfügt über eine Reihe benannter Textwerte, die als Umgebung bezeichnet werden. Bekannte Beispiele sind PATH, das der Shell mitteilt, wo Programme zu finden sind, und HOME oder USERPROFILE. Ihre eigene App kann denselben Mechanismus für Einstellungen wie APP_PORT, LOG_LEVEL oder DATABASE_URL verwenden.

Umgebungsvariablen festlegen

Muschel Für diese Sitzung Für einen Befehl
Bash / zsh exportiere APP_PORT=8080 APP_PORT=8080 Python app.py
PowerShell $env:APP_PORT = "8080" Stellen Sie es ein, führen Sie es aus und entfernen Sie es dann
Windows-Befehl setze APP_PORT=8080 Nicht eingebaut

Diese gelten nur für die aktuelle Terminalsitzung. Um sie dauerhaft zu machen, fügen Sie sie einem Shell-Profil wie ~/.bashrc hinzu oder verwenden Sie unter Windows die Systemumgebungseinstellungen oder setx. Bei Anwendungen ist es in der Regel besser, sie zum Zeitpunkt der Bereitstellung bereitzustellen, als sie systemweit festzulegen.

Lesen Sie sie im Code

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'

Verwenden Sie .get() mit einer Standardeinstellung für optionale Einstellungen. Verwenden Sie eckige Klammern, die KeyError erhöhen, für Einstellungen, ohne die die App nicht funktionieren kann. Andere Sprachen sind ähnlich: process.env.APP_PORT in Node.js, System.getenv("APP_PORT") in Java und os.Getenv("APP_PORT") in Go.

Alles ist eine Zeichenfolge

Das fängt Menschen in jeder Sprache ein. Wir haben einen Port festgelegt und dann „1“ hinzugefügt:

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

Der Wert kam als Text zurück und das Hinzufügen ergab 80801, nicht 8081. Konvertieren Sie explizit, wie in int(os.environ["APP_PORT"]). Boolesche Werte sind schlimmer:

os.environ["DEBUG"] = "false"
print(bool(os.environ["DEBUG"]))   # True

Jede nicht leere Zeichenfolge ist in Python wahr, daher wurde "false" zu True. In der Produktion wäre der Debug-Modus eingeschaltet gewesen. Boolesche Werte bewusst analysieren:

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

Wie Prozesse sie erben

Ein untergeordneter Prozess erhält eine Kopie der Umgebung seines übergeordneten Prozesses. Wir haben untergeordnete Python-Prozesse gestartet, um Folgendes zu sehen:

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

Es folgen drei praktische Lektionen. Von Ihnen exportierte Variablen sind für jedes Programm, das Sie starten, sichtbar, einschließlich Skripten, die Sie nicht geschrieben haben, was für Geheimnisse wichtig ist. Die Übergabe einer benutzerdefinierten Umgebung ersetzt das Ganze, also schließen Sie alles ein, was das Kind braucht. Und ein Skript kann die Variablen seiner übergeordneten Shell nicht ändern, weshalb Tools Sie auffordern, eine Datei zu source, anstatt sie auszuführen.

.env-Dateien

Das manuelle Eingeben vieler Variablen wird mühsam, daher behalten die meisten Projekte lokale Entwicklungswerte in einer .env-Datei:

# local development only
APP_PORT=8080
DATABASE_URL="postgres://dev:devonly@localhost:5432/shop"
FEATURE_SEARCH=true

Bibliotheken wie python-dotenv und dotenv für Node.js laden diese Datei beim Start. Node.js 20.6 und höher können eines auch nativ mit --env-file laden. Um die Idee zu veranschaulichen, haben wir einen kleinen Parser geschrieben, der die obige Datei in Folgendes umwandelte:

{'APP_PORT': '8080',
 'DATABASE_URL': 'postgres://dev:devonly@localhost:5432/shop',
 'FEATURE_SEARCH': 'true'}

Nutzen Sie in der Praxis eine echte Bibliothek. Es verarbeitet Anführungszeichen, Escapezeichen, Kommentare und mehrzeilige Werte, die unsere 10-zeilige Version ignoriert. Standardmäßig überschreiben die meisten Dotenv-Loader Variablen, die bereits festgelegt sind, nicht, sodass echte Umgebungswerte die Datei überwiegen. Das ist normalerweise das, was Sie wollen.

Tipp: Fügen Sie vor Ihrem ersten Commit .env bis .gitignore hinzu und committen Sie stattdessen eine .env.example mit jedem Variablennamen und sicheren Platzhalterwerten. Neue Entwickler kopieren es und tragen ihre eigenen Werte ein.

Validieren Sie die Konfiguration beim Start

Eine fehlende Variable sollte die App sofort mit einer klaren Meldung stoppen und nicht eine Stunde später einen verwirrenden Fehler verursachen. Wir haben beim Laden überprüft, ob die erforderlichen Einstellungen erforderlich sind:

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

Typisierte Konfigurationsbibliotheken gehen noch weiter. Pydantic-Einstellungen in Python, zod oder envalid in Node.js und die Konfigurationseigenschaften von Spring in Java konvertieren alle Typen und melden jedes Problem auf einmal.

Umgebungsvariablen und Geheimnisse

Umgebungsvariablen sind die gängige Methode, Geheimnisse wie API-Schlüssel an eine App zu übergeben, und sie sind weitaus besser, als sie hart zu codieren. Sie haben Schwächen, über die Sie Bescheid wissen sollten:

  • Kindprozesse erben sie, wie unser Test gezeigt hat.
  • Sie können in Absturzberichten, Debug-Seiten und Protokollen erscheinen, die die Umgebung sichern.
  • Auf einigen Systemen können andere Prozesse, die vom selben Benutzer ausgeführt werden, sie lesen.

Für die Produktion bewahren viele Teams Geheimnisse in einem dedizierten Manager wie HashiCorp Vault, AWS Secrets Manager, Azure Key Vault oder Google Secret Manager auf und injizieren sie zur Laufzeit. Manche bevorzugen das Mounten von Geheimnissen als Dateien statt als Variablen. Drucken Sie auf keinen Fall die Konfiguration in Protokollen aus und rotieren Sie alle Geheimnisse, die jemals an Git übergeben wurden, auch nachdem Sie sie aus dem Verlauf entfernt haben.

In Docker und 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-Plattformen speichern Geheimnisse verschlüsselt und maskieren sie in Protokollen. Vermeiden Sie es, Geheimnisse mit ENV in der Docker-Datei in Docker-Images zu backen, da diese in den Image-Ebenen lesbar bleiben. Vollständige Setups finden Sie in unserem Docker Compose-Tutorial und in unserem GitHub Actions CI/CD-Leitfaden.

Best Practices

  • Verwenden Sie klare Namen in Großbuchstaben mit einem Präfix, z. B. SHOP_DB_URL.
  • Lesen Sie die gesamte Konfiguration in einem Modul, konvertieren Sie dort Typen und validieren Sie sie beim Start.
  • Stellen Sie sichere Standardeinstellungen nur für nicht sensible Einstellungen bereit.
  • Halten Sie .env aus Git fern und behalten Sie eine .env.example bei.
  • Setzen Sie in der Produktion für sensible Werte einen Secrets-Manager ein.
  • Dokumentieren Sie jede Variable, ihren Typ und einen Beispielwert.

Debuggen von Umgebungsvariablenproblemen

Wenn sich eine App so verhält, als ob eine Einstellung fehlt, führen Sie diese Prüfungen der Reihe nach durch:

  1. Ist sie an der richtigen Stelle eingestellt? Eine in einer Terminalregisterkarte exportierte Variable existiert in einer anderen oder in einem vom System gestarteten Dienst nicht.
  2. Wurde die App neu gestartet? Ein laufender Prozess behält die Umgebung bei, mit der er gestartet wurde. Eine spätere Änderung einer Variablen hat bis zum Neustart keine Auswirkung.
  3. Ist der Name genau? Bei Namen wird unter Linux und macOS die Groß-/Kleinschreibung beachtet, daher sind App_Port und APP_PORT unterschiedliche Variablen. Windows behandelt sie als gleich.
  4. Überschreibt eine .env-Datei diese oder wird sie ignoriert? Überprüfen Sie, ob Ihr Loader vorhandene Werte überschreibt und welche Datei er tatsächlich liest.
  5. Gibt es vereinzelte Anführungszeichen oder Leerzeichen? Ein mit einem nachgestellten Leerzeichen oder zusätzlichen Anführungszeichen kopierter Wert kann auf schwer erkennbare Weise nicht analysiert werden oder nicht übereinstimmen.

Um zu sehen, was ein Prozess wirklich empfangen hat, geben Sie die Variablennamen aus, niemals die Werte von Geheimnissen. Unter Linux und macOS listet printenv die aktuelle Shell-Umgebung auf, und in PowerShell Get-ChildItem Env: macht es dasselbe. Wenn Sie innerhalb eines Containers env bis docker exec ausführen, wird die Ansicht des Containers angezeigt, die sich oft von der Ihrer eigenen Shell unterscheidet.

Ein letzter Tipp: Protokollieren Sie beim Start eine Zeile, in der aufgeführt ist, welche optionalen Einstellungen ihre Standardeinstellungen verwenden. Es kostet nichts und antwortet am meisten: „Warum macht es das?“ Fragen sofort.

Fazit

Umgebungsvariablen sind einfach, aber sie bergen ein paar Fallstricke: Alles ist ein String, Kinder erben sie und durch sie können Geheimnisse durchsickern. Behandeln Sie sie als Eingabe für Ihre App. Lesen Sie sie an einem Ort, konvertieren und validieren Sie sie frühzeitig und halten Sie echte Geheimnisse aus Dateien fern, die festgeschrieben werden.

Häufige Fragen

Was ist eine Umgebungsvariable?

Es handelt sich um einen benannten Textwert, den ein Prozess von seiner Umgebung empfängt, beispielsweise APP_PORT=8080. Apps verwenden sie für Einstellungen, die sich zwischen Umgebungen ändern.

Was ist eine .env-Datei?

Eine Textdatei mit KEY=Wert-Zeilen, die beim Start von einer Bibliothek wie python-dotenv geladen wird. Es dient hauptsächlich der lokalen Entwicklung und sollte nicht mit echten Geheimnissen begangen werden.

Warum ist meine Umgebungsvariable eine Zeichenfolge?

Betriebssysteme speichern Umgebungswerte als Text. Konvertieren Sie Zahlen mit int() und analysieren Sie Boolesche Werte explizit, da die Zeichenfolge „false“ als wahr gilt.

Sind Umgebungsvariablen für Geheimnisse sicher?

Sie sind besser als Hartcodierung, aber untergeordnete Prozesse erben sie und Protokolle können sie offenlegen. Produktionssysteme verwenden häufig einen speziellen Secrets-Manager.

Kann ein Skript die Variablen seiner übergeordneten Shell ändern?

Nein. Ein untergeordneter Prozess erhält eine Kopie der Umgebung, sodass seine Änderungen nicht zurückfließen. Um Variablen in der aktuellen Shell festzulegen, verwenden Sie stattdessen die Datei als Quelle.

Lerne weiter

Arbeiten Sie mit Stack Quora

Benötigen Sie Hilfe beim Testen, WordPress oder SEO?

Wir helfen Teams, bessere Software zu liefern und ihre Websites zu erweitern. Sagen Sie uns, woran Sie gerade arbeiten, und wir schlagen Ihnen einen klaren nächsten Schritt vor.

Lesen Sie weiter

Weitere Anleitungen finden Sie unter Tests, KI und moderne Entwicklung

Hat Ihnen dieser Artikel gefallen? Wählen Sie ein Thema und springen Sie direkt in unsere neuesten Tutorials.

Künstliche Intelligenz

KI-Halluzinationen erklärt: Warum Sprachmodelle Dinge erfinden und wie man sie reduziert

KI-Halluzinationen erklärt: Was sie sind, warum Sprachmodelle sie produzieren, gängige Typen, wie man sie mit Erdung und Eingabeaufforderungen reduz…

Artikel lesen →
DevOps

Erklärte Umgebungsvariablen: .env-Dateien, Geheimnisse und Konfiguration richtig gemacht

Umgebungsvariablen anhand von Python-Beispielen erklärt, die wir ausgeführt haben: Sie lesen und festlegen, warum jeder Wert ein String ist, .env-Da…

Artikel lesen →
Softwareentwicklung

API-Ratenbegrenzung erklärt: Algorithmen, Header und Behandlung von 429-Fehlern

API-Ratenbegrenzung anhand einer von uns durchgeführten Simulation erklärt: Token-Bucket, festes Fenster, gleitendes Fenster, 429 Antworten, Ratenbe…

Artikel lesen →
Testautomatisierung

Spott in Python: unittest.mock, Patch und die Fehler, die Tests zum Lügen machen

Mocking in Python mit unittest.mock, getestet: Mock und return_value, side_effect, Patchen, wo ein Name verwendet wird, Spezifikation und Autospec, …

Artikel lesen →
Softwaretests

Code-Abdeckung erklärt: Leitungs- und Zweigstellenabdeckung und was 100 % wirklich bedeutet

Erklärung der Codeabdeckung anhand eines echten Coverage.py-Laufs: Zeilen-, Zweig- und Bedingungsabdeckung, Lesen eines Berichts, warum bei 100 % Ab…

Artikel lesen →
Datenbanken & SQL

DELETE vs. TRUNCATE vs. DROP in SQL: Unterschiede, Rollback und Verwendungszweck

DELETE vs. TRUNCATE vs. DROP erklärt mit getestetem SQL: was jeweils entfernt wird, Rollback, Trigger, Identitätsrücksetzung, Geschwindigkeit und wi…

Artikel lesen →
Datenbanken & SQL

Erklärte SQL-Ansichten: Erstellen, nutzen und vermeiden Sie Fallstricke mit Beispielen

SQL-Ansichten anhand der von uns ausgeführten Abfragen erklärt: Erstellen einer Ansicht, Live-Ergebnisse, Ausblenden von Spalten, Leistung, aktualis…

Artikel lesen →
Softwareentwicklung

TypeScript-Schnittstelle vs. Typ: Echte Unterschiede, getestet mit dem Compiler

TypeScript-Schnittstelle vs. Typ, überprüft mit dem echten Compiler: Was beide können, was nur einer kann, Deklarationszusammenführung, Erweiterunge…

Artikel lesen →
Softwareentwicklung

Python *args und **kwargs erklärt: Flexible Funktionen mit klaren Beispielen

Python *args und **kwargs erklärt mit Code, den wir ausgeführt haben: Packen und Entpacken, Parameterreihenfolge, Nur-Schlüsselwort- und Nur-Positio…

Artikel lesen →
Softwareentwicklung

Python-Lambda-Funktionen: Syntax, tatsächliche Verwendung und wann man sie nicht verwendet

Python-Lambda-Funktionen erklärt mit Code, den wir ausgeführt haben: Syntax, Sortierung mit Schlüssel, Karte und Filter, Abschlüsse, die Late-Bindin…

Artikel lesen →
Künstliche Intelligenz

Was sind KI-Agenten? Wie sie funktionieren, mit einer einfachen Agentenschleife, die Sie ausführen können

Was sind KI-Agenten? Eine einfache Anleitung zur Agentenschleife, zu Tools, Speicher und Leitplanken, mit einer kleinen Python-Schleife, die wir aus…

Artikel lesen →
DevOps

Überwachung vs. Beobachtbarkeit: Der wahre Unterschied, mit Protokollen, Metriken und Traces

Überwachung vs. Beobachtbarkeit erklärt: bekannte und unbekannte Fehler, Protokolle, Metriken und Spuren, eine exemplarische Vorgehensweise für Chec…

Artikel lesen →
DevOps

Docker-Volumes vs. Bind Mounts: Containerdaten auf die richtige Art und Weise beibehalten

Docker-Volumes vs. Bind-Mounts erklärt: Warum Containerdaten verschwinden, benannte Volumes, Bind-Mounts, tmpfs, Compose-Beispiele, Backups, Berecht…

Artikel lesen →
Testautomatisierung

TDD vs. BDD: Hauptunterschiede, eine Funktion, die auf beide Arten entwickelt wurde und wann sie jeweils verwendet werden sollte

TDD vs. BDD erklärt mit einer Rabattfunktion, die in beide Richtungen aufgebaut ist: Rot-Grün-Refaktor mit Code, den wir ausgeführt haben, Gegeben-W…

Artikel lesen →
Softwareentwicklung

Git merge vs rebase: Unterschiede, Beispiele und wann man was nutzt

Git merge vs rebase erklärt mit echter Log-Ausgabe, einer Vergleichstabelle, der goldenen Regel des Rebase und einem sicheren Team-Workflow.

Artikel lesen →
Softwareentwicklung

Python List Comprehension: Syntax, Beispiele und wann man darauf verzichtet

Python List Comprehension erklärt: Syntax, Filter, if-else, verschachtelte Schleifen, gemessene Zeiten und Fälle, in denen eine normale for-Schleife…

Artikel lesen →
Datenbanken & SQL

SQL Joins erklärt mit Beispielen (Inner, Left, Right, Full)

SQL Joins Schritt für Schritt mit Beispieltabellen und Abfragen verstehen: INNER, LEFT, RIGHT, FULL, CROSS und SELF, plus typische Fehler und Perfor…

Artikel lesen →