CodeGym /Cours /Docker for Spring /postgres-profil Spring Boot

postgres-profil Spring Boot

Docker for Spring
Niveau 16, Leçon 2
Disponible

1. Passage à PostgreSQL via la configuration

Quand on connecte pour la première fois une base externe, la main du débutant se tend vers le marteau préféré : « réécrivons le code pour qu’il « sache gérer Docker ». C’est normal – le cerveau essaie de fixer une relation de cause à effet. Mais dans notre cours, nous faisons délibérément l’inverse : le code de l’application reste le même, seuls l’environnement et la configuration changent.

Si l’on considère le projet Container-Ready Catalog Service comme « un seul acteur », alors standalone et postgres sont deux costumes différents pour le même personnage. En mode standalone, il stocke les données dans la mémoire du processus (rapide et simple), et en mode postgres, il les enregistre dans PostgreSQL. Mais le contrat HTTP (contrôleurs, endpoints) ne doit pas se transformer en deux applications distinctes.

C’est pourquoi nous avons tant insisté sur le principe same image, different runtime config. L’image Docker est unique, le Dockerfile est unique (canonique), mais les paramètres de lancement et les profils actifs permettent à l’application d’« activer » le mode de stockage souhaité. C’est une habitude très mature : elle aide non seulement sur un projet d’apprentissage, mais évite aussi le chaos corporatif classique du type « nous avons une branche de code Docker séparée que personne ne teste ».

2. Le profil postgres dans Spring Boot

Le mot profile dans Spring Boot désigne un « mode de fonctionnement de l’application ». Ce n’est pas une simple métaphore, mais un mécanisme bien pratique : les profils permettent d’activer/désactiver des parties de configuration et des beans, et aussi de charger des fichiers de profil application-<profile>.yml. Un profil est une façon de dire à l’application : « aujourd’hui, tu fonctionnes comme ceci ».

Dans notre projet, les profils sont des « commutateurs d’infrastructure ». standalone est le mode le plus léger, sans dépendances externes. postgres est le mode dans lequel l’application doit pouvoir se connecter à PostgreSQL. Et un point important : nous ne sommes pas obligés de coder ces différences dans if (docker) ou if (isProd) en Java. Dans ce sens, les profils sont comme un interrupteur bien organisé : il allume le courant ou non, et nous n’avons pas besoin de tirer un câble à travers toute la maison jusqu’à l’ampoule à chaque fois.

Comment les profils sont-ils activés dans le monde des conteneurs ? Dans un conteneur, il n’y a pas de « bouton dans l’IDE », mais il y a des variables d’environnement, et pour Spring Boot cela s’intègre parfaitement dans le mécanisme d’externalized configuration. Le moyen le plus direct d’activer un profil est de passer une variable :

# On active le profil postgres au démarrage de l'application
SPRING_PROFILES_ACTIVE: postgres

On peut la passer via docker run -e, ou via Compose dans services.app.environment. Nous utilisons Compose – parce que nous vivons déjà dans un monde de plusieurs conteneurs.

Pour faire court : le profil postgres n’est pas une « fonctionnalité Docker », c’est un mécanisme standard de Spring Boot, qu’il est commode de piloter depuis Docker/Compose via des variables d’environnement.

3. Datasource : application-postgres.yml et variables d’environnement

Si vous pensiez que la configuration n’est « qu’un simple fichier application.yml », sachez qu’une petite maturation vous attend. Dans un vrai projet Boot, la configuration ressemble plus à un gâteau en couches : il y a des réglages de base, des surcharges par profil, et des variables d’environnement qui redéfinissent tout dans le conteneur. Et c’est normal : l’environnement conteneurisé consiste justement à ce que la configuration vienne de l’extérieur.

Notre objectif dans application-postgres.yml est de ne pas coder en dur des valeurs spécifiques comme jdbc:postgresql://postgres:5432/catalog, mais de décrire des « points de connexion » – des placeholders d’où viendront les valeurs. Ainsi, la même image pourra fonctionner à la fois dans Compose et (si on le souhaite) dans un autre environnement – en remplaçant simplement les valeurs des variables.

Voici un exemple minimal de configuration de profil :

Fichier : src/main/resources/application-postgres.yml

spring:
  datasource:
    # Les valeurs viennent de l'extérieur (généralement via les variables d'environnement dans le conteneur)
    url: ${SPRING_DATASOURCE_URL}
    username: ${SPRING_DATASOURCE_USERNAME}
    password: ${SPRING_DATASOURCE_PASSWORD}

Il est important de comprendre le mécanisme ${...}. Spring Boot prend la valeur de la propriété et essaie de la substituer à partir des sources de configuration. Dans un conteneur, la source la plus fréquente devient les variables d’environnement. Si la variable SPRING_DATASOURCE_URL n’est pas définie, l’application plantera très probablement au démarrage (car sans URL, le datasource ne peut pas être construit). Et c’est même une bonne chose : mieux vaut « échouer honnêtement » que de se connecter au mauvais endroit et écrire silencieusement des données vers l’inconnu.

