Contexte
Vous êtes d’astreinte. Un collègue a déployé myapp:1.4 vendredi soir avant de partir. Lundi matin, les utilisateurs signalent que l’application est inaccessible. Vous vous connectez au serveur via SSH.
Fichiers du projet :
server.js :
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
const HOST = process.env.HOST || '127.0.0.1';
app.get('/health', (req, res) => res.json({ status: 'ok' }));
app.get('/', (req, res) => res.send('Hello world'));
app.listen(PORT, HOST, () => {
console.log(`Server started on port ${PORT}`);
});Dockerfile :
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]Ce que tu observes sur le serveur :
$ docker ps
CONTAINER ID IMAGE STATUS PORTS
a3f91bc2d1e4 myapp:1.4 Up 3h (healthy) 0.0.0.0:3000->3000/tcp
$ curl http://localhost:3000
curl: (7) Failed to connect to localhost port 3000: Connection refused
$ docker logs a3f91bc2d1e4 --tail 5
Server started on port 3000
Database connected
Ready.
$ docker inspect a3f91bc2d1e4 | grep -i restart
"RestartPolicy": { "Name": "unless-stopped" }
"RestartCount": 0
$ sudo ufw status
Status: inactiveQuestion : Le conteneur est sain, aucun plantage, aucun pare-feu, le port semble correctement mappé. Pourquoi l’application est-elle inaccessible ?
Le bug est dans server.js, ligne 4 :
js
const HOST = process.env.HOST || '127.0.0.1';Pourquoi ça casse
À l’intérieur d’un container Docker, 127.0.0.1 est le loopback du container lui-même — pas de l’hôte. L’app écoute donc sur une interface que personne d’autre ne peut atteindre, même avec un port mapping déclaré.
Le docker ps est le piège classique :
0.0.0.0:3000->3000/tcpCe 0.0.0.0 décrit le côté hôte du mapping. Ça ne dit rien sur ce que l’app écoute à l’intérieur du container. Docker transfère le trafic entrant sur le port 3000 de l’hôte… vers le port 3000 du container — mais personne n’y répond, parce que l’app est repliée sur son loopback interne.
Les logs ne mentent pas non plus :
Server started on port 3000Node n’affiche pas l’interface d’écoute dans ce log — juste le port. Tout semble normal.
Le fix
Option 1 — modifier le code (recommandé) :
js
const HOST = process.env.HOST || '0.0.0.0';Option 2 — passer la variable d’environnement au démarrage :
bash
docker run -e HOST=0.0.0.0 -p 3000:3000 myapp:1.4Option 3 — vérifier avant de redéployer :
bash
# Depuis l'intérieur du container
docker exec -it a3f91bc2d1e4 curl http://127.0.0.1:3000
# → répond
docker exec -it a3f91bc2d1e4 curl http://0.0.0.0:3000
# → connection refused ← confirme le diagnosticÀ retenir
Dans un container Docker, une app doit toujours écouter sur
0.0.0.0pour être accessible depuis l’extérieur. Le port mapping Docker (-p) ne suffit pas si l’app elle-même refuse les connexions non-loopback.