Changer le thème

Accéder à l'hôte depuis un conteneur Docker : guide complet de host.docker.internal

Easton editorial illustration: observability control panel

Vendredi 15 h, je fixais le message d’erreur dans le terminal — Connection refused.

Franchement, c’était frustrant. MySQL tournait bien en local, Navicat se connectait, la ligne de commande aussi, mais l’application dans le conteneur refusait obstinément de joindre. J’ai vérifié trois fois la chaîne de connexion : localhost:3306, rien à redire. Utilisateur et mot de passe corrects. Alors, où était le problème ?

Finalement, j’ai compris : le coupable, c’étaient ces trois lettres — localhost.

Si vous avez déjà vécu ça — dans un conteneur Docker, localhost ou 127.0.0.1 ne joint jamais les services de l’hôte — cet article est pour vous. Je vais expliquer clairement pourquoi localhost dans un conteneur n’est pas celui que vous croyez, et comment résoudre le problème proprement avec host.docker.internal, ce « domaine magique ».

Dans cet article, vous apprendrez :

  • Le vrai principe de l’isolation réseau des conteneurs (sans jargon inutile)
  • La bonne configuration sur Mac, Windows et Linux
  • Une checklist de dépannage pratique (à consulter la prochaine fois)

Pourquoi localhost ne fonctionne pas ?

Réponse directe : un conteneur a son propre monde réseau.

Un peu abstrait ? Imaginez le conteneur comme une petite maison indépendante, avec sa propre adresse, sa propre boîte aux lettres, tout. Quand vous dites « localhost » ou frappez à « 127.0.0.1 » dans le conteneur, vous cherchez cette petite maison, pas la machine hôte à l’extérieur.

Concrètement :

  • Sur l’hôte, localhost pointe vers l’hôte lui-même
  • Dans le conteneur, localhost pointe vers le conteneur lui-même
  • Deux localhost, ce n’est pas la même chose

La première fois que j’ai compris ça, j’ai été surpris. MySQL tournait pourtant bien sur mon ordinateur — pourquoi le conteneur ne pouvait-il pas s’y connecter ? Parce que le conteneur cherchait MySQL dans son propre monde, et ne le trouvait évidemment pas.

Mécanisme d’isolation réseau des conteneurs

Docker crée pour chaque conteneur un « espace de noms réseau » indépendant (ne vous laissez pas intimider par le terme). En bref :

Chaque conteneur a sa propre carte réseau, sa propre adresse IP, sa propre table de routage. Comme chez vous et chez le voisin : même immeuble, mais chacun son Wi-Fi, sans interférence.

Conteneur et hôte sont reliés par un pont virtuel appelé docker0. L’IP du conteneur ressemble souvent à 172.17.0.x ; du point de vue du conteneur, l’hôte est à 172.17.0.1 (adresse de la passerelle du pont).

Dans le conteneur, accéder à localhost, c’est accéder au 127.0.0.1 du conteneur, pas au 127.0.0.1 de l’hôte. Impossible de joindre MySQL sur l’hôte.

Voici à quoi ressemble une erreur typique :

Error: connect ECONNREFUSED 127.0.0.1:3306

Ou :

Can't connect to MySQL server on 'localhost' (111)

C’est l’erreur classique quand on utilise localhost dans un conteneur pour joindre un service sur l’hôte.

Qu’est-ce que host.docker.internal ?

Puisque localhost ne convient pas, comment faire accéder le conteneur à l’hôte ?

Docker propose une solution élégante : host.docker.internal. C’est un domaine spécial qui se résout automatiquement vers l’IP de l’hôte. Considérez-le comme le « surnom » de l’hôte — quelle que soit l’IP réelle, ce nom suffit pour le trouver.

Par exemple, si MySQL écoute sur le port 3306 de l’hôte, connectez-vous ainsi dans le conteneur :

mysql://user:[email protected]:3306/dbname

Pas besoin de savoir si l’hôte est en 192.168.1.100 ou 10.0.0.5, ni de craindre un changement de réseau — host.docker.internal pointe toujours vers la bonne adresse.

Pratique, non ?

Versions et support par plateforme

Attention, il y a un piège.

Utilisateurs Mac et Windows (Docker Desktop)

Avec Docker Desktop (l’interface graphique), host.docker.internal est supporté nativement depuis la version 18.03 (mars 2018). Prêt à l’emploi, sans configuration supplémentaire.

Écrivez directement host.docker.internal dans le code :

const mysql = require('mysql2');
const connection = mysql.createConnection({
  host: 'host.docker.internal',  // aussi simple que ça
  port: 3306,
  user: 'root',
  password: 'your_password'
});

Utilisateurs Linux (Docker Engine)

Linux est moins chanceux. Docker y tourne directement sur le système, sans machine virtuelle intermédiaire comme sur Mac/Windows — host.docker.internal n’existe pas par défaut.

Bonne nouvelle : depuis Docker Engine 20.10 (décembre 2020), on peut l’activer manuellement. Comment ? La section suivante détaille tout.

Pour des versions plus anciennes, quelques alternatives :

  • Utiliser 172.17.0.1 (IP de la passerelle du pont Docker par défaut)
  • Utiliser l’IP réelle de l’hôte sur le réseau Docker
  • Utiliser docker.for.mac.host.internal (anciennes versions Mac uniquement)

Configuration sur les trois plateformes

Voici le concret, avec des configurations copiables.

Configuration Mac/Windows (Docker Desktop)

Le cas le plus simple.

Méthode 1 : utilisation directe dans le code

Aucune configuration supplémentaire — écrivez host.docker.internal directement :

# docker-compose.yml
version: '3'
services:
  app:
    image: myapp:latest
    environment:
      - DB_HOST=host.docker.internal  # directement
      - DB_PORT=3306

Méthode 2 : déclaration explicite (optionnelle)

Même si ce n’est pas nécessaire, vous pouvez ajouter extra_hosts :

version: '3'
services:
  app:
    image: myapp:latest
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      - DB_HOST=host.docker.internal

host-gateway est la syntaxe Docker 20.10+, signifiant « adresse de la passerelle de l’hôte ».

Avec docker run :

docker run -d \
  --add-host=host.docker.internal:host-gateway \
  -e DB_HOST=host.docker.internal \
  myapp:latest

Configuration Linux (Docker Engine)

Sur Linux, c’est un peu plus laborieux — configuration manuelle requise.

Méthode 1 : recommandée — host-gateway

La méthode la plus universelle, pour Docker 20.10+ sur toutes les plateformes :

# docker-compose.yml
version: '3'
services:
  app:
    image: myapp:latest
    extra_hosts:
      - "host.docker.internal:host-gateway"  # configuration clé
    environment:
      - DB_HOST=host.docker.internal
      - DB_PORT=3306

Avec docker run :

docker run -d \
  --add-host=host.docker.internal:host-gateway \
  -e DB_HOST=host.docker.internal \
  myapp:latest

Avantage : multi-plateforme — la même configuration fonctionne sur Mac, Windows et Linux.

Méthode 2 : alternative — IP du pont Docker

Si host-gateway n’est pas disponible (Docker trop ancien), utilisez l’adresse de la passerelle du pont par défaut :

version: '3'
services:
  app:
    image: myapp:latest
    extra_hosts:
      - "host.docker.internal:172.17.0.1"  # passerelle Docker par défaut
    environment:
      - DB_HOST=host.docker.internal

172.17.0.1 est la passerelle par défaut du réseau bridge Docker. Dans la grande majorité des cas, cette IP est correcte, sauf si vous avez modifié la configuration réseau Docker.

Méthode 3 : ultime — mode réseau host

Si rien d’autre ne fonctionne, voici la solution radicale :

docker run -d \
  --network=host \
  -e DB_HOST=localhost \  # localhost fonctionne ici
  myapp:latest

Ou dans docker-compose :

version: '3'
services:
  app:
    image: myapp:latest
    network_mode: "host"  # réseau de l'hôte
    environment:
      - DB_HOST=localhost  # localhost utilisable directement

Avantages : simple et direct — le conteneur utilise la pile réseau de l’hôte, localhost est le vrai localhost.

Inconvénients :

  • Compromet l’isolation réseau du conteneur
  • Conteneur et hôte partagent les ports, risque de conflit (ex. le conteneur veut le 8080 déjà pris sur l’hôte)
  • Linux uniquement, Mac/Windows non supportés
  • Non recommandé en production, réservé au dev local

Configuration multi-plateforme (fortement recommandée)

