Neu veröffentlicht: E-Commerce mit Power Pages, Stripe & Analytics

· Webentwicklung  · 5 minuten Lesezeit

Vite Build-Time-Umgebungsvariablen in Docker: warum kein ARG nötig ist

import.meta.env-Variablen werden von Vite beim Build eingebettet, nicht zur Laufzeit gelesen. In Docker funktioniert das über den Build Context, nicht über ARG/ENV. Ein häufiger Fehler und die überraschend einfache Lösung.

import.meta.env-Variablen werden von Vite beim Build eingebettet, nicht zur Laufzeit gelesen. In Docker funktioniert das über den Build Context, nicht über ARG/ENV. Ein häufiger Fehler und die überraschend einfache Lösung.

Inhalt

Das Missverständnis mit Vite-Umgebungsvariablen

Wenn du in einem React + Vite Projekt import.meta.env.VITE_SOMETHING verwendest, hat das eine Eigenschaft, die sich von klassischen Node.js-Umgebungsvariablen fundamental unterscheidet:

Der Wert wird zur Build-Zeit eingebettet, nicht zur Laufzeit gelesen.

Vite ersetzt alle import.meta.env.VITE_*-Referenzen während npm run build durch deren Werte. Das erzeugte JavaScript-Bundle enthält dann die Werte direkt als Strings, ohne jede Referenz auf Umgebungsvariablen. Warum Vite als Bundler und nicht tsc für das Bauen zuständig ist, erkläre ich im Artikel über moduleResolution: bundler und noEmit.

// Source code:
const apiUrl = import.meta.env.VITE_API_URL;

// After the build (in dist/):
const apiUrl = 'http://localhost:3000';

Das ist ein Feature, kein Bug. Es ermöglicht Tree-Shaking: Code-Pfade, die nur bei VITE_AUTH_ENABLED=false aktiv sind, landen nicht im Production-Bundle.

Das Problem mit Docker ARG

Wer das Vite-Verhalten kennt, kommt intuitiv auf Docker ARG/ENV:

# Attempt 1: ARG approach
FROM node:20-alpine AS builder
ARG VITE_AUTH_ENABLED=false
ENV VITE_AUTH_ENABLED=$VITE_AUTH_ENABLED
COPY . .
RUN npm run build
# docker-compose.yml
services:
  frontend:
    build:
      context: .
      args:
        VITE_AUTH_ENABLED: ${VITE_AUTH_ENABLED:-false}

Das sieht richtig aus. Ist es aber nicht, wenn die Variablen aus einer per-Projekt .env-Datei kommen sollen.

Das Problem: ${VITE_AUTH_ENABLED:-false} in Docker Compose wird von der Shell aufgelöst, bevor Docker Compose die Datei verarbeitet. Wenn keine Umgebungsvariable VITE_AUTH_ENABLED in der Shell gesetzt ist, greift der Default-Wert false. Docker Compose liest an dieser Stelle kein per-Projekt .env — es liest nur Shell-Umgebungsvariablen und eine optionale Root-.env-Datei.

Das Ergebnis: VITE_AUTH_ENABLED ist im Docker-Build immer false, egal was in frontend/.env steht.

Docker Compose env_file unter build ist nicht erlaubt

Ein naheliegender nächster Versuch:

# Attempt 2: env_file under build (not supported)
services:
  frontend:
    build:
      context: ./frontend
      env_file: ./frontend/.env # ← Fehler

Docker Compose v2.38.2 (und alle aktuellen Versionen) unterstützen env_file nicht unter dem build-Block:

additional properties 'env_file' not allowed

env_file funktioniert unter dem Service-Level (für Laufzeit-Variablen), nicht unter build (für Build-Zeit-Variablen). Das ist eine bekannte Einschränkung.

Die korrekte Lösung mit Build Context

Die Lösung ist überraschend einfach und braucht keinerlei Änderungen an Docker Compose oder dem Dockerfile.

Schritt 1: Prüfen, was im Build Context landet.

frontend/
├── .dockerignore
├── Dockerfile
├── package.json
├── src/
└── .env          ← Wird von COPY . . einbezogen?
# frontend/.dockerignore
node_modules/
dist/

node_modules/ und dist/ sind ausgeschlossen. .env steht nicht in .dockerignore. Also wird .env von COPY . . in den Container kopiert.

Schritt 2: Vite liest .env automatisch.

FROM node:20-alpine AS builder
WORKDIR /app
COPY . .             # ← .env ist jetzt im Container
RUN npm ci
RUN npm run build    # ← Vite liest .env und bettet Werte ein

Wenn Vite npm run build ausführt, sucht es automatisch nach .env, .env.local, .env.production usw. im Working Directory. Es findet frontend/.env (weil der Container-WORKDIR /app ist und COPY . . alles hineinkopiert hat) und liest die Werte.

Kein ARG, kein ENV, keine Compose-Konfiguration notwendig.

Der einzige Fallstrick mit no-cache nach Änderungen

Docker cached Build-Layer. Wenn frontend/.env sich ändert, aber keine anderen Dateien, kann Docker den gecachten Layer wiederverwenden und die neue .env nicht einlesen.

Nach Änderungen an frontend/.env immer mit --no-cache bauen:

docker compose build --no-cache frontend && docker compose up -d frontend

Oder den entsprechenden Layer invalidieren, indem eine andere Datei (z.B. package.json) geändert wird. Das ist weniger praktisch.

Verifizierung

Um sicherzustellen, dass die richtigen Werte im Bundle gelandet sind:

# Check whether AUTH_ENABLED is set correctly
grep -r "auth-disabled" dist/assets/

# Or positively: check whether the real client ID is in the bundle
grep -r "VITE_ZITADEL_CLIENT_ID" dist/assets/  # Should return no matches
grep -r "dein-client-id-wert" dist/assets/     # Should return matches

Wenn "auth-disabled" im Bundle gefunden wird, obwohl VITE_AUTH_ENABLED=true in .env steht: Docker hat den gecachten Layer verwendet. --no-cache löst das.

Konsequenz .env gehört nicht in .gitignore für Docker-Projekte?

Nein. Die .env-Datei enthält Geheimnisse (Client-IDs, API-Keys) und gehört nicht ins Git-Repository.

Der Ansatz ist: .env.example committen, .env in .gitignore, CI/CD-Pipeline injiziert Werte über echte Secrets (GitHub Actions Secrets → frontend/.env vor dem Build schreiben).

# GitHub Actions example
- name: Create frontend .env
  run: |
    echo "VITE_AUTH_ENABLED=true" > frontend/.env
    echo "VITE_ZITADEL_CLIENT_ID=${{ secrets.ZITADEL_CLIENT_ID }}" >> frontend/.env
    echo "VITE_ZITADEL_DOMAIN=${{ secrets.ZITADEL_DOMAIN }}" >> frontend/.env

Der Docker-Build läuft dann genauso wie lokal, nur dass .env von der CI-Pipeline erzeugt wird, nicht manuell.

Warum kein Root-.env

In vielen Projekten gibt es eine Root-.env, die alle Services konfiguriert. Das ist praktisch für den Start. In diesem Projekt gibt es bewusst per-Projekt-.env-Dateien:

  • frontend/.env — Vite-Variablen für das Frontend
  • extension/.env — Vite-Variablen für die Chrome Extension
  • backend/.env — Node.js-Variablen für das Backend

Jeder Service hat seinen eigenen Scope. Änderungen an Frontend-Variablen betreffen nicht das Backend-Dockerfile. docker compose build frontend reicht, kein Rebuild aller Services nötig.

Diagramm: Vite liest .env im Docker Build Context Abbildung: Der Ablauf: docker compose build kopiert den Build Context (inkl. .env) in den Container. Dort fuehrt npm run build Vite aus. Vite liest .env automatisch und bettet alle VITE-Variablen in das JS-Bundle ein. Das fertige Bundle enthaelt keine Umgebungsvariablen-Referenzen mehr._


Alle Artikel der Serie

  1. Vision und Systemübersicht: Chrome Extension, RAG-Architektur, Projekthintergrund: Artikel lesen
  2. RAG-System Aufbau: Qdrant, Embeddings, Cosine-Ähnlichkeit in TypeScript: Artikel lesen
  3. AI Provider Abstraktion: Ollama vs. OpenAI, Interface-Design, kein Vendor-Lock-in: Artikel lesen
  4. Chrome Extension MV3: Drei isolierte Laufzeitkontexte, Message Passing, Strategy Pattern: Artikel lesen
  5. Docker Compose Strategie: Override-Pattern, von lokal zu Azure: Artikel lesen
  6. Ollama lokal vs. Docker: Die Entscheidung und ihre Konsequenzen: Artikel lesen
  7. Ollama Auto-Pull Entrypoint: Automatisiertes Modell-Setup beim Container-Start: Artikel lesen
  8. tsconfig und Vite: Node16 vs. bundler, warum Vite eigene Regeln hat: Artikel lesen
  9. Instagram Caption mit MutationObserver vollständig laden: Artikel lesen
  10. Chrome Extension Foundation mit Health-Dot und Retry-Queue: Artikel lesen
  11. Phase 2 Features: Shadow DOM Overlay, Tailwind v4, Duplicate Detection: Artikel lesen
  12. Race Condition bei der Plattformerkennung: Wie ein UI-Event die Instagram-Erkennung bricht: Artikel lesen
  13. PostId-Extraktion in zwei Instagram-Layouts: querySelector vs. Ancestor-Traversal: Artikel lesen
  14. Instagram Karussell vollständig erfassen mit MutationObserver: Artikel lesen
  15. Notiz und Tags beim Screenshot-Speichern: Artikel lesen
  16. Instagram Tastatur-Shortcuts blockieren Chrome Extension Eingaben: Artikel lesen
  17. Lowercase-Normalisierung und Duplikat-Erkennung im Tag-Input: Artikel lesen
  18. Zitadel Login V2 in Docker Compose: drei versteckte Fehler: Artikel lesen
  19. PKCE OAuth in einer Chrome MV3 Extension: Artikel lesen
  20. React Frontend mit react-oidc-context und Zitadel: Artikel lesen
  21. Vite Build-Time-Umgebungsvariablen in Docker (dieser Artikel)

Du baust ein Vite-Frontend in Docker und kämpfst mit Umgebungsvariablen? Lass uns das gemeinsam einschätzen.

Zurück zum Blog

Ähnliche Beiträge

Alle Beiträge ansehen