Aller au contenu
Edouard Topin's Blog
TOP creuse le dev / Série 01/01

« Ça marche sur ma machine » : pourquoi pas sur la tienne ?

L’application démarre, mais sa base reste inaccessible. TOP suit la connexion : localhost, flux réseau, compte de service et permissions PostgreSQL.

Edouard Topin
9 min de lecture
TOP compare une application et PostgreSQL sur un poste, puis dans deux conteneurs où localhost ne désigne plus la base.

Le développeur te montre son application d’inventaire : deux équipements, leurs noms, tout fonctionne. Tu déploies le même code dans un conteneur. Le processus démarre. Le port HTTP répond. Mais dès que tu demandes la liste, l’application renvoie une erreur.

« Pourtant, ça marche sur ma machine. »

Dans les logs, une première piste : la connexion à localhost:5432 est refusée. La base PostgreSQL tourne pourtant bien. Avant d’ouvrir un flux ou de changer un mot de passe, il faut comprendre qui essaie de joindre quoi, depuis où et avec quelle identité.

Pour ce premier TOP Dev, nous suivrons cette application jusqu’à une vraie lecture en base. Tu retrouveras des repères infra — DNS, routage, filtrage — pour découvrir ce que le code suppose de son environnement. Un petit labo permettra ensuite de casser et réparer la connexion, sans développer une application complète.

Le trajet à vérifier

  1. La destinationQuel serveur l’application cherche-t-elle ?
  2. Le transportLa connexion atteint-elle PostgreSQL ?
  3. L’identitéQuel compte de service se présente ?
  4. L’actionCe compte peut-il lire les équipements ?

Le navigateur parle à l’application en HTTP. C’est l’application qui ouvre la connexion PostgreSQL, avec son propre compte. Le flux à examiner vers la base part donc de son environnement d’exécution, pas de ton navigateur ni de ton poste d’administration.

Premier piège : localhost a changé de voisinage

Sur le poste du développeur, l’application et PostgreSQL peuvent tourner directement sur le même système. L’adresse localhost convient alors pour joindre la base locale. Déplaçons l’application dans un conteneur avec son réseau isolé : la même adresse désigne maintenant la boucle locale de ce conteneur. Aucun PostgreSQL n’y écoute.

Le code n’a pas forcément changé. Le sens de sa configuration a changé avec son lieu d’exécution. Dans le schéma de couverture, la flèche qui revient dans le conteneur illustre ce mauvais appel à soi-même. La bonne connexion sort vers le service de base de données.

Avec les deux services app et db sur un même réseau Compose, l’application utilise db:5432. Compose fournit la résolution du nom de service ; les échanges entre conteneurs utilisent le port du conteneur cible. Publier un port de la base sur le poste n’est pas nécessaire pour cet échange. Réseaux Compose.

Cette explication suppose deux espaces réseau distincts. Des conteneurs qui partagent explicitement le réseau d’un autre service ou de l’hôte ont un autre comportement. Et db est le nom choisi dans notre labo, pas une adresse universelle à copier en production.

Voici ce que l’application doit recevoir :

PGHOST=db
PGPORT=5432
PGDATABASE=inventory
PGUSER=svc_inventory
PGPASSWORD=… fourni séparément

Ces noms sont ceux utilisés par notre exemple. Chaque application a son mécanisme de configuration. Le point à vérifier est la valeur effectivement lue par le processus, pas seulement le contenu d’un fichier présent sur le serveur. Une modification peut nécessiter de recréer le conteneur ou de redémarrer le service.

L’adresse est bonne. Le flux passe-t-il ?

Prenons maintenant une base sur une autre VM ou un service managé. Pour établir la connexion, l’application doit résoudre le bon nom, disposer d’un chemin vers la destination et recevoir les réponses. Le filtrage doit autoriser le trafic requis. PostgreSQL doit aussi écouter sur l’interface et le port visés : une base limitée à sa boucle locale ne devient pas accessible parce qu’un pare-feu autorise 5432. Paramètres d’écoute PostgreSQL.