Si votre équipe mélange Mac et Linux, ou si votre code tourne dans plusieurs environnements, utilisez :

# docker-compose.yml
version: '3'
services:
  app:
    image: myapp:latest
    extra_hosts:
      - "host.docker.internal:host-gateway"  # reconnu sur toutes les plateformes
    environment:
      - DB_HOST=host.docker.internal
      - DB_PORT=3306
      - DB_USER=root
      - DB_PASSWORD=your_password

Cette configuration fonctionne sur toutes les plateformes Docker 20.10+ (fin 2020). Si votre Docker date d’avant 2020… honnêtement, il est temps de mettre à jour.

Points clés pour les services sur l’hôte

Configurer le conteneur ne suffit pas.

Les services sur l’hôte doivent aussi être correctement configurés, sinon la connexion échouera. Beaucoup l’oublient — voici ce qu’il faut savoir.

Le service doit écouter sur la bonne adresse

C’est le piège le plus courant.

Beaucoup de services n’écoutent par défaut que sur 127.0.0.1, donc uniquement les connexions locales. Or un conteneur Docker n’est pas « local » — les requêtes venant du pont Docker seront refusées.

Il faut écouter sur 0.0.0.0, c’est-à-dire accepter les connexions sur toutes les interfaces.

Configuration MySQL

Fichier de configuration, généralement :

  • Linux : /etc/mysql/mysql.conf.d/mysqld.cnf
  • Mac (Homebrew) : /usr/local/etc/my.cnf
  • Windows : C:\ProgramData\MySQL\MySQL Server 8.0\my.ini

Modifiez bind-address :

[mysqld]
# Avant, souvent :
# bind-address = 127.0.0.1

# Changez en :
bind-address = 0.0.0.0

Puis redémarrez MySQL :

# Linux
sudo systemctl restart mysql

# Mac
brew services restart mysql

# Windows
# Redémarrer le service MySQL dans le gestionnaire de services

Configuration Redis

Éditez redis.conf (souvent /etc/redis/redis.conf ou /usr/local/etc/redis.conf) :

# Trouvez cette ligne
bind 127.0.0.1 -::1

# Changez en
bind 0.0.0.0

Redémarrez Redis :

# Linux
sudo systemctl restart redis

# Mac
brew services restart redis

Configuration PostgreSQL

Éditez postgresql.conf :

listen_addresses = '*'  # écouter sur toutes les adresses

Modifiez aussi pg_hba.conf pour autoriser le segment Docker :

# Ajoutez cette ligne pour autoriser 172.17.0.0/16
host    all             all             172.17.0.0/16           md5

Configuration des droits utilisateur (MySQL)

Même si MySQL écoute sur 0.0.0.0, les droits utilisateur comptent encore.

Les droits MySQL sont gérés par « utilisateur@hôte source ». root@localhost et root@% sont deux utilisateurs distincts.

Si votre utilisateur MySQL n’autorise que localhost, le conteneur ne pourra pas se connecter. Accordez les droits pour le segment Docker :

-- Option 1 : accès depuis n'importe quel hôte (simple mais moins sûr)
GRANT ALL PRIVILEGES ON *.* TO 'your_user'@'%' IDENTIFIED BY 'your_password';

-- Option 2 : segment Docker uniquement (plus sûr)
GRANT ALL PRIVILEGES ON *.* TO 'your_user'@'172.17.0.%' IDENTIFIED BY 'your_password';

-- Recharger les droits
FLUSH PRIVILEGES;

Pour MySQL 8.0+, la syntaxe diffère légèrement :

-- Créer l'utilisateur
CREATE USER 'your_user'@'%' IDENTIFIED BY 'your_password';

-- Accorder les droits
GRANT ALL PRIVILEGES ON *.* TO 'your_user'@'%';

FLUSH PRIVILEGES;

Configuration du pare-feu

Sur certains systèmes, le pare-feu bloque l’accès des conteneurs Docker aux services de l’hôte.

Vérifier l’état du pare-feu :

# Linux (ufw)
sudo ufw status

# Linux (firewalld)
sudo firewall-cmd --state

Autoriser le segment Docker (exemple port MySQL 3306) :

# ufw
sudo ufw allow from 172.17.0.0/16 to any port 3306

# firewalld
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="172.17.0.0/16" port port="3306" protocol="tcp" accept'
sudo firewall-cmd --reload

Recommandations de sécurité

Écouter sur 0.0.0.0 comporte un risque — votre service devient accessible à d’autres machines du réseau.

En production :

  1. Écouter sur une interface spécifique : si vous connaissez l’interface Docker, n’écoutez que celle-ci

    bind-address = 172.17.0.1
  2. Combiner avec le pare-feu : autoriser uniquement le segment Docker, bloquer le reste

  3. Utiliser un conteneur de base de données dédié : ne pas faire tourner la BDD sur l’hôte ; lancez-la via Docker Compose, conteneur app et conteneur BDD sur le même réseau — plus sûr

En développement local :

Honnêtement, écouter sur 0.0.0.0 en local pose peu de problème. Votre machine n’est pas un serveur, l’extérieur n’y accède pas. Pas de stress.

Checklist de dépannage des problèmes courants

Problème de connexion ? Pas de panique — suivez cette checklist.

Problème 1 : Connection refused (connexion refusée)

L’erreur la plus fréquente :

Error: connect ECONNREFUSED host.docker.internal:3306

Ou :

Can't connect to MySQL server on 'host.docker.internal' (111)

Causes possibles et étapes :

Étape 1 : vérifier que le service tourne sur l’hôte

Sur l’hôte :

# MySQL
sudo systemctl status mysql    # Linux
brew services list              # Mac

# Vérifier que le port est en écoute
netstat -an | grep 3306
# ou
lsof -i :3306

Si le service n’est pas démarré, démarrez-le d’abord.

Étape 2 : vérifier l’adresse d’écoute

Sur l’hôte :

# Voir sur quelle adresse MySQL écoute
sudo netstat -tlnp | grep 3306

La sortie devrait ressembler à :

tcp  0  0 0.0.0.0:3306  0.0.0.0:*  LISTEN  1234/mysqld

Regardez la troisième colonne. Si c’est 0.0.0.0:3306, tout va bien. Si c’est 127.0.0.1:3306, le service n’écoute que localement — le conteneur ne pourra pas se connecter.

Solution : modifiez bind-address en 0.0.0.0 comme dans la section « Configuration des services sur l’hôte ».

Étape 3 : vérifier le pare-feu

Désactivez temporairement le pare-feu pour tester :

# Linux (ufw)
sudo ufw disable

# Linux (firewalld)
sudo systemctl stop firewalld

# Mac
# Réglages système -> Sécurité et confidentialité -> Pare-feu -> Désactiver

Si ça fonctionne sans pare-feu, c’était bien lui. Configurez les règles comme indiqué plus haut, puis réactivez le pare-feu.

Problème 2 : Connection timeout (délai dépassé)

Error: connect ETIMEDOUT host.docker.internal:3306

Un timeout est plus délicat qu’un refus — les paquets partent mais ne reviennent pas.

Étapes :

Étape 1 : vérifier la résolution de host.docker.internal

Dans le conteneur :

# Entrer dans le conteneur
docker exec -it your_container sh

# Tester
ping host.docker.internal

Si ping échoue ou « unknown host », host.docker.internal n’est pas configuré.

Utilisateurs Linux : vérifiez que docker-compose.yml ou docker run inclut --add-host=host.docker.internal:host-gateway.

Étape 2 : vérifier le numéro de port

Êtes-vous sûr que c’est 3306 ? MySQL a peut-être un autre port.

Sur l’hôte :

# Port réel de MySQL
sudo netstat -tlnp | grep mysqld

Étape 3 : tester la connectivité conteneur → hôte

Dans le conteneur :

# Tester le port
telnet host.docker.internal 3306

# Sans telnet, utiliser nc
nc -zv host.docker.internal 3306

Si le port ne répond pas, revérifiez pare-feu et configuration du service.

Problème 3 : Unknown host (host.docker.internal introuvable)

getaddrinfo ENOTFOUND host.docker.internal

Échec de résolution DNS — le conteneur ne connaît pas host.docker.internal.

Solution :

Ajoutez extra_hosts à la configuration :

services:
  app:
    extra_hosts:
      - "host.docker.internal:host-gateway"

Ou avec docker run :

docker run --add-host=host.docker.internal:host-gateway ...

Problème 4 : Échec d’authentification (Access denied)

Access denied for user 'root'@'172.17.0.2' (using password: YES)

MySQL est joignable, mais les droits utilisateur sont incorrects.

Solution :

-- Voir les droits actuels
SELECT user, host FROM mysql.user WHERE user='root';

-- S'il n'y a que root@localhost, créer root@% ou [email protected].%
CREATE USER 'root'@'%' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%';
FLUSH PRIVILEGES;

Problème 5 : Configuration multi-plateforme incohérente

Mac et Linux partagent le même docker-compose.yml — ça marche sur Mac, pas sur Linux.

Solution :

Unifiez avec host-gateway, valable partout :

services:
  app:
    extra_hosts:
      - "host.docker.internal:host-gateway"

Assurez-vous que Docker ≥ 20.10. Si quelqu’un a une version trop ancienne, incitez-le à mettre à jour.

Formule de dépannage rapide

En cas de problème de connexion, dans cet ordre :

  1. Service démarré ?systemctl status / brew services list
  2. Bonne adresse d’écoute ?netstat -tlnp, 0.0.0.0 ou 127.0.0.1 ?
  3. Conteneur configuré ? → vérifier extra_hosts ou --add-host
  4. DNS OK ?ping host.docker.internal dans le conteneur
  5. Port accessible ?telnet ou nc dans le conteneur
  6. Pare-feu actif ? → désactiver temporairement pour tester
  7. Droits accordés ? → utilisateur MySQL @localhost ou @% ?

Dans neuf cas sur dix, c’est l’un des trois premiers.

Cas pratiques

Assez de théorie — deux exemples concrets.

Cas 1 : application Spring Boot et MySQL sur l’hôte

Scénario : projet Spring Boot conteneurisé, base MySQL locale sur l’hôte.

Étape 1 : configurer Spring Boot

application.yml :

spring:
  datasource:
    # host.docker.internal pour joindre MySQL sur l'hôte
    url: jdbc:mysql://host.docker.internal:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: your_password
    driver-class-name: com.mysql.cj.jdbc.Driver

Étape 2 : Docker Compose

docker-compose.yml :

version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    extra_hosts:
      - "host.docker.internal:host-gateway"  # configuration clé
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://host.docker.internal:3306/mydb
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: your_password

Étape 3 : MySQL sur l’hôte

Éditez /etc/mysql/mysql.conf.d/mysqld.cnf :

[mysqld]
bind-address = 0.0.0.0

Redémarrez MySQL :

sudo systemctl restart mysql

Accordez les droits :

CREATE USER 'root'@'%' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%';
FLUSH PRIVILEGES;

Étape 4 : lancer et tester

docker-compose up --build

Si vous voyez HikariPool-1 - Start completed, la connexion à la base est établie.

Retour d’expérience :

Ma première config a donné Connection refused. Dépannage :

  1. MySQL démarré ? systemctl status mysql → oui
  2. Adresse d’écoute ? netstat -tlnp | grep 3306127.0.0.1:3306
  3. Modifié bind-address = 0.0.0.0, redémarré MySQL
  4. Relancé — connecté

Cas 2 : application Node.js et Redis sur l’hôte

Scénario : projet Node.js avec Redis en cache, Redis sur l’hôte en dev local.

Étape 1 : code Node.js

// redis-client.js
const redis = require('redis');

const client = redis.createClient({
  host: process.env.REDIS_HOST || 'host.docker.internal',
  port: process.env.REDIS_PORT || 6379,
  // Si Redis a un mot de passe
  password: process.env.REDIS_PASSWORD
});

client.on('connect', () => {
  console.log('Redis connected successfully');
});

client.on('error', (err) => {
  console.error('Redis error:', err);
});

module.exports = client;

Étape 2 : Docker Compose

docker-compose.yml :

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      NODE_ENV: development
      REDIS_HOST: host.docker.internal
      REDIS_PORT: 6379

Étape 3 : Redis sur l’hôte

Éditez /etc/redis/redis.conf ou /usr/local/etc/redis.conf :

# Trouvez bind
bind 127.0.0.1 ::1

# Changez en
bind 0.0.0.0

Si protected-mode yes :

protected-mode no  # OK en dev local, pas en production

Redémarrez Redis :

# Linux
sudo systemctl restart redis

# Mac
brew services restart redis

Étape 4 : vérification

docker-compose up

Redis connected successfully — c’est bon.

Multi-plateforme :

Avec variables d’environnement unifiées :

