Modèles de configuration d'environnement qui gardent les URLs, clés et feature flags hors du code pour le web, le backend et le mobile en dev, staging et prod.

La config codée en dur semble fonctionner le premier jour. Puis vous avez besoin d'un environnement staging, d'une deuxième API ou d'un basculement rapide de fonctionnalité, et le changement « simple » devient un risque de release. La solution est simple : sortez les valeurs d'environnement des fichiers sources et placez-les dans un dispositif prévisible.
Les coupables habituels sont faciles à repérer :
« Il suffit de changer pour la prod » crée l'habitude des modifications de dernière minute. Ces modifications sautent souvent les revues, les tests et la reproductibilité. Une personne change une URL, une autre change une clé, et maintenant vous ne pouvez plus répondre à une question basique : quelle configuration exacte a été livrée avec ce build ?
Un scénario courant : vous créez une nouvelle version mobile contre staging, puis quelqu'un bascule l'URL sur la prod juste avant la sortie. Le backend change encore le lendemain, et il faut revenir en arrière. Si l'URL est codée en dur, revenir en arrière signifie une nouvelle mise à jour de l'app. Les utilisateurs attendent, et les tickets support s'empilent.
L'objectif ici est un schéma simple qui fonctionne pour une app web, un backend Go et une app mobile Flutter :
Dev, staging et prod devraient ressembler à la même application exécutée à trois endroits différents. Le but est de changer des valeurs, pas le comportement.
Ce qui doit changer : tout ce qui dépend du lieu d'exécution ou des utilisateurs : URLs et hostnames de base, identifiants, intégrations sandbox vs réelles, et contrôles de sécurité comme le niveau de logs ou des réglages plus stricts en prod.
Ce qui doit rester identique : la logique et le contrat entre les composants. Les routes API, les formes de requête/réponse, les noms de fonctionnalités et les règles métier principales ne devraient pas varier selon l'environnement. Si staging se comporte différemment, il n'est plus un répétiteur fiable pour la production.
Une règle pratique pour « nouvel environnement » vs « nouvelle valeur de config » : créez un nouvel environnement seulement quand vous avez besoin d'un système isolé (données séparées, accès et risques distincts). Si vous avez juste besoin d'endpoints différents ou de chiffres différents, ajoutez une valeur de config.
Exemple : vous voulez tester un nouveau fournisseur de recherche. Si c'est sûr de l'activer pour un petit groupe, conservez un seul staging et ajoutez un feature flag. Si cela nécessite une base de données séparée et des contrôles d'accès stricts, c'est le moment de créer un nouvel environnement.
Un bon dispositif fait une chose bien : il rend difficile l'envoi accidentel d'une URL de dev, d'une clé de test ou d'une fonctionnalité inachevée.
Utilisez les mêmes trois couches pour chaque app (web, backend, mobile) :
Pour éviter la confusion, choisissez une seule source de vérité par app et tenez-vous-en. Par exemple, le backend lit les variables d'environnement au démarrage, l'app web lit des variables au moment du build ou un petit fichier de config runtime, et l'app mobile lit un petit fichier d'environnement sélectionné au build. La cohérence à l'intérieur de chaque app compte plus que forcer le même mécanisme partout.
Un schéma simple et réutilisable ressemble à ceci :
Donnez à chaque élément de config un nom clair qui répond à trois questions : ce que c'est, où cela s'applique et quel type c'est.
Une convention pratique :
Ainsi, personne n'a à deviner si « BASE_URL » est pour l'app React, le service Go ou l'app Flutter.
Le code React s'exécute dans le navigateur de l'utilisateur, donc tout ce que vous livrez peut être lu. L'objectif est simple : gardez les secrets côté serveur, et laissez le navigateur lire seulement des paramètres « sûrs » comme l'API base URL, le nom de l'app, ou un toggle de fonctionnalité non sensible.
La config au build est injectée lors de la compilation du bundle. Elle convient aux valeurs qui changent rarement et qui sont sûres à exposer.
La config runtime est chargée au démarrage de l'app (par exemple depuis un petit fichier JSON servi avec l'app, ou une variable globale injectée). Elle est préférable pour les valeurs que vous voulez pouvoir changer après le déploiement, comme basculer une API base URL entre environnements.
Une règle simple : si le changer ne devrait pas nécessiter de reconstruire l'UI, rendez-le runtime.
Gardez un fichier local pour les développeurs (non commité) et définissez les vraies valeurs dans votre pipeline de déploiement.
.env.local (ignoré par git) avec par exemple VITE_API_BASE_URL=http://localhost:8080VITE_API_BASE_URL comme variable d'environnement dans le job de build, ou mettez-la dans un fichier de config runtime créé pendant le déploiementExemple runtime (servi à côté de votre app) :
{ "apiBaseUrl": "https://api.staging.example.com", "features": { "newCheckout": false } }
Puis chargez-le une fois au démarrage et conservez-le en un seul endroit :
export async function loadConfig() {
const res = await fetch('/config.json', { cache: 'no-store' });
return res.json();
}
Considérez toute variable d'environnement utilisée par React comme publique. N'y mettez pas de mots de passe, de clés d'API privées ou d'URLs de base de données.
Exemples sûrs : API base URL, Sentry DSN (public), version du build, et toggles simples de fonctionnalités.
La config backend reste plus sûre lorsqu'elle est typée, chargée depuis des variables d'environnement et validée avant que le serveur commence à accepter du trafic.
Commencez par décider de ce dont le backend a besoin pour démarrer, et rendez ces valeurs explicites. Valeurs « indispensables » typiques :
APP_ENV (dev, staging, prod)HTTP_ADDR (par exemple :8080)DATABASE_URL (DSN Postgres)PUBLIC_BASE_URL (utilisé pour les callbacks et liens)API_KEY (pour un service tiers)Puis chargez-les dans une struct et échouez rapidement si quelque chose manque ou est mal formé. Ainsi, vous trouvez les problèmes en quelques secondes, pas après un déploiement partiel.
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
}
Cela empêche les DSN de base de données, les clés d'API et les URLs de callback d'être dans le code ou dans Git. Dans des environnements hébergés, vous injectez ces variables d'environnement par environnement pour que dev, staging et prod puissent différer sans changer une seule ligne.
Les apps Flutter ont généralement besoin de deux couches de config : des flavors au moment du build (ce que vous publiez) et des paramètres runtime (ce que l'app peut changer sans nouvelle release). Les séparer évite que « juste un changement d'URL » devienne un rebuild d'urgence.
Créez trois flavors : dev, staging, prod. Les flavors contrôlent ce qui doit être fixé au moment du build, comme le nom de l'app, le bundle id, la signature, le projet analytics et si les outils de debug sont activés.
Passez uniquement des valeurs par défaut non sensibles avec --dart-define (ou votre CI) pour ne jamais les coder en dur dans le code :
ENV=stagingDEFAULT_API_BASE=https://api-staging.example.comCONFIG_URL=https://config.example.com/mobile.jsonEn Dart, lisez-les avec String.fromEnvironment et construisez un AppConfig simple au démarrage.
Si vous voulez éviter de rebuild pour de petits changements d'endpoints, ne traitez pas l'API base URL comme une constante. Récupérez un petit fichier de config au lancement de l'app (et mettez-le en cache). Le flavor définit seulement où récupérer la config.
Une séparation pratique :
Si vous déplacez votre backend, mettez à jour la remote config pour pointer vers la nouvelle base URL. Les utilisateurs existants la récupèrent au prochain lancement, avec un fallback sûr sur la dernière valeur mise en cache.
Les feature flags servent aux rollouts progressifs, A/B tests, kill switches rapides et tests en staging avant l'activation en prod. Ce ne sont pas un remplacement des contrôles de sécurité. Si un flag protège quelque chose qui doit être sécurisé, ce n'est pas un flag — c'est une règle d'auth.
Traitez chaque flag comme une API : nom clair, propriétaire, et date de suppression prévue.
Utilisez des noms qui indiquent ce qui se passe quand le flag est ON, et quelle partie du produit est touchée. Un schéma simple :
feature.checkout_new_ui_enabled (fonctionnalité client)ops.payments_kill_switch (interrupteur d'urgence)exp.search_rerank_v2 (expérience)release.api_v3_rollout_pct (déploiement progressif)debug.show_network_logs (diagnostic)Préférez des booléens positifs (..._enabled) plutôt que des doubles négations. Gardez un préfixe stable pour pouvoir chercher et auditer les flags.
Commencez par des valeurs sûres : si le service de flags tombe, votre app doit se comporter comme la version stable.
Un pattern réaliste : déployer un nouvel endpoint côté backend, garder l'ancien en service, et utiliser release.api_v3_rollout_pct pour déplacer progressivement le trafic. Si les erreurs montent, revenez en arrière sans hotfix.
Pour éviter l'accumulation de flags :
Un « secret » est tout élément qui causerait des dégâts s'il fuit. Pensez aux tokens API, mots de passe DB, secrets OAuth client, clés de signature (JWT), secrets de webhook et certificats privés. Ne sont pas des secrets : les API base URLs, numéros de build, flags ou IDs d'analytics publics.
Séparez les secrets du reste des paramètres. Les développeurs doivent pouvoir changer librement la config sûre, tandis que les secrets sont injectés uniquement au runtime et uniquement là où c'est nécessaire.
En dev, gardez les secrets locaux et jetables. Utilisez un fichier .env ou le trousseau OS et facilitez la réinitialisation. Ne le commitez jamais.
En staging et prod, les secrets doivent vivre dans un store dédié, pas dans le repo, pas dans les logs de chat, et pas intégrés dans les apps mobiles.
La rotation échoue quand vous échangez une clé et oubliez que d'anciens clients l'utilisent encore. Planifiez une fenêtre de chevauchement.
Cette approche marche pour les clés API, secrets de webhook et clés de signature. Elle évite les pannes surprises.
Vous avez une API staging et une nouvelle API production. Le but est de déplacer le trafic par phases, avec un moyen rapide de revenir en arrière si quelque chose cloche. C'est plus simple quand l'app lit l'API base URL depuis la config, pas depuis le code.
Traitez l'URL d'API comme une valeur de déploiement partout. Dans la web app (React), c'est souvent une valeur au moment du build ou un fichier de config runtime. En mobile (Flutter), c'est typiquement une flavor plus une remote config. Dans le backend (Go), c'est une variable d'environnement runtime. L'important est la cohérence : le code utilise un seul nom de variable (par exemple API_BASE_URL) et n'intègre jamais l'URL dans des composants, services ou écrans.
Un rollout phasé sûr :
La vérification consiste surtout à détecter tôt les décalages. Avant que de vrais utilisateurs voient le changement, confirmez que les endpoints de santé répondent, que les flux d'auth fonctionnent, et qu'un compte de test peut compléter un parcours clé de bout en bout.
La plupart des bugs de config en production sont ennuyeux : une valeur staging oubliée, un flag par défaut inversé, ou une clé d'API manquante dans une région. Un passage rapide en attrape la plupart.
Avant de déployer, confirmez que trois choses correspondent à l'environnement cible : endpoints, secrets et valeurs par défaut.
Puis faites un smoke test rapide. Choisissez un parcours utilisateur réel et exécutez-le de bout en bout, avec une installation fraîche ou un profil navigateur propre pour ne pas dépendre de tokens en cache.
Une bonne pratique : traitez staging comme la production avec des valeurs différentes. Cela signifie le même schéma de config, les mêmes règles de validation et la même forme de déploiement. Seules les valeurs doivent différer.
La plupart des pannes de config ne sont pas exotiques. Ce sont des erreurs simples qui passent parce que la config est dispersée entre fichiers, étapes de build et dashboards, et que personne ne peut répondre : « Quelles valeurs cette app utilisera-t-elle maintenant ? » Un bon dispositif rend cette question facile.
Un piège courant est de mettre des valeurs runtime dans des endroits de build-time. Intégrer une API base URL dans un build React signifie que vous devez rebuild pour chaque environnement. Ensuite quelqu'un déploie le mauvais artefact et la production pointe sur staging.
Une règle plus sûre : n'intégrez que ce qui ne change vraiment jamais après la release (comme la version de l'app). Gardez les détails d'environnement (API URLs, switches, endpoints analytics) en runtime quand c'est possible, et rendez la source de vérité évidente.
Cela arrive quand des valeurs par défaut sont « pratiques » mais dangereuses. Une app mobile peut par défaut pointer vers une API dev si elle ne peut pas lire la config, ou un backend peut retomber sur une base locale si une env var manque. Ça transforme une petite erreur de config en outage.
Deux habitudes aident :
Un exemple réaliste : une release sort le vendredi soir, et le build de production contient par accident une clé de paiement staging. Tout « fonctionne » jusqu'à ce que les paiements échouent silencieusement. La solution n'est pas une nouvelle librairie de paiement. C'est une validation qui rejette les clés non-prod en production.
Un staging qui ne correspond pas à la production donne une fausse confiance. Des réglages de DB différents, des jobs background manquants ou des flags supplémentaires font apparaître des bugs seulement après la mise en production.
Gardez staging proche en miroirant le même schéma de config, les mêmes règles de validation et la même forme de déploiement. Seules les valeurs doivent varier.
Le but n'est pas des outils sophistiqués. C'est la consistance ennuyeuse : mêmes noms, mêmes types, mêmes règles entre dev, staging et prod. Quand la config est prévisible, les releases cessent d'être risquées.
Commencez par écrire un contrat de config clair en un seul endroit. Restez concis mais spécifique : chaque nom de clé, son type (string, number, boolean), d'où il peut venir (env var, remote config, build-time) et sa valeur par défaut. Ajoutez des notes pour les valeurs qui ne doivent jamais être définies dans une app cliente (comme les clés API privées). Traitez ce contrat comme une API : les changements nécessitent une revue.
Ensuite, faites échouer les erreurs tôt. Le meilleur moment pour découvrir une API base URL manquante, c'est en CI, pas après un déploiement. Ajoutez une validation automatisée qui charge la config de la même manière que votre app et vérifie :
Enfin, facilitez la récupération quand un changement de config est erroné. Capturez un snapshot de ce qui tourne, changez une chose à la fois, vérifiez rapidement et conservez une voie de rollback.
Si vous construisez et déployez avec une plateforme comme Koder.ai (koder.ai), les mêmes règles s'appliquent : traitez les valeurs d'environnement comme des entrées pour le build et l'hébergement, gardez les secrets hors du code exporté, et validez la config avant d'envoyer. Cette cohérence rend les redéploiements et les rollbacks routiniers.
Quand la config est documentée, validée et réversible, elle cesse d'être une source de pannes et redevient une partie normale du processus de mise en production.