Ci-dessous, un fallback séparé, uniquement pour un lancement local en dehors de Compose, lorsque l’application se connecte réellement à PostgreSQL sur votre machine hôte. Dans l’environnement actuel app + postgres, ce n’est pas un défaut fonctionnel : à l’intérieur du conteneur, l’hôte reste postgres:5432, pas localhost:5432.

Fichier : src/main/resources/application-postgres.yml

spring:
  datasource:
    # Uniquement pour un lancement local hors Compose, quand Postgres est réellement accessible sur la machine hôte
    url: ${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/catalog}
    username: ${SPRING_DATASOURCE_USERNAME:catalog}
    password: ${SPRING_DATASOURCE_PASSWORD:change-me}

Un tel fallback est utile comme chemin local séparé, mais ne doit pas par inertie se retrouver dans la configuration Compose.

4. Paramètres de connexion à PostgreSQL

Dans toute intégration avec une base, il y a toujours un moment où l’on a envie de dire : « Mais j’ai juste écrit l’URL – pourquoi ça ne marche pas encore ? » C’est une étape normale. Alors prenons le temps d’examiner calmement quels paramètres sont nécessaires à l’application Spring Boot pour construire un DataSource, et comment les lire avec les yeux, pas comme un ensemble de lettres aléatoires.

Pour une connexion de base, nous avons besoin de trois choses : url, username, password. Tout le reste (driver, pool de connexions, timeouts) n’est pas abordé pour le moment, car notre objectif est de monter un premier environnement mature et non de transformer ce cours Docker en cours de tuning JDBC.

Dans url pour PostgreSQL, la structure est claire : jdbc:postgresql://<host>:<port>/<db>

Où <host> est l’endroit *où l’on se connecte* (dans notre cas, le nom de service postgres), <port> est le port de PostgreSQL dans le réseau Compose (généralement 5432), et <db> est le nom de la base (par exemple catalog). Si vous lisez cette ligne avec assurance, vous avez déjà une longueur d’avance sur la plupart des gens qui « copient simplement tant que ça marche ».

Il est pratique de voir cela dans un petit tableau :

Partie de l’URL Exemple Signification dans notre environnement
jdbc:postgresql jdbc:postgresql « C’est une connexion JDBC à PostgreSQL »
host postgres Nom du service dans Compose (donc un nom DNS au sein du réseau)
port 5432 Port de PostgreSQL à l’intérieur du conteneur de la base (port conteneur)
db catalog Nom de la base de données dans PostgreSQL (POSTGRES_DB)

Et c’est là que commence la confusion typique : le port 5432 est le port « interne » de la base. Sur la machine hôte, il peut être publié sur 5432, 55432 ou même ne pas être publié du tout. Pour la communication app -> postgres, cela n’a pas d’importance : l’application communique avec la base via le port du conteneur à l’intérieur du réseau Compose.

5. SPRING_DATASOURCE_URL dans Compose

Ici, un seul repère fonctionnel est nécessaire : dans Compose, l’application se connecte à la base via le nom de service et le port du conteneur. Pour l’environnement actuel, c’est postgres:5432, pas localhost:5432.

Version incorrecte (habituelle, mais erronée pour le conteneur app) :

# Dans le conteneur, localhost = le conteneur app lui-même (pas le conteneur postgres)
SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/catalog

Version correcte pour la pile Compose :

# Dans le réseau Compose, on cherche la base par le nom de service : postgres
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/catalog

postgres est ici le nom du service de base, 5432 son port interne dans le réseau Compose. Si l’on souhaite ne pas dupliquer le nom de la base à plusieurs endroits, on peut assembler l’URL ainsi :

SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB}

Ce n’est pas « une perversion esthétique », mais une simple discipline : le nom de la base vit en un seul endroit et ne se propage pas sur plusieurs lignes de configuration.

6. Ajout de app dans le même compose.yaml

Le service postgres est déjà décrit, ajoutons maintenant au même compose.yaml la partie concernant l’application. Dans le monde Compose, cela signifie que dans services.app.environment doivent apparaître les variables qui activent le profil et fournissent les paramètres du datasource.

Voici un fragment minimal mais lisible pour le service app :

services:
  app:
    build: .
    environment:
      # 1) On dit à l'application dans quel mode fonctionner
      SPRING_PROFILES_ACTIVE: postgres

      # 2) On dit à l'application où se connecter (host=postgres – nom du service BDD dans Compose)
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB}

      # 3) Login/mot de passe sont issus des mêmes valeurs qui initialisent Postgres lui-même
      SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER}
      SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}

Ce n’est donc pas une alternative séparée, mais une partie de la même pile Compose : le service de base est déjà décrit, maintenant app a reçu le profil et les paramètres de connexion. La persistance des données de la base est gérée séparément via un volume, et non par une « version Docker » spéciale de l’application.

Notez les deux couches de substitution qu’il est facile de confondre. ${POSTGRES_DB} et ${POSTGRES_USER} sont des substitutions de variables Compose dans le YAML (généralement issues de .env). Ensuite SPRING_DATASOURCE_URL et ses consœurs sont déjà des variables d’environnement à l’intérieur du conteneur que Spring Boot lira au démarrage. Compose substitue d’abord les valeurs dans le YAML, puis transmet les chaînes résultantes au conteneur, et ensuite Spring Boot lit les variables d’environnement.

Pour que cette couche soit propre, on conserve généralement à côté du compose.yaml un fichier .env.example (et le vrai .env en local, sans le committer). Par exemple :

# Exemple de fichier d'environnement : copiez dans .env et adaptez à vos besoins
POSTGRES_DB=catalog
POSTGRES_USER=catalog
POSTGRES_PASSWORD=change-me

Et oui, le mot de passe change-me est le mot de passe idéal… pour que vous ayez honte de le laisser tel quel, même dans un projet d’apprentissage. Mais pour un environnement local, il est acceptable comme placeholder : l’essentiel est que les secrets ne soient pas codés en dur. Nous ne transformons pas cette leçon en cours de sécurité, mais nous maintenons une hygiène de base.

Il y a un autre petit « zen du conteneur » : nous n’ajoutons aucun « mode Docker en Java ». Le commutateur est à l’extérieur. Si nécessaire, on change le profil et les variables d’environnement. Sinon, on lance comme avant. C’est le moment où Docker commence à faire gagner du temps à l’équipe, et non l’inverse.

7. POSTGRES_* et SPRING_DATASOURCE_*

À ce stade, il y a souvent une confusion du type « j’ai tout écrit correctement, mais ça ne marche toujours pas ». Et la raison est souvent que les variables se ressemblent, mais concernent des entités différentes. Nous avons un conteneur de base et un conteneur d’application, chacun veut sa propre configuration.

POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD sont lus par le conteneur PostgreSQL lors de l’initialisation. Ce sont ses propres réglages : quelle base créer, quel utilisateur créer, quel mot de passe définir. Cela ne concerne pas Spring, mais Postgres.

SPRING_DATASOURCE_URL, SPRING_DATASOURCE_USERNAME, SPRING_DATASOURCE_PASSWORD sont lus par l’application Spring Boot pour se connecter à la base. Cela concerne déjà le datasource et JDBC.

Il est pratique d’avoir en tête « qui lit la variable » :

Variable Qui lit Pourquoi
POSTGRES_DB conteneur postgres crée une base avec ce nom
POSTGRES_USER conteneur postgres crée un utilisateur
POSTGRES_PASSWORD conteneur postgres définit le mot de passe de l’utilisateur
SPRING_DATASOURCE_URL conteneur app (Spring Boot) savoir où se connecter
SPRING_DATASOURCE_USERNAME conteneur app (Spring Boot) avec quel utilisateur se connecter
SPRING_DATASOURCE_PASSWORD conteneur app (Spring Boot) avec quel mot de passe se connecter

Et le plus agréable : nous n'avons pas besoin d'inventer deux ensembles de valeurs différents. Nous pouvons utiliser les POSTGRES_* comme « source de vérité » pour le nom de la base et le compte, et construire les SPRING_DATASOURCE_* à partir d’eux. Cela rend l’environnement moins fragile : on change l’utilisateur à un seul endroit – et tout le reste s’ajuste.

Si l’on veut une explication un peu plus « humaine », les POSTGRES_* sont ce que vous dites à la base : « crée-moi un monde comme ceci ». Et les SPRING_DATASOURCE_* sont ce que vous dites à l’application : « voici l’adresse et les clés du monde, vas-y ». Si les clés ne correspondent pas, la base ne vous ouvrira tout simplement pas la porte. Sans rancune, ce n’est pas personnel, c’est la sécurité.

8. Contrat HTTP inchangé

Lorsque l’on change le mode de stockage des données, le plus important est de ne pas casser la partie utilisateur : l’API. Si, lors de la connexion à PostgreSQL, les endpoints ou les DTO changent soudainement, cela signifie que vous construisez deux applications différentes, et non un seul service avec deux modes. Dans notre projet, c’est considéré comme une rupture architecturale, pas comme « bon, ça a marché comme ça ».

C’est pourquoi il est utile de voir cette idée encore une fois au niveau du code. Le contrôleur reste un contrôleur, et il ne doit pas savoir ce qu’il y a en dessous : mémoire ou Postgres. Par exemple, un « squelette » conditionnel de contrôleur pourrait ressembler à ceci :

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
class CatalogController {

    // Important : la couche web ne sait pas où sont stockées les données (mémoire ou PostgreSQL) – c'est la configuration/le profil qui le décide.
    @GetMapping("/api/catalog/items")
    List<String> items() {
        // Mini-réponse pour fixer l'idée : le contrat API ne change pas lors du changement de profil.
        return List.of("demo");
    }
}

C’est bien sûr un mini-exemple (dans un vrai projet il y aura des DTO et des services), mais il fixe parfaitement l’idée : la couche web ne se transforme pas en « deux versions pour deux environnements ». Toute la magie opère via la configuration et les profils : les services/repositories sous le capot sont remplacés en fonction du profil, tandis que le HTTP reste stable.

Si l’on veut voir tout le flux « en une seule image », on peut le représenter ainsi :

flowchart TD
  C[compose.yaml] -->|environment vars| E[env dans le conteneur app]
  E -->|Spring Boot config| P[profil postgres]
  P --> DS[DataSource]
  DS --> DB[(service PostgreSQL)]

Compose définit les variables d’environnement, Spring Boot les lit et active le profil approprié, le profil conduit à la bonne configuration du datasource, et ce n’est qu’alors que la communication avec Postgres commence. Le code Java ne doit pas du tout connaître le mot « Compose ». Et c’est un excellent signe.

9. Erreurs typiques lors de l’activation du mode postgres

À ce stade, les erreurs ne sont généralement pas « subtiles », mais très humaines : nous sommes simplement habitués au lancement local et nous transposons ces habitudes dans le monde des conteneurs. Par conséquent, les erreurs se répètent d’un projet à l’autre – et c’est même agréable car elles se soignent de la même manière.

Erreur n°1 : on a oublié d’activer le profil postgres, puis on s’étonne que PostgreSQL « semble ignoré ».
Si SPRING_PROFILES_ACTIVE n’est pas défini (ou est défini avec une autre valeur), l’application peut démarrer en mode standalone et ne même pas essayer de se connecter à la base. De l’extérieur, cela ressemble à « les deux conteneurs tournent, mais les données ne sont pas sauvegardées ». En réalité, l’application vit simplement dans un autre mode. Il est utile de retenir la règle : si vous activez Postgres, le profil doit être explicitement actif.

Erreur n°2 : on a laissé localhost dans SPRING_DATASOURCE_URL.
C’est l’erreur classique du jour, et elle a une certaine élégance : elle semble très plausible, mais dans le réseau du conteneur, cela signifie « connecte-toi à toi-même ». Le résultat est généralement le même : Connection refused, et dans les logs de l’application on verra qu’elle essaie de se connecter à 127.0.0.1. Le remède est simple : l’hôte dans l’URL doit être le nom de service de la base (postgres), pas localhost.

Erreur n°3 : on confond « port pour la machine hôte » et « port pour le conteneur ».
Si vous avez publié la base comme 55432:5432, alors pour une connexion depuis votre ordinateur portable, c’est effectivement localhost:55432. Mais pour l’application à l’intérieur de Compose, c’est toujours postgres:5432. Sinon, vous forcez le conteneur de l’application à essayer d’atteindre un port hôte qui ne lui est pas destiné. Une bonne règle mentale : un conteneur se connecte toujours à un autre conteneur via le port du conteneur.

Erreur n°4 : on a mélangé les variables POSTGRES_* et SPRING_DATASOURCE_* .
Parfois, dans l’application, on définit par erreur SPRING_DATASOURCE_USERNAME: ${POSTGRES_DB} (ou on met simplement un mauvais nom de variable). En conséquence, la base est créée avec un utilisateur, et l’application arrive avec un autre – vous voyez apparaître password authentication failed. Le remède est une vérification calme : POSTGRES_* pour la base, SPRING_DATASOURCE_* pour l’application.

Erreur n°5 : on code en dur les valeurs dans application-postgres.yml et ensuite on « ne comprend pas »
Dès que vous avez intégré jdbc:postgresql://postgres:5432/catalog dans le jar, vous avez rendu la configuration « cuite ». Cela brise le principe same image, different runtime config : pour se connecter à une autre base ou changer le mot de passe, vous devrez reconstruire l’image. Il est bien plus robuste de conserver application-postgres.yml comme une structure de placeholders, et de fournir les valeurs via des variables d’environnement.

1
Mission
Docker for Spring, niveau 16, leçon 2
Bloqué
Création de `application-postgres.yml` avec des placeholders
Création de `application-postgres.yml` avec des placeholders
1
Mission
Docker for Spring, niveau 16, leçon 2
Bloqué
Séparation de `POSTGRES_*` et `SPRING_DATASOURCE_*`
Séparation de `POSTGRES_*` et `SPRING_DATASOURCE_*`
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION