Cambiar tema

Acceso del contenedor Docker al host: guía completa de host.docker.internal

Easton editorial illustration: observability control panel

Un viernes a las tres de la tarde miraba el terminal: Connection refused.

La verdad es que me desesperé. MySQL local funcionaba bien, Navicat conectaba, la línea de comandos también, pero la app en el contenedor no. Revisé tres veces la cadena de conexión: localhost:3306, correcta. Usuario y contraseña también. ¿Dónde estaba el problema?

Al final descubrí que estaba en una sola palabra: localhost.

Si te ha pasado lo mismo — usar localhost o 127.0.0.1 dentro de un contenedor Docker para conectar con un servicio del host y que siempre falle — este artículo es para ti. Te explico de forma directa por qué localhost dentro del contenedor no es el localhost que imaginas, y cómo resolverlo con el dominio mágico host.docker.internal.

En este artículo aprenderás:

  • La lógica real del aislamiento de red del contenedor (sin jerga innecesaria)
  • La configuración correcta en Mac, Windows y Linux
  • Una lista práctica de diagnóstico para la próxima vez

¿Por qué localhost no funciona?

Respuesta corta: el contenedor tiene su propio mundo de red.

Suena abstracto. Piensa en el contenedor como una casita independiente con su propio número, buzón y todo lo demás. Cuando dentro del contenedor dices «localhost» o llamas a 127.0.0.1, buscas esa casita, no la máquina anfitriona de fuera.

En concreto:

  • En el host, localhost apunta al propio host
  • En el contenedor, localhost apunta al propio contenedor
  • Son dos localhost distintos

La primera vez que lo entendí también me sorprendió. MySQL corría perfectamente en mi ordenador; ¿por qué el contenedor no conectaba? Por eso: el contenedor busca MySQL en su propio mundo y no lo encuentra.

Mecanismo de aislamiento de red del contenedor

Docker crea un espacio de nombres de red independiente para cada contenedor (no te asustes con el término). En la práctica:

Cada contenedor tiene su propia interfaz de red, su IP y su tabla de enrutamiento. Como dos pisos en el mismo edificio: cada uno con su Wi‑Fi, sin interferencias.

Contenedor y host se conectan mediante un puente virtual llamado docker0. La IP del contenedor suele ser 172.17.0.x; desde el contenedor, el host se ve como 172.17.0.1 (la puerta de enlace del puente).

Si dentro del contenedor accedes a localhost, llegas al 127.0.0.1 del contenedor, no al del host. Por eso no conecta con MySQL del host.

Un error típico:

Error: connect ECONNREFUSED 127.0.0.1:3306

O:

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

Son el clásico fallo de «usar localhost en el contenedor para llegar al host».

¿Qué es host.docker.internal?

Si localhost no sirve, ¿cómo accede el contenedor al host?

Docker ofrece una solución elegante: host.docker.internal. Es un dominio especial que se resuelve a la IP del host. Piénsalo como el apodo del host: da igual cuál sea su IP real, con ese nombre lo encuentras.

Por ejemplo, si MySQL escucha en el puerto 3306 del host, desde el contenedor conectas así:

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

No importa si el host es 192.168.1.100 o 10.0.0.5, ni si cambias de red: host.docker.internal apunta a la dirección correcta.

¿Cómodo, verdad?

Versiones y soporte por plataforma

Aquí hay un matiz importante.

Usuarios de Mac y Windows (Docker Desktop)

Con Docker Desktop (la versión con interfaz gráfica), desde la versión 18.03 (marzo de 2018) host.docker.internal viene soportado de forma nativa. Listo para usar, sin configuración extra.

En el código escribes directamente host.docker.internal:

const mysql = require('mysql2');
const connection = mysql.createConnection({
  host: 'host.docker.internal',  // Así de simple
  port: 3306,
  user: 'root',
  password: 'your_password'
});

Usuarios de Linux (Docker Engine)