Une demande d’ouverture de flux exploitable précise la source vue par le contrôle réseau, la destination, le protocole et le port cible. Dans notre cas : environnement applicatif vers PostgreSQL, TCP destination 5432. Un NAT peut modifier l’adresse source observée. Un routage retour absent ou un filtrage asymétrique peut interrompre l’échange. Le traitement des réponses dépend aussi du caractère stateful ou stateless des contrôles : « ouvrir 5432 dans les deux sens » ne décrit pas correctement tous les cas.

Depuis l’environnement applicatif, lorsque ces outils sont disponibles :

getent hosts db
nc -vz -w 3 db 5432

Le premier aide à observer la résolution ; le second tente une connexion TCP. Leur disponibilité et leurs options varient selon l’image. Une image minimale ne contient pas nécessairement ces outils. Un conteneur de diagnostic peut aider, à condition de vérifier qu’il reproduit bien le contexte réseau pertinent.

Lis le résultat sans lui faire dire plus qu’il ne prouve :

Observation Ce qu’elle oriente à vérifier
Nom introuvable Nom configuré, DNS et rattachement au réseau
Connexion refusée Destination atteinte avec refus actif : écoute absente, mauvais port ou rejet possible
Délai dépassé Absence de réponse à temps : routage, filtrage, cible indisponible ou saturation possibles
Connexion TCP réussie Un service accepte le transport ; l’identité et les droits restent à vérifier

Un timeout ne démontre donc pas à lui seul un blocage pare-feu. Corrèle la tentative avec les routes et les journaux disponibles. À l’inverse, une erreur PostgreSQL explicite sur le mot de passe montre que l’échange est allé plus loin qu’un simple paquet perdu.

Atteindre la base ne donne pas accès aux données

TOP suit cinq contrôles : résolution DNS, connexion TCP, TLS si configuré, authentification et autorisation de la requête SQL.

Ce diagramme est une succession de contrôles logiques, pas une topologie physique ni une capture du protocole. TLS intervient lorsqu’il est configuré. Une connexion peut atteindre le serveur puis échouer sur la confiance du certificat. Avec le client PostgreSQL libpq, sslmode=verify-full vérifie notamment la chaîne de confiance et le nom du serveur. Il faut corriger le nom ou les certificats attendus, pas contourner cette vérification pour faire disparaître l’erreur. TLS avec PostgreSQL.

Vient ensuite le compte de service : une identité dédiée à l’application, ici le rôle PostgreSQL svc_inventory doté de la capacité LOGIN. Ce rôle n’est pas automatiquement l’utilisateur Linux du conteneur ni ton compte personnel. Le développeur utilisait peut-être un compte propriétaire des tables ; la recette utilise une identité moins privilégiée.

PostgreSQL contrôle aussi les conditions d’authentification via pg_hba.conf : type de connexion, origine, base demandée et utilisateur. La première règle correspondante est retenue ; un échec d’authentification ne fait pas essayer les suivantes. Un message no pg_hba.conf entry pointe vers cette admission, pas vers un droit SELECT manquant. Règles pg_hba.conf.

Enfin, authentification et autorisation sont deux choses différentes. Le bon mot de passe ne suffit pas pour lire inventory.devices. Dans cet exemple, le compte doit pouvoir se connecter à la base, utiliser le schéma et sélectionner les lignes de la table. Ces droits peuvent être directs ou hérités. Privilèges PostgreSQL.

GRANT CONNECT ON DATABASE inventory TO svc_inventory;
-- Les commandes suivantes s’exécutent dans la base inventory.
GRANT USAGE ON SCHEMA inventory TO svc_inventory;
GRANT SELECT ON TABLE inventory.devices TO svc_inventory;

Ces commandes supposent un rôle déjà créé et sont exécutées par un administrateur ou propriétaire habilité. Elles décrivent notre besoin de lecture. Elles n’accordent ni création de tables ni modification des équipements. Les migrations de schéma relèvent d’une autre responsabilité ; les nouvelles tables pourront nécessiter leurs propres droits.

À toi de casser, puis réparer la connexion

Le labo contient une petite API Bun et PostgreSQL. L’API exécute une seule lecture, avec svc_inventory. Tu n’as pas besoin d’installer Bun ni PostgreSQL sur ton poste : Docker et Compose les exécutent. Le téléchargement initial des images demande un accès réseau. Les commandes ci-dessous ciblent macOS ou Linux avec sh, openssl et unzip.

Télécharger le labo TOP Dev, décompresse-le puis ouvre un terminal dans le dossier top-dev-inventory :

sh setup.sh
docker compose up -d --wait

Le script génère deux mots de passe locaux dans .env. La base n’a aucun port publié ; seule l’API est accessible sur 127.0.0.1:18080. Ce labo jetable ne configure pas TLS et n’est pas un modèle de déploiement de production. Ses fichiers sont lisibles avant exécution.

1. Le processus vit, la requête échoue

Ouvre http://localhost:18080/live puis http://localhost:18080/devices. La première URL confirme que le processus tourne. La seconde doit répondre HTTP 503 avec inventory_unavailable.

docker compose logs app

Au départ, PGHOST vaut localhost. L’application cherche donc PostgreSQL dans son propre conteneur. Notre API rend l’échec visible ; retourner une liste vide avec HTTP 200 aurait confondu « aucun équipement » et « inventaire inaccessible ».

2. La bonne destination, la mauvaise identité

Exécute la même lecture dans un processus temporaire, sur le réseau du labo, en corrigeant le nom mais en fournissant volontairement un mauvais secret :

docker compose run --rm -e PGHOST=db -e PGPASSWORD=wrong app bun app.js --check

L’authentification doit échouer. Le serveur a pu répondre : continuer à chercher un port fermé n’est plus la piste prioritaire. Ce test utilise le même programme et la même image, mais un nouveau processus ; il ne modifie pas le service HTTP déjà lancé.

3. Le bon compte, le droit manquant

Relance sans remplacer le mot de passe généré :

docker compose run --rm -e PGHOST=db app bun app.js --check

Tu dois rencontrer une erreur de permission sur devices. L’initialisation a volontairement accordé CONNECT et USAGE, mais pas SELECT. Le compte est authentifié ; la lecture est refusée. Accorde uniquement ce droit dans la base du labo :

docker compose exec db psql -U postgres -d inventory -c 'GRANT SELECT ON inventory.devices TO svc_inventory;'

Relance la lecture temporaire. Elle doit retourner router-paris et switch-lyon. Nous avons enfin testé l’action attendue, avec le compte de l’application.

4. Réparer le service, pas seulement le test

Dans .env, remplace APP_DB_HOST=localhost par APP_DB_HOST=db, puis recrée l’application pour qu’elle lise cette valeur :

docker compose up -d --force-recreate app

Recharge /devices dans le navigateur : les deux équipements doivent apparaître. Le compte de service n’est toujours pas administrateur. Le succès vient de la bonne destination, du bon secret et du droit nécessaire.

Pour arrêter le labo en conservant les données : docker compose down. Pour supprimer aussi les données de ce labo et repartir de zéro : docker compose down -v. Garde .env pour le redémarrer avec les mêmes secrets. L’initialisation SQL ne se rejoue que sur une base vide ; changer les mots de passe du fichier ne modifie pas ceux d’un volume déjà initialisé.

Ce qu’on transmet au prochain déploiement

Avant de conclure « problème infra » ou « problème applicatif », rassemble un petit contrat d’exécution : version du code livré, runtime, dépendances, destination attendue, flux requis, identité de service et opérations SQL nécessaires. Notre labo conserve le même programme et les mêmes images pendant l’enquête. Il fait varier la configuration et les droits ; les tags d’images ne constituent pas pour autant un verrouillage immuable des versions.

Une vérification de type pg_isready aide à attendre un serveur qui accepte les connexions. Elle ne valide pas le mot de passe du service ni son droit de lire une table. PostgreSQL le précise explicitement. Limites de pg_isready. Le contrôle décisif reste ici la requête d’inventaire, lancée depuis le bon contexte avec la bonne identité.

La prochaine fois, remplace « la VM est joignable » par une preuve située : cette application, depuis cet environnement, avec ce compte, réussit cette opération. C’est un point de rencontre concret entre dev, infra et administration de bases. Pour explorer le trajet qui précède l’application, retrouve TOP Infra et le parcours d’une URL.

Reçois le prochain par email

Les nouveaux articles et séries, envoyés à leur publication. Aucun autre courrier.

Désabonnement en un clic, à tout moment.

Retour au blog
Partager

Suivre le blog

Nouveaux articles, réflexions et mises à jour.