Startseite / Artikel

Warum ignorieren Vite‑React‑Anwendungen die Kubernetes-Umgebungsvariablen zur Laufzeit?

Füge VITE_ Variablen während des Builds direkt ein, sodass Änderungen an der Umgebung eines Kubernetes-Pods niemals eine React-Anwendung erreichen. Setze stattdessen window.env aus dem Einstiegspunkt des Containers ein.

Verlag: REACTAPP.TOP5 Min. Lesezeit1057 Wörter

Quellen: Why Your Vite + React App Ignores Kubernetes Env Vars (And How I Fixed It)

Frontend-Teams, die bereits mit Node-Servern vertraut sind, gehen oft davon aus, dass Umgebungsvariablen in Pods genauso funktionieren wie bei einem React-Bundle in Kubernetes. Ein mit Vite erstelltes SPA widerlegt diese Annahme auf unauffällige, aber kostspielige Weise: Die Werte, die Sie in der Deployment-Konfiguration festlegen, erreichen den Browser niemals, weil sie zu keinem Zeitpunkt während der Anfrage abgerufen werden.

Die Einrichtung

Stellen Sie sich eine mit Vite aufgebauten React-Anwendung vor. Die Konfiguration verwendet das übliche VITE_-Präfix – wie VITE_API_URL, VITE_APP_NAME sowie verschiedene Einstellungsoptionen.

Der Release-Prozess läuft über Jenkins. Die Pipeline erstellt ein Docker-Bild und liefert während dieses Erstellungsprozesses Umgebungsvariablen, sodass diese im Bild vorhanden erscheinen. Dasselbe Image wird später in einem Kubernetes-Pod gestartet, wobei die Deployment-Konfiguration ebenfalls Umgebungsvariablen deklariert – in der Annahme, dass der laufende Prozess sie genauso liest wie Express.

Das passiert beim Vite-Client niemals. Selbst nach dem Aktualisieren einer Schlüsselwerte in der Deployment-Datei, dem Neustart des Pods und dem Erneuern des Browsers wird weiterhin der vorherige String angezeigt. Die Überprüfung des Pods mit kubectl exec … -- env zeigt, dass die Variable im Containerumfeld vorhanden ist. Der statische JavaScript-Code bezieht sich einfach nicht auf sie.

Warum das passiert (die einfache Erklärung)

Für Personen mit Erfahrung in Node oder Express fällt die fehlende Erklärung leicht übersehen: Vite löst Umgebungsvariablen während der Kompilierung des Projekts auf, nicht, wenn der Benutzer die Seite lädt.

Wo auch immer der Quellcode auf import.meta.env.VITE_API_URL verweist, ersetzt der Build diesen Ausdruck durch einen literalen String. Nach Abschluss von npm run build findet keine Laufzeitabfrage mehr statt – es bleibt nur text, der im Bundle eingebettet ist, was eher einer einmaligen Such- und Ersetzungsaktion gleicht als der Verwendung von process.env auf einem Server.

Daher sieht der Pipeline-Ablauf wie folgt aus:

  1. Jenkins erstellt das Image und Vite embeddet alle VITE_*-Werte, die in diesem Build-Schritt vorhanden waren.
  2. Das Image wird hochgeladen und in den Schedule aufgenommen.
  3. Der Pod startet mit neueren oder umgebungsspezifischen Variablen.
  4. Diese Pod-Werte kommen jedoch zu spät an. Der Browser lädt statische Ressourcen herunter (oft von nginx), deren JavaScript bereits die Strings vom Vortag enthält.

Docker und Kubernetes betrachten Umgebungsvariablen als eine Laufzeit-Sache, sodass dasselbe Image sowohl für die Staging- als auch für die Produktumgebung genutzt werden kann. Vite hingegen betrachtet import.meta.env als eine Bauzeit-Sache. Der Einsatz von Laufzeitmechanismen gegen einen Bauzeitkontrakt führt zu stummen Fehlern: kein Stacktrace, keine Warnung, nur veraltete Konfigurationen.

Dieser Widerspruch in den Denkmodellen ist der Kern des Problems. Die Plattform „ignoriert“ Kubernetes nicht; der Client hatte einfach nie einen Weg, die Umgebung des Pods zu überwachen.

Was ist also die eigentliche Lösung?

Sobald das Timing-Problem klar ist, besteht die Abhilfe darin, umgebungsbezogene Zeichenketten nicht mehr direkt in das Image einzubauen. Man erstellt eine saubere Build-Version und injiziert die Werte anschließend beim Start des Containers – bevor der Browser das Anwendungs-Paket ausführt.

Ein praktisches Muster sieht so aus:

1. Senden Sie einen kleinen Eingangspunkt, der beim Start des Containers ausgeführt wird, alle mit VITE_ vorangestellten Variablen aus der Prozessumgebung sammelt und eine einfache env-config.js-Datei erstellt, die window.__env__ zuweist:

#!/bin/sh
echo "window.__env__ = {" > /usr/share/nginx/html/env-config.js
env | grep '^VITE_' | while IFS='=' read -r key value; do
  echo "  \"$key\": \"$value\"," >> /usr/share/nginx/html/env-config.js
done
echo "};" >> /usr/share/nginx/html/env-config.js
exec nginx -g "daemon off;"

Die Filterung nach dem VITE_-Präfix bildet die Sicherheitsgrenze. Namen wie DB_PASSWORD oder interne Cluster-Geheimnisse bleiben außerhalb der Datei und erreichen niemals den Browser.

2. Laden Sie dieses Skript aus index.html vor dem Anwendungs-Bundle, damit window.__env__ bereits vor dem Ausführen des React-Codes vorhanden ist.

3. Verwenden Sie in der App-Konfiguration vorzugsweise window.__env__ und greifen Sie bei lokaler Entwicklung auf import.meta.env zurück:

const getEnv = (key) => window.__env__?.[key] || import.meta.env[key];
export const config = {
  apiUrl: getEnv("VITE_API_URL"),
};

4. Stellen Sie sicher, dass die Build-Phase des Dockerfiles frei von sensiblen oder umgebungsbezogenen VITE_*-Eingaben ist. Sie erzeugt lediglich statische Ressourcen; die Einbettung erfolgt später im Entrypoint:

FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build        # no sensitive vars baked in here anymore
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 80
ENTRYPOINT ["/entrypoint.sh"]   # runs at pod startup, injects env, THEN starts nginx

Der Builder benötigt nicht länger VITE_API_URL oder ähnliche Werte. Unterschiede in der Umgebung treten erst auf, nachdem das Image bereits läuft.

5. Deklarieren Sie die Umgebungsvariablen auf der Kubernetes Deployment auf die übliche Weise. Auf der Cluster-Seite sind keine Vite-spezifischen Einstellungen erforderlich:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: app
          image: my-app:latest
          ports:
            - containerPort: 80
          env:
            # only VITE_ vars actually reach the browser
            - name: VITE_API_URL
              value: "https://api.my-cluster.com"
            - name: VITE_APP_NAME
              value: "MyApp"
            # non-VITE_ vars stay inside the pod, never exposed to the frontend
            - name: NODE_ENV
              value: "production"
          # for real secrets, pull from a Kubernetes secret instead of a plain value:
          # - name: VITE_API_KEY
          #   valueFrom:
          #     secretKeyRef:
          #       name: my-secret
          #       key: api-key
---
apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  selector:
    app: my-app
  type: NodePort
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080

Der env:-Block ist reines Kubernetes. Der Entrypoint liest diese Werte beim Start aus und stellt den VITE_-Teil dem Browser zur Verfügung.

Nach dieser Änderung startet das Aktualisieren der Deployment-Umgebung (zum Beispiel durch Ausrichten von VITE_API_URL auf eine neue HTTPS-Quelle mit kubectl set env) den Pod neu, regeneriert env-config.js, und beim nächsten Seitenladen wird der überarbeitete Endpunkt verwendet. Keine Neubildung von Bildern, kein Jenkins-Zyklus – ein einziges Image für viele Umgebungen, was der übliche Grund für die Verwendung von Containern ist.

Der Teil, der mich wirklich genervt hat

Der schwierige Teil ist selten die zwanzigminütige Behebung. Schmerzhaft ist vielmehr das Schweigen: Es gibt weder Konsolefehler noch Build-Warnungen, die anzeigen würden, dass der String bereits bei drei Builds zuvor „eingefroren“ wurde. Die Benutzeroberfläche wirkt, als würde Kubernetes ignoriert, obwohl der Client zur Laufzeit nie erfahren hat, dass Kubernetes existiert.

Falls ein Vite + React-Service auf Kubernetes migriert wird und die CI-Pipeline weiterhin VITE_*-Werte verwendet, die späterige Pod-Überwrites voraussetzen, überspringen Sie diesen Debugging-Zyklus. Verwenden Sie keine clientseitigen Konfigurationen, die auf das jeweilige Umfeld abgestimmt sind, im Image. Injectieren Sie diese Konfigurationen beim Start des Containers und lassen Sie den Browser die Konfiguration laden, bevor Ihr Bundle ausgeführt wird.