const REDIS_HOST = process.env.REDIS_HOST || (
  process.platform === 'linux' ? 'host.docker.internal' : 'host.docker.internal'
);

En fait, tout le monde peut utiliser host.docker.internal maintenant. Avec extra_hosts: ["host.docker.internal:host-gateway"] dans Docker Compose, Mac et Linux partagent la même config.

Cas 3 : environnement de dev complet

Modèle pratique — conteneur app, MySQL et Redis sur l’hôte :

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      # Base de données
      DB_HOST: host.docker.internal
      DB_PORT: 3306
      DB_NAME: myapp
      DB_USER: root
      DB_PASSWORD: your_password

      # Redis
      REDIS_HOST: host.docker.internal
      REDIS_PORT: 6379

      # Application
      NODE_ENV: development
      PORT: 8080
    volumes:
      - .:/app
      - /app/node_modules  # ne pas monter node_modules
    command: npm run dev  # hot reload en dev

Checklist de configuration sur l’hôte :

# MySQL
# Éditer /etc/mysql/mysql.conf.d/mysqld.cnf
bind-address = 0.0.0.0
# Redémarrer : sudo systemctl restart mysql

# Redis
# Éditer /etc/redis/redis.conf
bind 0.0.0.0
protected-mode no
# Redémarrer : sudo systemctl restart redis

# Pare-feu (si nécessaire)
sudo ufw allow from 172.17.0.0/16 to any port 3306
sudo ufw allow from 172.17.0.0/16 to any port 6379

Cette config fonctionne sur Mac et Linux — copier-coller et c’est parti.

Résumé

Trois points essentiels :

1. Comprendre le principe

Le conteneur a son propre réseau ; localhost dans le conteneur, c’est le conteneur, pas l’hôte. C’est l’isolation par espace de noms réseau — une conception Docker, pas un bug.

2. Choisir la bonne méthode

Selon votre environnement :

EnvironnementSolution recommandéeConfiguration
Mac/Windows (Docker Desktop)host.docker.internal directementAucune config supplémentaire
Linux (Docker Engine 20.10+)extra_hosts: host-gatewaydocker-compose ou —add-host
Équipe multi-plateformeextra_hosts: host-gatewayConfig unifiée
Ancien Linux172.17.0.1extra_hosts avec IP
Dernier recours--network=hostDev local uniquement, compromet l’isolation

3. Configurer les services

Le conteneur seul ne suffit pas — configurez aussi l’hôte :

  • Adresse d’écoute 0.0.0.0
  • Droits MySQL pour le segment Docker
  • Pare-feu autorisant le segment Docker

Arbre de décision rapide

Problème de connexion :

Impossible de joindre l'hôte ?

Mac/Windows ou Linux ?

Mac/Windows :
  → host.docker.internal directement
  → Sinon, vérifier la config des services sur l'hôte

Linux :
  → Docker ≥ 20.10 ?
      Oui → extra_hosts: host-gateway
      Non → extra_hosts: 172.17.0.1
  → Vérifier les services sur l'hôte
  → Vérifier le pare-feu

Tout essayé sans succès ?
  → Checklist de dépannage
  → Dernier recours : --network=host (dev local uniquement)

Pour finir

Le développement conteneurisé est pratique, mais le réseau recèle des pièges. Maîtriser host.docker.internal et host-gateway règle la plupart des cas.

Gardez cet article sous la main pour la prochaine panne de connexion. Partagez-le avec vos collègues qui galèrent encore sur ce sujet.

Si vous avez rencontré des problèmes réseau conteneur plus étranges, ou une meilleure solution, dites-le en commentaire — ça peut aider d’autres personnes.

Configuration complète pour accéder à l'hôte depuis un conteneur Docker

Résoudre les problèmes d'accès aux services de l'hôte avec host.docker.internal sur Mac, Windows et Linux

⏱️ Estimated time: 15 min

  1. 1

    Step 1: Comprendre la cause et la solution

    Cause : localhost ou 127.0.0.1 dans un conteneur ne joint jamais les services de l'hôte, car localhost pointe vers le conteneur lui-même, pas l'hôte — une configuration spéciale est nécessaire.

    Solution : utiliser host.docker.internal, un « domaine magique » qui se résout automatiquement vers l'IP de l'hôte.

    Support par plateforme :
    • Mac/Windows : support natif Docker Desktop, sans configuration supplémentaire
    • Linux : Docker 20.10+ requis, avec --add-host ou extra_hosts dans docker-compose
  2. 2

    Step 2: Configuration Mac/Windows

    Étapes Mac/Windows :

    1. Utiliser directement host.docker.internal :
    docker run -e DATABASE_URL=host.docker.internal:3306 my-app

    2. Remplacer localhost dans la configuration applicative :
    • Chaîne de connexion : host.docker.internal:3306
    • Variable d'environnement : DATABASE_HOST=host.docker.internal

    3. Vérifier la connexion :
    • Test dans le conteneur : docker exec -it container-name ping host.docker.internal
    • Test direct de la connexion applicative

    Note : Mac/Windows ne nécessitent aucune configuration supplémentaire, Docker Desktop gère tout automatiquement.
  3. 3

    Step 3: Configuration et dépannage Linux

    Méthodes Linux :

    1. Docker ≥ 20.10 (recommandé) :
    docker run --add-host=host.docker.internal:host-gateway my-app

    Ou dans docker-compose.yml :
    extra_hosts:
    - "host.docker.internal:host-gateway"

    2. Docker < 20.10 :
    docker run --add-host=host.docker.internal:172.17.0.1 my-app

    3. Checklist de dépannage complète :
    • Confirmer que le service tourne sur l'hôte (netstat -tuln | grep 3306)
    • Vérifier que le port est ouvert (telnet host.docker.internal 3306)
    • Remplacer localhost par host.docker.internal
    • Sur Linux, ajouter --add-host
    • Vérifier le pare-feu (iptables -L)
    • Confirmer que le service écoute sur 0.0.0.0 et non 127.0.0.1

FAQ

Pourquoi localhost dans un conteneur ne joint-il pas les services de l'hôte ?
L'isolation réseau des conteneurs en est la cause : chaque conteneur a son propre espace de noms réseau ; localhost dans le conteneur pointe vers le conteneur lui-même (127.0.0.1), pas vers l'hôte.

Comme deux maisons distinctes sur la même machine physique, chacune a sa propre adresse réseau. Quand le conteneur accède à localhost, il accède au réseau interne du conteneur, pas à celui de l'hôte.

Solution : utiliser host.docker.internal, un domaine spécial qui se résout automatiquement vers l'IP de l'hôte, permettant au conteneur d'accéder aux services de l'hôte.
host.docker.internal est-il supporté sur toutes les plateformes ?
Support par plateforme :

• Mac/Windows (Docker Desktop) : support natif, sans configuration
• Linux (Docker 20.10+) : configuration manuelle --add-host requise
• Linux (Docker < 20.10) : utiliser 172.17.0.1 comme IP de l'hôte

Pour les équipes multi-plateformes : unifier avec extra_hosts dans docker-compose pour que toutes les plateformes fonctionnent.
Que faire si host.docker.internal ne fonctionne toujours pas ?
Étapes de dépannage :

1. Vérifier que le service tourne sur l'hôte :
netstat -tuln | grep numéro_de_port

2. Confirmer l'adresse d'écoute du service :
Le service doit écouter sur 0.0.0.0, pas uniquement 127.0.0.1

3. Tester la connectivité réseau :
docker exec -it container-name ping host.docker.internal
docker exec -it container-name telnet host.docker.internal numéro_de_port

4. Vérifier les règles de pare-feu :
iptables -L (Linux)
Paramètres du pare-feu Windows

5. Dernier recours (environnement de dev uniquement) :
--network=host (compromet l'isolation du conteneur)
Faut-il utiliser host.docker.internal en production ?
Non recommandé. En production, il vaut mieux :

• Utiliser les noms de service (réseau Docker Compose) ou les noms de conteneurs
• Utiliser des adresses IP explicites
• Utiliser un mécanisme de découverte de services (Consul, etcd, etc.)

host.docker.internal sert surtout à :
• L'environnement de développement local
• Le débogage et les tests
• Accéder aux outils de dev sur l'hôte (base de données, Redis, etc.)

En production, host.docker.internal entraîne :
• Une dépendance à la configuration réseau de l'hôte
• Une réduction des avantages de la conteneurisation
• Une complexité opérationnelle accrue

14 min de lecture · Publié le: 17 déc. 2025 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog