Muster zur Umgebungs‑Konfiguration, die URLs, Keys und Feature‑Flags aus dem Code fernhalten — für Web, Backend und Mobile in Dev, Staging und Prod.

Hartkodierte Konfiguration wirkt am ersten Tag unproblematisch. Dann braucht man eine Staging‑Umgebung, eine zweite API oder einen schnellen Feature‑Schalter, und die „einfache“ Änderung wird zum Release‑Risiko. Die Lösung ist einfach: Halte Umgebungswerte aus den Quell‑Dateien fern und lege sie in einem vorhersehbaren Setup ab.
Die üblichen Übeltäter sind leicht zu erkennen:
„Ändere es einfach für Prod“ erzeugt die Gewohnheit von Last‑Minute‑Edits. Diese Änderungen umgehen oft Review, Tests und Wiederholbarkeit. Eine Person ändert eine URL, eine andere einen Key – und plötzlich kannst du eine einfache Frage nicht beantworten: Welche exakte Konfiguration wurde mit diesem Build ausgeliefert?
Ein typisches Szenario: Du baust eine neue Mobile‑Version gegen Staging, dann ändert jemand kurz vor dem Release die URL auf Prod. Das Backend ändert sich am nächsten Tag erneut, und du musst zurückrollen. Wenn die URL hartkodiert ist, bedeutet Zurückrollen ein weiteres App‑Update. Nutzer warten, und die Support‑Tickets häufen sich.
Das Ziel ist ein einfaches Schema, das über Web, Go‑Backend und Flutter‑Mobile funktioniert:
Dev, Staging und Prod sollten sich wie dieselbe App an drei verschiedenen Orten anfühlen. Es geht darum, Werte zu ändern, nicht Verhalten.
Geändert werden sollten alles, was an den Ort oder die Nutzer gebunden ist: Basis‑URLs und Hostnamen, Zugangsdaten, Sandbox‑ vs. Echt‑Integrationen und Sicherheitskontrollen wie Loglevel oder strengere Einstellungen in Prod.
Gleich bleiben sollte die Logik und das Contract‑Verhalten zwischen Komponenten. API‑Routen, Request/Response‑Formate, Feature‑Namen und Geschäftsregeln sollten nicht je nach Umgebung variieren. Wenn Staging anders funktioniert, ist es keine verlässliche Generalprobe für Production mehr.
Eine praktische Regel für „neue Umgebung“ vs. „neuer Konfigwert“: Erstelle eine neue Umgebung nur, wenn du ein isoliertes System brauchst (separate Daten, Zugriff und Risiko). Wenn du nur andere Endpunkte oder andere Zahlen brauchst, füge einen Konfigwert hinzu.
Beispiel: Du willst einen neuen Suchanbieter testen. Wenn er sicher für eine kleine Gruppe aktiviert werden kann, behalte eine Staging‑Umgebung und füge ein Feature‑Flag hinzu. Wenn er jedoch eine separate Datenbank und strikte Zugriffsregeln erfordert, dann ist eine neue Umgebung angebracht.
Ein gutes Setup erreicht eines: Es macht es schwer, versehentlich eine Dev‑URL, einen Test‑Key oder ein unfertiges Feature auszuliefern.
Verwende für jede App (Web, Backend, Mobile) dieselben drei Ebenen:
Um Verwirrung zu vermeiden, wähle eine Single Source of Truth pro App und bleibe dabei. Zum Beispiel liest das Backend beim Start Umgebungsvariablen, die Web‑App liest Build‑Time‑Variablen oder eine kleine Runtime‑Konfigurationsdatei, und die Mobile‑App liest eine kleine Umgebungsdatei, die beim Build ausgewählt wird. Konsistenz innerhalb einer App ist wichtiger als dieselbe Mechanik überall zu erzwingen.
Ein einfaches, wiederverwendbares Schema sieht so aus:
Gib jedem Konfig‑Item einen klaren Namen, der drei Fragen beantwortet: was es ist, wo es gilt und welchen Typ es hat.
Eine praktische Konvention:
So muss niemand raten, ob „BASE_URL“ für die React‑App, den Go‑Service oder die Flutter‑App ist.
React‑Code läuft im Browser des Nutzers, sodass alles, was du auslieferst, gelesen werden kann. Ziel ist simpel: Secrets bleiben auf dem Server, der Browser liest nur „sichere“ Einstellungen wie API‑Basis‑URL, App‑Name oder nicht‑sensible Feature‑Flags.
Build‑Time‑Konfiguration wird beim Erstellen des Bundles injiziert. Sie eignet sich für Werte, die selten ändern und die sicher zu exponieren sind.
Runtime‑Konfiguration wird beim Start der App geladen (z. B. aus einer kleinen JSON‑Datei, die mit der App ausgeliefert wird, oder einem injizierten globalen Objekt). Sie eignet sich besser für Werte, die du nach dem Deploy ändern möchtest, etwa das Umschalten einer API‑Basis‑URL zwischen Umgebungen.
Eine einfache Regel: Wenn das Ändern eines Wertes keinen Rebuild der UI rechtfertigt, mache ihn zur Runtime‑Konfiguration.
Halte eine lokale Datei für Entwickler (nicht committed) und setze echte Werte in deiner Deploy‑Pipeline.
.env.local (gitignored) mit etwa VITE_API_BASE_URL=http://localhost:8080VITE_API_BASE_URL als Umgebungsvariable im Build‑Job oder lege sie in eine zur Laufzeit erzeugte Konfigurationsdatei während des DeploymentsRuntime‑Beispiel (neben deiner App bereitgestellt):
{ "apiBaseUrl": "https://api.staging.example.com", "features": { "newCheckout": false } }
Dann lade die Datei einmal beim Start und halte die Werte an einer einzigen Stelle:
export async function loadConfig() {
const res = await fetch('/config.json', { cache: 'no-store' });
return res.json();
}
Behandle alles in React‑Env‑Variablen als öffentlich. Packe keine Passwörter, privaten API‑Keys oder Datenbank‑URLs in die Web‑App.
Sichere Beispiele: API‑Basis‑URL, Sentry DSN (public), Build‑Version und einfache Feature‑Flags.
Backend‑Konfiguration bleibt sicherer, wenn sie getypt ist, aus Umgebungsvariablen geladen und vor dem Start validiert wird.
Beginne damit, explizit festzulegen, was das Backend zum Laufen braucht. Typische "Must‑Have"‑Werte sind:
APP_ENV (dev, staging, prod)HTTP_ADDR (z. B. :8080)DATABASE_URL (Postgres DSN)PUBLIC_BASE_URL (für Callbacks und Links)API_KEY (für einen Drittanbieterdienst)Lade diese Werte in eine Struct und fail fast, wenn etwas fehlt oder falsch formatiert ist. So findest du Probleme in Sekunden, nicht erst nach einem teilweisen Deploy.
package config
import (
"errors"
"net/url"
"os"
"strings"
)
type Config struct {
Env string
HTTPAddr string
DatabaseURL string
PublicBaseURL string
APIKey string
}
func Load() (Config, error) {
c := Config{
Env: mustGet("APP_ENV"),
HTTPAddr: getDefault("HTTP_ADDR", ":8080"),
DatabaseURL: mustGet("DATABASE_URL"),
PublicBaseURL: mustGet("PUBLIC_BASE_URL"),
APIKey: mustGet("API_KEY"),
}
return c, c.Validate()
}
func (c Config) Validate() error {
if c.Env != "dev" && c.Env != "staging" && c.Env != "prod" {
return errors.New("APP_ENV must be dev, staging, or prod")
}
if _, err := url.ParseRequestURI(c.PublicBaseURL); err != nil {
return errors.New("PUBLIC_BASE_URL must be a valid URL")
}
if !strings.HasPrefix(c.DatabaseURL, "postgres://") {
return errors.New("DATABASE_URL must start with postgres://")
}
return nil
}
func mustGet(k string) string {
v, ok := os.LookupEnv(k)
if !ok || strings.TrimSpace(v) == "" {
panic("missing env var: " + k)
}
return v
}
func getDefault(k, def string) string {
if v, ok := os.LookupEnv(k); ok && strings.TrimSpace(v) != "" {
return v
}
return def
}
Das hält Datenbank‑DSNs, API‑Keys und Callback‑URLs aus dem Code und aus Git. In gehosteten Setups injizierst du diese Env‑Variablen pro Umgebung, sodass Dev, Staging und Prod sich unterscheiden können, ohne eine einzige Zeile zu ändern.
Flutter‑Apps brauchen meist zwei Konfigurationsschichten: Build‑Time‑Flavors (was du lieferst) und Runtime‑Einstellungen (was die App ohne neues Release ändern kann). Diese Trennung verhindert, dass „nur eine URL‑Änderung“ zu einem Notfall‑Rebuild wird.
Erstelle drei Flavors: dev, staging, prod. Flavors sollten Dinge kontrollieren, die beim Build fixiert sein müssen, wie App‑Name, Bundle‑ID, Signatur, Analytics‑Projekt und ob Debug‑Tools aktiviert sind.
Gib nur nicht‑sensible Defaults mit --dart-define (oder deinem CI) weiter, sodass du sie nie hartkodierst:
ENV=stagingDEFAULT_API_BASE=https://api-staging.example.comCONFIG_URL=https://config.example.com/mobile.jsonIn Dart liest du sie mit String.fromEnvironment und baust beim Start ein einfaches AppConfig‑Objekt.
Wenn du für kleine Endpunkt‑Änderungen keinen Rebuild willst, behandle die API‑Basis‑URL nicht als Konstante. Lade bei App‑Start eine kleine Konfigurationsdatei (und cache sie). Der Flavor legt nur fest, woher die Konfiguration geholt wird.
Eine praktische Aufteilung:
Wenn du das Backend verschiebst, aktualisierst du die Remote‑Config, sodass bestehende Nutzer die neue Basis‑URL beim nächsten Start übernehmen, mit sicherer Rückfalloption auf den zuletzt gecachten Wert.
Feature‑Flags sind nützlich für schrittweise Rollouts, A‑B‑Tests, schnelle Kill‑Switches und das Testen riskanter Änderungen in Staging, bevor du sie in Prod einschaltest. Sie ersetzen keine Sicherheitskontrollen. Wenn ein Flag etwas schützt, das wirklich gesichert werden muss, ist es kein Flag — es ist eine Auth‑Regel.
Behandle jedes Flag wie eine API: klarer Name, ein Owner und ein Enddatum.
Verwende Namen, die beschreiben, was passiert, wenn das Flag AN ist, und welchen Produktbereich es betrifft. Ein einfaches Schema:
feature.checkout_new_ui_enabled (kundenorientiertes Feature)ops.payments_kill_switch (Notaus‑Schalter)exp.search_rerank_v2 (Experiment)release.api_v3_rollout_pct (schrittweiser Rollout)debug.show_network_logs (Diagnose)Bevorzuge positive Booleans (..._enabled) statt doppelter Verneinungen. Halte ein stabiles Präfix, damit Flags durchsuchbar und auditierbar sind.
Starte mit sicheren Defaults: Wenn der Flag‑Service ausfällt, sollte deine App sich wie die stabile Version verhalten.
Ein realistisches Muster: Liefere einen neuen Endpoint im Backend, halte den alten weiter am Laufen und nutze release.api_v3_rollout_pct, um Traffic schrittweise zu verschieben. Wenn Fehler steigen, schaltest du zurück, ohne einen Hotfix.
Um ein Flag‑Wachstum zu verhindern, halte dich an ein paar Regeln:
Ein "Secret" ist alles, was bei Leck Schaden anrichten würde. Denke an API‑Tokens, Datenbankpasswörter, OAuth‑Client‑Secrets, Signaturschlüssel (JWT), Webhook‑Secrets und private Zertifikate. Keine Secrets sind: API‑Basis‑URLs, Build‑Nummern, Feature‑Flags oder öffentliche Analytics‑IDs.
Trenne Secrets von den übrigen Einstellungen. Entwickler sollten sichere Konfiguration frei ändern können, während Secrets nur zur Laufzeit injiziert und nur dort verfügbar sind, wo sie gebraucht werden.
Im Dev‑Umfeld halte Secrets lokal und leicht austauschbar. Nutze eine .env‑Datei oder den OS‑Keychain und mache das Zurücksetzen einfach. Niemals committen.
In Staging und Prod gehören Secrets in einen dedizierten Secret‑Store, nicht ins Repo, nicht in Chat‑Logs und nicht in mobile Binaries.
Rotation scheitert oft, weil man einen Key swapt und vergisst, dass alte Clients ihn noch nutzen. Plane ein Überlappungsfenster.
Dieser Überlappungsansatz funktioniert für API‑Keys, Webhook‑Secrets und Signier‑Schlüssel und vermeidet Überraschungs‑Outages.
Du hast eine Staging‑API und eine neue Production‑API. Ziel ist, Traffic phased umzuziehen mit einer schnellen Rückkehr‑Option, falls etwas schiefgeht. Das ist einfacher, wenn die App die API‑Basis‑URL aus der Konfiguration liest, nicht aus hartkodiertem Code.
Behandle die API‑URL überall als deploy‑zeitlichen Wert. In der Web‑App (React) ist sie oft Build‑Time oder Runtime‑Config‑Datei. In Mobile (Flutter) ist es typischerweise ein Flavor plus Remote‑Config. Im Backend (Go) ist es eine Runtime‑Env‑Var. Wichtig ist die Konsistenz: Der Code nutzt einen einzigen Variablennamen (z. B. API_BASE_URL) und bettet die URL nie direkt in Komponenten, Services oder Screens ein.
Ein sicherer, gestaffelter Rollout kann so aussehen:
Verifikation ist vor allem frühes Erkennen von Unstimmigkeiten. Bevor echte Nutzer die Änderung sehen, prüfe Health‑Endpoints, Auth‑Flows und dass ein Testkonto einen wesentlichen End‑to‑End‑Pfad erfolgreich durchlaufen kann.
Die meisten Production‑Config‑Bugs sind langweilig: ein Staging‑Wert blieb stehen, ein Flag‑Default ist falsch oder ein API‑Key fehlt in einer Region. Ein schneller Check fängt die meisten ab.
Vor dem Deploy bestätige, dass drei Dinge zur Zielumgebung passen: Endpunkte, Secrets und Defaults.
Dann mache einen schnellen Smoke‑Test. Wähle einen echten Nutzer‑Flow und führe ihn End‑to‑End aus, mit frischer Installation oder sauberem Browser‑Profil, damit du nicht von gecachten Tokens abhängst.
Eine praktische Gewohnheit: Behandle Staging wie Production mit anderen Werten. Das bedeutet dieselbe Konfig‑Schema, dieselben Validierungsregeln und dieselbe Deployment‑Form. Nur die Werte unterscheiden sich, nicht die Struktur.
Die meisten Konfigurationsausfälle sind nicht exotisch. Es sind einfache Fehler, die durch verstreute Konfiguration in Dateien, Build‑Schritten und Dashboards passieren, sodass niemand klar beantworten kann: „Welche Werte verwendet diese App gerade?“ Ein gutes Setup macht diese Frage leicht.
Eine häufige Falle ist, Runtime‑Werte an Build‑Time‑Orte zu legen. Eine in den React‑Build gebackene API‑Basis‑URL bedeutet, dass du für jede Umgebung neu bauen musst. Dann wird vielleicht das falsche Artefakt deployed und Production zeigt auf Staging.
Eine sicherere Regel: Backe nur Werte ein, die wirklich nach dem Release nie mehr ändern (z. B. eine App‑Version). Halte Umgebungsdetails (API‑URLs, Feature‑Switches, Analytics‑Endpoints) wenn möglich zur Laufzeit und mache die Quelle der Wahrheit offensichtlich.
Das passiert, wenn Defaults zwar „hilfreich“ sind, aber unsicher. Eine Mobile‑App könnte standardmäßig auf eine Dev‑API zurückfallen, wenn sie Konfig nicht lesen kann, oder ein Backend könnte auf eine lokale DB zurückfallen, wenn eine Env‑Var fehlt. Das verwandelt einen kleinen Konfig‑Fehler in einen kompletten Ausfall.
Zwei Gewohnheiten helfen:
Ein realistisches Beispiel: Ein Release geht Freitagabend raus, und der Produktionsbuild enthält versehentlich einen Staging‑Payment‑Key. Alles „funktioniert“, bis Zahlungen stillschweigend fehlschlagen. Die Lösung ist keine neue Payment‑Bibliothek — es ist Validierung, die nicht‑production Keys in Prod ablehnt.
Staging, das nicht zu Production passt, gibt falsche Sicherheit. Unterschiedliche Datenbankeinstellungen, fehlende Hintergrundjobs oder zusätzliche Feature‑Flags führen dazu, dass Bugs erst nach dem Launch auftreten.
Halte Staging nah an Production, indem du dasselbe Konfig‑Schema, dieselben Validierungsregeln und dieselbe Deployment‑Form verwendest. Nur die Werte sollten abweichen, nicht die Struktur.
Das Ziel ist kein fancy Tooling, sondern langweilige Konsistenz: dieselben Namen, dieselben Typen, dieselben Regeln in Dev, Staging und Prod. Wenn Konfiguration vorhersehbar ist, fühlt sich ein Release nicht mehr riskant an.
Beginne damit, einen klaren Config‑Contract an einem Ort zu dokumentieren. Halte ihn kurz, aber spezifisch: jeder Schlüsselname, sein Typ (string, number, boolean), woher er kommen darf (Env‑Var, Remote‑Config, Build‑Time) und sein Default. Notiere Werte, die niemals in einer Client‑App gesetzt werden dürfen (z. B. private API‑Keys). Behandle diesen Contract wie eine API: Änderungen brauchen Review.
Lass Fehler früh fehlschlagen. Die beste Zeit, eine fehlende API‑Basis‑URL zu entdecken, ist in CI, nicht nach dem Deploy. Füge automatisierte Validierung hinzu, die Konfiguration genauso lädt wie deine App und prüft:
Schließlich: Erleichtere die Wiederherstellung, wenn eine Konfig‑Änderung schiefgeht. Snapshot, ändere eine Sache nach der anderen, verifiziere schnell und behalte einen Rollback‑Pfad.
Wenn du mit einer Plattform wie Koder.ai (koder.ai) baust und deployst, gelten dieselben Regeln: Behandle Umgebungswerte als Inputs für Build und Hosting, halte Secrets aus exportiertem Quellcode fern und validiere Konfiguration bevor du auslieferst. Diese Konsistenz macht Redeploys und Rollbacks routinemäßig.
Wenn Konfiguration dokumentiert, validiert und reversibel ist, wird sie keine Fehlerquelle mehr, sondern ein normaler Teil des Auslieferungsprozesses.