Configuration Spring Boot multi-environnements : Dev, Test, Prod

17 min readPar SpringCraft

Ton application fonctionne parfaitement en local. Mais quand tu la déploies en production, catastrophe : elle se connecte à la mauvaise base de données, les logs sont trop verbeux, les ports ne correspondent pas...

Contexte technique

Versions utilisées dans cet article :

  • Spring Boot 3.2+
  • Java 17+

Migration Spring Boot 2.x → 3.x : Le système de profils est identique. Seules les dépendances changent (javax.*jakarta.*).

Pourquoi c'est critique en entreprise ?

Sans configuration multi-environnements, tu exposes ton projet à trois risques majeurs :

  1. Incident de production : Connexion accidentelle à la base de production depuis un environnement de dev. Les données peuvent être corrompues ou supprimées.
  2. Coût infrastructure : Logs DEBUG en production génèrent un volume excessif. Résultat : disque saturé, puis crash applicatif.
  3. Faille de sécurité : Credentials en dur dans le code. Si le repo devient public (erreur de manipulation), vos accès sont exposés.

Cas terrain : Une startup a perdu l'équivalent de plusieurs mois de travail en quelques heures parce que leur application de dev, configurée avec ddl-auto: create-drop, s'est connectée à la production.

Le setup d'une configuration multi-environnements prend quelques heures. Les conséquences d'une mauvaise gestion peuvent coûter bien plus cher en temps, en argent et en confiance clients.

Dans cet article, nous allons voir comment Spring Boot te permet de gérer facilement différentes configurations pour le développement, les tests et la production, sans dupliquer de code et sans compromettre la sécurité.

Sommaire


Pourquoi des configurations différentes

Imaginons que tu développes une application de e-commerce. Tu as besoin de configurations différentes selon l'environnement :

ConfigurationDéveloppementTestProduction
Base de donnéesH2 en mémoirePostgreSQL localePostgreSQL AWS RDS
LogsDEBUG (tout voir)INFOWARN (seulement les erreurs)
EmailConsole (fake)MailHog (fake SMTP)SendGrid (vrai)
CacheDésactivéDésactivéRedis activé
Port8080808180 ou 443

Si vous utilisez la même configuration partout, vous allez :

  • Envoyer de vrais emails à vos clients pendant vos tests 😱
  • Avoir des logs DEBUG en production qui ralentissent l'application
  • Perdre vos données de test à chaque redémarrage (si vous utilisez H2 en prod)

Spring Boot résout ça avec les profils.


Les profils Spring Boot

Un profil est un ensemble de configurations spécifique à un environnement.

Spring Boot supporte les profils nativement. Vous pouvez définir autant de profils que vous voulez :

  • dev : pour le développement en local
  • test : pour les tests automatisés
  • staging : pour un environnement de pré-production
  • prod : pour la production

Comment ça marche ?

Spring Boot charge toujours le fichier application.yml (ou .properties).

Ensuite, selon le profil actif, il charge en plus le fichier application-{profil}.yml.

Exemple :

  • Si vous activez le profil dev, Spring charge : application.yml + application-dev.yml
  • Si vous activez le profil prod, Spring charge : application.yml + application-prod.yml

Les propriétés dans application-{profil}.yml remplacent celles de application.yml.


Fichiers de configuration : application.yml vs application.properties

Spring Boot supporte deux formats :

1. application.properties

spring.application.name=springcraft-app
server.port=8080
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=admin
spring.datasource.password=secret

2. application.yml (recommandé)

spring:
  application:
    name: springcraft-app
server:
  port: 8080
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/mydb
    username: admin
    password: secret

Configurer chaque environnement

Voici une structure typique pour un projet multi-environnements :

src/main/resources/
├─ application.yml           # Configuration commune à tous les profils
├─ application-dev.yml       # Configuration développement
├─ application-test.yml      # Configuration tests
└─ application-prod.yml      # Configuration production

application.yml (configuration commune)

Ce fichier contient les propriétés communes à tous les environnements.

spring:
  application:
    name: springcraft-app
 
  # Profil par défaut si aucun n'est spécifié
  profiles:
    active: ${SPRING_PROFILE:dev}
 
# Configuration commune
server:
  port: 8080
 
logging:
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"

Explication :

  • spring.profiles.active: ${SPRING_PROFILE:dev} : Active le profil dev par défaut, sauf si la variable d'environnement SPRING_PROFILE est définie.

application-dev.yml (développement)

Configuration pour travailler en local. On veut :

  • Une base de données en mémoire (H2) pour ne pas installer PostgreSQL
  • Des logs détaillés (DEBUG)
  • La console H2 activée pour voir la base
spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password:
 
  h2:
    console:
      enabled: true
      path: /h2-console
 
  jpa:
    show-sql: true  # Affiche les requêtes SQL dans la console
    hibernate:
      ddl-auto: create-drop  # Recrée les tables à chaque démarrage
 
logging:
  level:
    com.springcraft: DEBUG  # Logs détaillés pour votre code
    org.hibernate.SQL: DEBUG  # Logs SQL

Pourquoi ddl-auto: create-drop ? En développement, on veut souvent repartir de zéro à chaque démarrage. Spring recrée automatiquement les tables.

Danger ⚠️ : Ne JAMAIS utiliser create-drop en production, sinon vous perdez toutes vos données !

application-test.yml (tests automatisés)

Configuration pour les tests unitaires et d'intégration.

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
 
  jpa:
    show-sql: false  # Pas besoin de voir les SQL dans les tests
    hibernate:
      ddl-auto: create-drop
 
logging:
  level:
    com.springcraft: INFO  # Moins verbeux pour ne pas polluer les logs de test

application-prod.yml (production)

Configuration pour la production. On veut :

  • Une vraie base de données PostgreSQL
  • Pas de logs DEBUG (ralentit l'application)
  • Pas de ddl-auto (on gère les migrations avec Flyway/Liquibase)
  • Des connexions à la base optimisées
spring:
  datasource:
    url: ${DATABASE_URL}  # Injecté depuis l'environnement
    username: ${DATABASE_USER}
    password: ${DATABASE_PASSWORD}
    hikari:
      maximum-pool-size: 20  # Pool de connexions optimisé
      minimum-idle: 5
      connection-timeout: 30000
 
  jpa:
    show-sql: false
    hibernate:
      ddl-auto: none  # ⚠️ Ne JAMAIS modifier la structure en production
 
logging:
  level:
    com.springcraft: INFO  # Seulement les infos importantes
    org.springframework: WARN  # Seulement les warnings/erreurs

Notez bien : Les informations sensibles (URL de base, user, password) sont injectées depuis l'environnement avec ${DATABASE_URL}. On ne met jamais de vrais mots de passe dans le code !


Activer un profil

Il existe plusieurs façons d'activer un profil :

1. Dans application.yml (par défaut)

spring:
  profiles:
    active: dev

2. En ligne de commande

java -jar app.jar --spring.profiles.active=prod

3. Variable d'environnement

export SPRING_PROFILES_ACTIVE=prod
java -jar app.jar

4. Dans IntelliJ IDEA

  1. Run > Edit Configurations
  2. Active profiles : dev

5. Dans les tests

@SpringBootTest
@ActiveProfiles("test")  // Force le profil "test"
class UserServiceTest {
    // ...
}

Vérifier le profil actif

Au démarrage, Spring affiche le profil actif dans les logs :

The following 1 profile is active: "dev"

Gestion des secrets : ne jamais commiter de mots de passe

Règle d'or : Ne JAMAIS mettre de secrets (mots de passe, clés API, tokens) directement dans application.yml.

❌ Ce qu'il NE FAUT PAS faire

# application-prod.yml
spring:
  datasource:
    password: SuperMotDePasseSecret123  # 🚨 DANGER !

Pourquoi c'est dangereux ?

  • Ce fichier est versionné dans Git
  • N'importe qui ayant accès au repo peut voir le mot de passe
  • Si votre repo est public, c'est une faille de sécurité majeure

✅ La bonne pratique : variables d'environnement

# application-prod.yml
spring:
  datasource:
    url: ${DATABASE_URL}
    username: ${DATABASE_USER}
    password: ${DATABASE_PASSWORD}

Puis, sur votre serveur de production :

export DATABASE_URL=jdbc:postgresql://prod-db.aws.com:5432/mydb
export DATABASE_USER=admin
export DATABASE_PASSWORD=SuperMotDePasseSecret123

Avantages :

  • Les secrets ne sont jamais dans Git
  • Vous pouvez changer le mot de passe sans redéployer l'application
  • Chaque environnement a ses propres secrets

Valeurs par défaut

Vous pouvez définir une valeur par défaut si la variable d'environnement n'existe pas :

spring:
  datasource:
    url: ${DATABASE_URL:jdbc:postgresql://localhost:5432/mydb}

Signification : Si DATABASE_URL n'est pas défini, utilise jdbc:postgresql://localhost:5432/mydb.

Outils pour gérer les secrets

En production, utilisez des outils dédiés :

  • AWS Secrets Manager (si vous êtes sur AWS)
  • Azure Key Vault (si vous êtes sur Azure)
  • HashiCorp Vault (solution agnostique)
  • Kubernetes Secrets (si vous êtes sur Kubernetes)

Profils conditionnels avec @Profile

Vous pouvez activer des beans uniquement dans certains profils.

Exemple : Service d'envoi d'email

En développement, vous ne voulez pas envoyer de vrais emails. Vous voulez juste les afficher dans la console.

Interface commune :

public interface EmailService {
    void sendEmail(String to, String subject, String body);
}

Implémentation pour le développement :

@Service
@Profile("dev")  // Activé seulement en profil "dev"
public class ConsoleEmailService implements EmailService {
 
    @Override
    public void sendEmail(String to, String subject, String body) {
        System.out.println("===== EMAIL =====");
        System.out.println("To: " + to);
        System.out.println("Subject: " + subject);
        System.out.println("Body: " + body);
        System.out.println("=================");
    }
}

Implémentation pour la production :

@Service
@Profile("prod")  // Activé seulement en profil "prod"
public class SmtpEmailService implements EmailService {
 
    @Autowired
    private JavaMailSender mailSender;
 
    @Override
    public void sendEmail(String to, String subject, String body) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(to);
        message.setSubject(subject);
        message.setText(body);
        mailSender.send(message);  // Envoie vraiment l'email
    }
}

Utilisation :

@Service
public class UserService {
 
    @Autowired
    private EmailService emailService;  // Spring injecte la bonne implémentation selon le profil
 
    public void registerUser(User user) {
        // ...
        emailService.sendEmail(user.getEmail(), "Bienvenue !", "Merci de vous être inscrit.");
    }
}

En développement (dev), ConsoleEmailService est utilisé → l'email s'affiche dans la console.

En production (prod), SmtpEmailService est utilisé → l'email est vraiment envoyé.

Combiner plusieurs profils

Vous pouvez activer un bean pour plusieurs profils :

@Service
@Profile({"dev", "test"})  // Activé en dev ET en test
public class ConsoleEmailService implements EmailService {
    // ...
}

Ou exclure un profil :

@Service
@Profile("!prod")  // Activé partout SAUF en prod
public class ConsoleEmailService implements EmailService {
    // ...
}

Bonnes pratiques et erreurs à éviter

✅ Bonnes pratiques

1. Toujours avoir un profil par défaut

Définissez un profil par défaut pour éviter les surprises :

spring:
  profiles:
    active: ${SPRING_PROFILE:dev}

2. Séparer les secrets des configurations

Ne mettez jamais de secrets dans les fichiers de configuration versionnés.

3. Documenter vos profils

Créez un fichier README.md qui explique :

  • Quels profils existent
  • Comment les activer
  • Quelles sont les différences entre chaque profil

4. Tester chaque profil

Avant de déployer en production, testez que le profil prod fonctionne correctement.

5. Utiliser des noms de profils standards

Utilisez des noms clairs et cohérents :

  • dev (pas development, local, debug, etc.)
  • test
  • staging (si vous avez un environnement de pré-prod)
  • prod

❌ Erreurs à éviter

1. Dupliquer toute la configuration dans chaque profil

Mauvais :

# application-dev.yml
spring:
  application:
    name: springcraft-app  # Dupliqué partout
server:
  port: 8080  # Dupliqué partout
# ...

Bon :

# application.yml (configuration commune)
spring:
  application:
    name: springcraft-app
server:
  port: 8080
 
# application-dev.yml (seulement ce qui change)
spring:
  datasource:
    url: jdbc:h2:mem:testdb

Impact maintenance :

  • Duplication = risque d'incohérence entre environnements
  • Modification = autant de fichiers à modifier que de profils
  • Sur un projet de taille réelle, cela représente un effort significativement plus important pour chaque évolution

2. Utiliser create-drop en production

# application-prod.yml
spring:
  jpa:
    hibernate:
      ddl-auto: create-drop  # 🚨 DANGER ! Supprime toutes les données au redémarrage

Conséquence réelle :

  • Redémarrage applicatif (mise à jour, crash, etc.) = TOUTES vos données sont perdues
  • Pas de retour en arrière possible sans backup
  • Downtime prolongé le temps de restaurer la base

Cas réel : Une entreprise a perdu 6 mois de données clients car un développeur a oublié de retirer create-drop avant le déploiement.

En production, utilisez toujours :

spring:
  jpa:
    hibernate:
      ddl-auto: none  # ⚠️ Ne jamais laisser Hibernate modifier le schéma

Gérez les migrations avec Flyway ou Liquibase (contrôle de version du schéma).

Trade-off :

  • Avantage ddl-auto en dev : Rapidité, pas besoin de gérer les migrations
  • Inconvénient en prod : Risque énorme de perte de données
  • Solution : Flyway/Liquibase en prod, ddl-auto en dev uniquement

3. Oublier de changer le profil en production

Si vous déployez en production sans activer le profil prod, vous risquez d'utiliser la configuration de développement (H2, logs DEBUG, etc.).

Impact performance :

  • Logs DEBUG en production = 10x plus de logs = disque plein en quelques heures
  • Disque plein = app crash = downtime

Impact sécurité :

  • Console H2 exposée sur /h2-console = accès direct à votre base de données
  • Logs verbeux = exposition d'informations sensibles

Solution : Automatisez l'activation du profil dans votre pipeline CI/CD.

Exemple Dockerfile :

ENV SPRING_PROFILES_ACTIVE=prod

Exemple Kubernetes :

env:
  - name: SPRING_PROFILES_ACTIVE
    value: prod

4. Mélanger plusieurs profils

Évitez d'activer plusieurs profils en même temps si ce n'est pas nécessaire :

# Confus
java -jar app.jar --spring.profiles.active=dev,prod

Spring va charger application-dev.yml ET application-prod.yml, ce qui peut créer des conflits.

Quand c'est utile : Profils orthogonaux (ex: prod,mysql vs prod,postgresql).

Règle : Un profil principal (dev/test/prod) + éventuellement des profils secondaires (cache, messaging).

5. Commiter des secrets dans Git

Erreur #1 en sécurité :

# application-prod.yml (dans Git)
spring:
  datasource:
    password: SuperMotDePasseSecret123  # 🚨 DANGER CRITIQUE

Conséquences :

  • Secrets visibles dans l'historique Git (même si vous supprimez le commit après)
  • Repo public par accident = credentials exposés sur Google
  • Attaque automatisée par bots qui scannent GitHub

Cas réel :

  • 2024 : Des milliers de credentials AWS exposés sur GitHub public
  • Impact : factures cloud imprévues + incident de sécurité majeur

Solution :

# application-prod.yml
spring:
  datasource:
    password: ${DB_PASSWORD}  # Variable d'environnement

Puis sur le serveur :

export DB_PASSWORD=SuperMotDePasseSecret123

Mieux encore : Utilisez un gestionnaire de secrets (AWS Secrets Manager, Vault, etc.).

6. Utiliser les mêmes credentials partout

Mauvais :

# application.yml
spring:
  datasource:
    username: admin
    password: ${DB_PASSWORD}  # Même mot de passe dev/test/prod

Pourquoi c'est dangereux :

  • Si votre laptop est compromis → accès à la prod
  • Si un développeur part → changer le mot de passe partout

Bon :

  • Credentials différents par environnement
  • Rotation régulière des mots de passe prod (tous les 90 jours)
  • Principe du moindre privilège : user dev avec SELECT uniquement

7. Ne pas documenter les profils

Impact onboarding : Sans documentation, chaque nouveau développeur doit comprendre par lui-même quelle configuration utiliser. Cela ralentit l'intégration et multiplie les questions.

Solution : Créez un README.md :

## Profils disponibles
 
- `dev` : Développement local (H2, logs DEBUG)
  - Activer : `--spring.profiles.active=dev`
 
- `test` : Tests automatisés (H2, logs INFO)
  - Activé automatiquement dans les tests
 
- `prod` : Production (PostgreSQL, logs WARN)
  - Variables requises : DB_URL, DB_USER, DB_PASSWORD

Anti-patterns de production : cas réels

Cas 1 : L'environnement de staging qui coûte cher

Situation : Un environnement de staging configuré comme la prod, mais utilisé une fois par mois.

Erreur :

# application-staging.yml
spring:
  datasource:
    hikari:
      maximum-pool-size: 50  # Même config que prod

Conséquence :

  • Instance RDS dimensionnée pour la production
  • Utilisée très rarement = gaspillage de ressources

Solution : Profil staging adapté

# application-staging.yml
spring:
  datasource:
    hikari:
      maximum-pool-size: 5  # Suffisant pour les tests manuels

Impact : Réduction significative du coût d'infrastructure sans compromettre la capacité de validation.

Cas 2 : Les logs qui saturent le disque

Situation : Logs DEBUG oubliés en production.

Impact observé :

  • Volume de logs excessif (plusieurs dizaines de Go par jour)
  • Saturation du disque en quelques jours
  • Crash applicatif quand le disque est plein
  • Downtime prolongé le temps d'identifier la cause et de nettoyer

Solution :

# application-prod.yml
logging:
  level:
    root: WARN  # Seulement les warnings et erreurs
    com.springcraft: INFO  # Votre code en INFO

Règle : En prod, jamais de DEBUG sauf sur une classe spécifique pour du troubleshooting temporaire.

Cas 3 : Le cache désactivé qui ralentit tout

Situation : Cache désactivé en prod car oublié dans la config.

Conséquences observées :

  • Charge importante sur la base de données
  • Latence dégradée pour les utilisateurs
  • Nécessité de sur-dimensionner l'infrastructure base de données

Avec cache Redis :

  • Réduction drastique des requêtes vers la base (taux de hit élevé)
  • Latence divisée par 10
  • Possibilité de réduire la taille de l'instance de base de données

Impact : Amélioration significative des performances et réduction du coût d'infrastructure.

Configuration :

# application-prod.yml
spring:
  cache:
    type: redis
  data:
    redis:
      host: ${REDIS_HOST}
      port: 6379

Trade-offs : quand utiliser une autre approche

Alternative 1 : Spring Cloud Config Server

Notre approche : Fichiers YAML locaux dans src/main/resources.

Spring Cloud Config : Configuration centralisée dans un repo Git séparé.

Trade-off :

CritèreFichiers locauxSpring Cloud Config
ComplexitéFaibleMoyenne (serveur additionnel)
ScalabilitéBonne (< 10 microservices)Excellente (> 10 microservices)
DéploiementSimpleNécessite un serveur Config
RafraîchissementRedémarrage requisDynamique avec @RefreshScope
Coût infraMinimalServeur Config additionnel

Quand utiliser Spring Cloud Config :

  • Vous avez 10+ microservices
  • Vous voulez modifier la config sans redéployer
  • Vous avez besoin d'un audit centralisé des modifications

Quand rester sur des fichiers locaux :

  • Monolithe ou < 5 microservices
  • Redéploiement acceptable (< 5 min)
  • Budget serré

Alternative 2 : Variables d'environnement pures (12-factor app)

Notre approche : YAML + variables d'environnement pour les secrets.

12-factor pure : Tout en variables d'environnement, pas de fichiers de config.

# 12-factor pur
export SERVER_PORT=8080
export SPRING_DATASOURCE_URL=jdbc:postgresql://...
export SPRING_DATASOURCE_USERNAME=admin
export SPRING_DATASOURCE_PASSWORD=secret
# ... 50+ variables

Trade-off :

  • Avantage : Configuration 100% externalisée, immuabilité des images Docker
  • Inconvénient : Gestion de 50+ variables d'environnement, difficile à debugger

Quand l'utiliser :

  • Environnement Kubernetes natif
  • Forte contrainte de sécurité (pas de fichiers de config)
  • Tooling pour gérer les variables (Helm charts, Kustomize)

Notre recommandation : Hybride (YAML pour structure, env vars pour secrets).

Alternative 3 : Profils multiples actifs

Exemple : Activer plusieurs profils simultanément.

java -jar app.jar --spring.profiles.active=prod,cache,messaging

Quand c'est utile :

  • Profils orthogonaux : prod (environnement) + redis (type de cache) + kafka (messaging)
  • Permet de composer la config

Attention : Risque de conflits si deux profils définissent la même propriété.

Ordre de priorité : Le dernier profil listé gagne.


Pour aller plus loin

Vous savez maintenant gérer vos configurations multi-environnements ! Voici les prochaines étapes :

📚 Série d'articles complémentaires

  1. Comment structurer un projet Spring Boot - Organisez votre code proprement
  2. Tests dans Spring Boot : Guide complet - Testez vos différents profils
  3. Gestion des exceptions dans Spring Boot - Gérez les erreurs selon l'environnement
  4. CRUD complet avec Spring Boot - Un exemple complet avec profils
  5. Spring Boot en production : Checklist - Préparez votre déploiement

🎯 Points clés à retenir

  1. Utilisez les profils Spring Boot : dev, test, prod
  2. Ne dupliquez pas : mettez la config commune dans application.yml
  3. Jamais de secrets dans Git : utilisez des variables d'environnement
  4. Testez vos profils avant de déployer
  5. Profil par défaut : toujours en définir un

Avec une bonne gestion des configurations, vous éviterez 90% des problèmes de déploiement. C'est un investissement qui vaut le coup ! 🚀

Bonne configuration ! ⚙️