En Linux la cosa es distinta. Docker corre directamente sobre el sistema, sin la capa de máquina virtual de Mac/Windows, y host.docker.internal no existe por defecto.

Desde Docker Engine 20.10 (diciembre de 2020) puedes habilitarlo con configuración. Cómo hacerlo lo vemos en la siguiente sección.

Si tu versión es más antigua, alternativas:

  • Usar 172.17.0.1 (IP de puerta de enlace del puente por defecto)
  • Usar la IP real del host en la red Docker
  • Usar docker.for.mac.host.internal (solo en versiones antiguas de Mac)

Configuración en las tres plataformas

Aquí va lo práctico: configuraciones que puedes copiar.

Mac/Windows (Docker Desktop)

El caso más sencillo.

Método 1: directamente en el código

Sin configuración extra; en el código usa host.docker.internal:

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

Método 2: declaración explícita (opcional)

Aunque no hace falta, puedes añadir extra_hosts:

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

host-gateway es la sintaxis nueva de Docker 20.10+ y significa «dirección de puerta de enlace del host».

Con docker run:

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

Linux (Docker Engine)

En Linux hay un paso manual más.

Método 1: recomendado — host-gateway

El enfoque más universal, válido en Docker 20.10+ en todas las plataformas:

# docker-compose.yml
version: '3'
services:
  app:
    image: myapp:latest
    extra_hosts:
      - "host.docker.internal:host-gateway"  # Configuración clave
    environment:
      - DB_HOST=host.docker.internal
      - DB_PORT=3306

Con docker run:

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

Ventaja: multiplataforma — la misma configuración en Mac, Windows y Linux.

Método 2: alternativa — IP del puente Docker

Si host-gateway no está disponible (Docker muy antiguo), usa la puerta de enlace del puente por defecto:

version: '3'
services:
  app:
    image: myapp:latest
    extra_hosts:
      - "host.docker.internal:172.17.0.1"  # Puerta de enlace por defecto
    environment:
      - DB_HOST=host.docker.internal

172.17.0.1 es la puerta de enlace de la red bridge de Docker. En casi todos los casos es correcta, salvo que hayas cambiado la red por defecto.

Método 3: último recurso — modo host

Si nada más funciona:

docker run -d \
  --network=host \
  -e DB_HOST=localhost \  # Aquí sí puedes usar localhost
  myapp:latest

O en docker-compose:

version: '3'
services:
  app:
    image: myapp:latest
    network_mode: "host"  # Red del host
    environment:
      - DB_HOST=localhost  # localhost real

Ventajas: directo; el contenedor usa la pila de red del host y localhost es localhost de verdad.

Inconvenientes:

  • Rompe el aislamiento de red del contenedor
  • Contenedor y host comparten puertos (posibles conflictos, p. ej. 8080)
  • Solo en Linux; Mac/Windows no lo soportan
  • No recomendado en producción; solo desarrollo local

Configuración multiplataforma (muy recomendada)

Si en el equipo hay Mac y Linux, o despliegas en entornos distintos, usa esto:

# docker-compose.yml
version: '3'
services:
  app:
    image: myapp:latest
    extra_hosts:
      - "host.docker.internal:host-gateway"  # Todas las plataformas lo entienden
    environment:
      - DB_HOST=host.docker.internal
      - DB_PORT=3306
      - DB_USER=root
      - DB_PASSWORD=your_password

Funciona en Docker 20.10+ (finales de 2020) en todas las plataformas. Si tu Docker es anterior… en serio, toca actualizar.

Puntos clave de configuración en el host

Configurar el contenedor no basta.

El servicio en el host también debe estar bien; mucha gente lo olvida.

El servicio debe escuchar en la dirección correcta

Es el fallo más habitual.

Muchos servicios escuchan solo en 127.0.0.1, es decir, solo conexiones locales. Un contenedor Docker no cuenta como «local»; las peticiones desde el puente Docker se rechazan.

Hay que escuchar en 0.0.0.0: «aceptar conexiones en todas las interfaces».

Configuración de MySQL

Archivo de configuración, normalmente en:

  • 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

Modifica bind-address:

[mysqld]
# Antes podía ser:
# bind-address = 127.0.0.1

# Cámbialo a:
bind-address = 0.0.0.0

Reinicia MySQL:

# Linux
sudo systemctl restart mysql

# Mac
brew services restart mysql

# Windows
# Reinicia el servicio MySQL desde el administrador de servicios

Configuración de Redis

Edita redis.conf (suele estar en /etc/redis/redis.conf o /usr/local/etc/redis.conf):

# Busca esta línea
bind 127.0.0.1 -::1

# Cámbiala a
bind 0.0.0.0

Reinicia Redis:

# Linux
sudo systemctl restart redis

# Mac
brew services restart redis

Configuración de PostgreSQL

Edita postgresql.conf:

listen_addresses = '*'  # Escuchar en todas las direcciones

Y modifica pg_hba.conf para permitir la red Docker:

# Añade esta línea para la red 172.17.0.0/16
host    all             all             172.17.0.0/16           md5

Permisos de usuario (MySQL)

Aunque MySQL escuche en 0.0.0.0, siguen los permisos de usuario.

En MySQL los permisos son «usuario@host de origen». root@localhost y root@% no son lo mismo.

Si el usuario solo puede conectar desde localhost, el contenedor seguirá fallando. Concede acceso desde la red Docker:

-- Opción 1: desde cualquier host (simple, menos seguro)
GRANT ALL PRIVILEGES ON *.* TO 'your_user'@'%' IDENTIFIED BY 'your_password';

-- Opción 2: solo red Docker (más seguro)
GRANT ALL PRIVILEGES ON *.* TO 'your_user'@'172.17.0.%' IDENTIFIED BY 'your_password';

-- Aplicar permisos
FLUSH PRIVILEGES;

En MySQL 8.0+ la sintaxis cambia un poco:

-- Crear usuario
CREATE USER 'your_user'@'%' IDENTIFIED BY 'your_password';

-- Conceder permisos
GRANT ALL PRIVILEGES ON *.* TO 'your_user'@'%';

FLUSH PRIVILEGES;

Configuración del firewall

En algunos sistemas el firewall bloquea que los contenedores accedan a servicios del host.

Comprobar estado del firewall:

# Linux (ufw)
sudo ufw status

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

Permitir la red Docker (ejemplo puerto 3306 de MySQL):

# 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

Recomendaciones de seguridad

Escuchar en 0.0.0.0 tiene riesgo: el servicio queda expuesto a otras máquinas de la red.

En producción:

  1. Solo una interfaz concreta: si sabes qué interfaz usa Docker, escucha solo ahí
    bind-address = 172.17.0.1
    

2. **Firewall**: permite solo la red Docker y bloquea el resto

3. **Base de datos en contenedor**: no corras la BD en el host; levántala con Docker Compose en la misma red que la app

**En desarrollo local**:

En local, escuchar en `0.0.0.0` suele ser aceptable. Tu portátil no es un servidor expuesto a Internet. No te agobies.

## Lista de diagnóstico de problemas frecuentes

¿Problemas de conexión? Sigue esta lista paso a paso.

### Problema 1: Connection refused (conexión rechazada)

El error más común:

Error: connect ECONNREFUSED host.docker.internal:3306


O:

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


**Causas posibles y comprobaciones**:

**Paso 1: ¿el servicio corre en el host?**

En el host:

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

# ¿Escucha el puerto?
netstat -an | grep 3306
# o
lsof -i :3306

Si no está activo, arranca el servicio.

Paso 2: dirección de escucha

En el host:

# Dónde escucha MySQL
sudo netstat -tlnp | grep 3306

La salida debería ser similar a:

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

Mira la tercera columna. 0.0.0.0:3306 = todas las interfaces, bien. 127.0.0.1:3306 = solo local, el contenedor no entra.

Solución: en la sección de configuración del host, pon bind-address = 0.0.0.0.

Paso 3: firewall

Prueba desactivarlo temporalmente:

# Linux (ufw)
sudo ufw disable

# Linux (firewalld)
sudo systemctl stop firewalld

# Mac
# Preferencias del sistema → Seguridad → Firewall → Desactivar

Si al desactivarlo conecta, configura reglas como arriba y vuelve a activar el firewall.

Problema 2: Connection timeout (tiempo de espera agotado)

Error: connect ETIMEDOUT host.docker.internal:3306

El timeout suele ser más difícil: el paquete sale pero no vuelve.

Comprobaciones:

Paso 1: ¿se resuelve host.docker.internal?

Dentro del contenedor:

# Entrar al contenedor
docker exec -it your_container sh

# Probar
ping host.docker.internal

Si falla o dice «unknown host», host.docker.internal no está bien configurado.

En Linux: confirma extra_hosts o --add-host=host.docker.internal:host-gateway en docker-compose o docker run.

Paso 2: ¿puerto correcto?

¿Seguro que es 3306? ¿MySQL usa otro puerto?

En el host:

# Puerto real de MySQL
sudo netstat -tlnp | grep mysqld

Paso 3: conectividad contenedor → host

Dentro del contenedor:

# Probar puerto
telnet host.docker.internal 3306

# Sin telnet, usa nc
nc -zv host.docker.internal 3306

Si el puerto no responde, revisa firewall y configuración del servicio.

Problema 3: Unknown host (no resuelve host.docker.internal)

getaddrinfo ENOTFOUND host.docker.internal

Fallo de DNS: el contenedor no conoce ese dominio.

Solución:

Añade extra_hosts:

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

O en docker run:

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

Problema 4: Error de autenticación (Access denied)

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

Ya conecta con MySQL, pero los permisos no cuadran.

Solución:

Concede permisos al usuario:

-- Ver usuarios actuales
SELECT user, host FROM mysql.user WHERE user='root';

-- Si solo existe root@localhost, crea root@% o [email protected].%
CREATE USER 'root'@'%' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%';
FLUSH PRIVILEGES;

Problema 5: Configuración distinta entre plataformas

Mismo docker-compose.yml: en Mac funciona, en Linux no.

Solución:

Unifica con host-gateway:

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

Docker ≥20.10. Si alguien del equipo tiene una versión antigua, que actualice.

Regla mnemotécnica rápida

Orden de comprobación:

  1. ¿Servicio activo?systemctl status / brew services list
  2. ¿Escucha bien?netstat -tlnp, ¿0.0.0.0 o 127.0.0.1?
  3. ¿Contenedor configurado?extra_hosts o --add-host
  4. ¿DNS OK?ping host.docker.internal dentro del contenedor
  5. ¿Puerto abierto?telnet o nc desde el contenedor
  6. ¿Firewall? → desactivar temporalmente para probar
  7. ¿Permisos MySQL? → usuario @localhost vs @%

En nueve de diez casos es uno de los tres primeros.

Casos prácticos

Teoría aparte, dos ejemplos reales.

Caso 1: Spring Boot conectando a MySQL en el host

Escenario: proyecto Spring Boot en Docker, MySQL local en el host.

Paso 1: configurar Spring Boot

application.yml:

spring:
  datasource:
    # MySQL del host con host.docker.internal
    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

Paso 2: Docker Compose

docker-compose.yml:

version: '3.8'

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

Paso 3: MySQL en el host

Edita /etc/mysql/mysql.conf.d/mysqld.cnf:

[mysqld]
bind-address = 0.0.0.0

Reinicia MySQL:

sudo systemctl restart mysql

Concede permisos:

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

Paso 4: arrancar y probar

docker-compose up --build

Si ves algo como HikariPool-1 - Start completed, la base de datos conectó.

Diagnóstico real:

La primera vez tuve Connection refused:

  1. ¿MySQL activo? systemctl status mysql → sí
  2. ¿Dónde escucha? netstat -tlnp | grep 3306127.0.0.1:3306
  3. Cambié bind-address = 0.0.0.0, reinicié MySQL
  4. Volví a ejecutar → conectó

Caso 2: Node.js con Redis en el host

Escenario: proyecto Node.js con Redis como caché; en desarrollo Redis corre en el host.

Paso 1: código 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 tiene contraseña
  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;

Paso 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

Paso 3: Redis en el host

Edita /etc/redis/redis.conf o /usr/local/etc/redis.conf:

# Línea bind
bind 127.0.0.1 ::1

# Cámbiala a
bind 0.0.0.0

Si protected-mode yes:

protected-mode no  # OK en desarrollo local; no en producción

Reinicia Redis:

# Linux
sudo systemctl restart redis

# Mac
brew services restart redis

Paso 4: verificar

docker-compose up

Si aparece Redis connected successfully, listo.

Multiplataforma:

Con variables de entorno unificadas:

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

Espera — ahora todos pueden usar host.docker.internal. Solo hace falta extra_hosts: ["host.docker.internal:host-gateway"] en Compose; Mac y Linux comparten la misma configuración.

Caso 3: entorno de desarrollo completo

Plantilla útil: app en contenedor, MySQL y Redis en el host:

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      # Base de datos
      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

      # App
      NODE_ENV: development
      PORT: 8080
    volumes:
      - .:/app
      - /app/node_modules  # No montar node_modules
    command: npm run dev  # Recarga en caliente

Checklist en el host:

# MySQL
# Editar /etc/mysql/mysql.conf.d/mysqld.cnf
bind-address = 0.0.0.0
# Reinicio: sudo systemctl restart mysql

# Redis
# Editar /etc/redis/redis.conf
bind 0.0.0.0
protected-mode no
# Reinicio: sudo systemctl restart redis

# Firewall (si hace falta)
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

Esta configuración sirve en Mac y Linux; copia y pega.

Resumen

En tres ideas:

1. Entender el principio

El contenedor tiene su propia red; localhost dentro del contenedor es el contenedor, no el host. Es aislamiento por espacios de nombres de red, diseño de Docker, no un bug.

2. Elegir el método

Según tu entorno:

EntornoEnfoque recomendadoConfiguración
Mac/Windows (Docker Desktop)Usar host.docker.internal directamenteSin configuración extra
Linux (Docker Engine 20.10+)extra_hosts: host-gatewaydocker-compose o —add-host
Equipo multiplataformaextra_hosts: host-gatewayUna sola configuración
Linux antiguo172.17.0.1extra_hosts con IP fija
Último recurso--network=hostSolo desarrollo local; rompe aislamiento

3. Configurar bien el host

El contenedor no basta; en el host:

  • Escuchar en 0.0.0.0
  • Permisos MySQL para la red Docker
  • Firewall que permita la red Docker

Árbol de decisión rápido

Si no conecta al host:

¿No conecta al servicio del host?

¿Mac/Windows o Linux?

Mac/Windows:
  → Usa host.docker.internal
  → Si falla, revisa configuración del servicio en el host

Linux:
  → ¿Docker ≥20.10?
      Sí → extra_hosts: host-gateway
      No → extra_hosts: 172.17.0.1
  → Revisa servicio en el host
  → Revisa firewall

¿Sigue fallando?
  → Lista de diagnóstico paso a paso
  → Último recurso: --network=host (solo desarrollo local)

Para cerrar

Desarrollar con contenedores es cómodo, pero la red tiene trampas. Con host.docker.internal y host-gateway resuelves la mayoría.

Guarda este artículo para la próxima vez. Si alguien del equipo sufre lo mismo, compártelo.

Si has visto algún caso raro de red en contenedores o tienes otra solución, cuéntalo en los comentarios; puede ayudar a más gente.

Flujo completo de configuración para que un contenedor Docker acceda al host

Usa host.docker.internal para resolver el acceso del contenedor a servicios del host en Mac, Windows y Linux

⏱️ Estimated time: 15 min

  1. 1

    Step 1: Entender la causa y la solución

    Causa raíz: conectar al host con localhost o 127.0.0.1 desde el contenedor siempre falla, porque localhost dentro del contenedor apunta al propio contenedor, no al host; hace falta una configuración especial.

    Solución: usa el dominio mágico host.docker.internal, que se resuelve automáticamente a la IP del host.

    Soporte por plataforma:
    • Mac/Windows: Docker Desktop lo soporta de forma nativa, sin configuración extra
    • Linux: requiere Docker 20.10+, con --add-host o extra_hosts en docker-compose
  2. 2

    Step 2: Configuración en Mac/Windows

    Pasos en Mac/Windows:

    1. Usa host.docker.internal directamente:
    docker run -e DATABASE_URL=host.docker.internal:3306 my-app

    2. Sustituye localhost en la configuración de la app:
    • Cadena de conexión: host.docker.internal:3306
    • Variable de entorno: DATABASE_HOST=host.docker.internal

    3. Verifica la conexión:
    • Prueba dentro del contenedor: docker exec -it container-name ping host.docker.internal
    • Prueba la conexión de la aplicación

    Nota: en Mac/Windows no hace falta configuración extra; Docker Desktop lo gestiona automáticamente.
  3. 3

    Step 3: Configuración y diagnóstico en Linux

    Configuración en Linux:

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

    O en 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. Lista de comprobación:
    • Confirma que el servicio corre en el host (netstat -tuln | grep 3306)
    • Comprueba que el puerto está abierto (telnet host.docker.internal 3306)
    • Usa host.docker.internal en lugar de localhost
    • En Linux añade --add-host
    • Revisa el firewall (iptables -L)
    • Confirma que el servicio escucha en 0.0.0.0 y no solo en 127.0.0.1

FAQ

¿Por qué localhost desde el contenedor no conecta con servicios del host?
El aislamiento de red del contenedor: cada contenedor tiene su propio espacio de nombres de red; localhost dentro del contenedor apunta al propio contenedor (127.0.0.1), no al host.

Es como dos casas independientes en la misma máquina física, cada una con su dirección de red. Al acceder a localhost, el contenedor usa su red interna, no la del host.

Solución: usa el dominio especial host.docker.internal, que se resuelve a la IP del host y permite acceder a servicios que corren en él.
¿host.docker.internal está soportado en todas las plataformas?
Soporte por plataforma:

• Mac/Windows (Docker Desktop): soporte nativo, sin configuración
• Linux (Docker 20.10+): hay que configurar --add-host manualmente
• Linux (Docker <20.10): usa 172.17.0.1 como IP del host

Para equipos multiplataforma: unifica con extra_hosts en docker-compose para que funcione en todas las plataformas.
¿Qué hacer si sigue sin conectar tras configurar host.docker.internal?
Pasos de diagnóstico:

1. Comprueba que el servicio corre en el host:
netstat -tuln | grep <puerto>

2. Confirma la dirección de escucha:
el servicio debe escuchar en 0.0.0.0, no solo en 127.0.0.1

3. Prueba la conectividad:
docker exec -it container-name ping host.docker.internal
docker exec -it container-name telnet host.docker.internal <puerto>

4. Revisa el firewall:
iptables -L (Linux)
Configuración del firewall de Windows

5. Último recurso (solo desarrollo):
--network=host (rompe el aislamiento del contenedor)
¿Debería usarse host.docker.internal en producción?
No se recomienda. En producción conviene:

• Usar nombres de servicio (red Docker Compose) o nombres de contenedor
• Usar direcciones IP explícitas
• Usar descubrimiento de servicios (Consul, etcd, etc.)

host.docker.internal sirve sobre todo para:
• Entorno de desarrollo local
• Depuración y pruebas
• Acceder a herramientas de desarrollo en el host (base de datos, Redis)

En producción implica:
• Depender de la red del host
• Reducir las ventajas de la contenedorización
• Aumentar la complejidad operativa

13 min de lectura · Publicado el: 17 dic 2025 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog