DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

Linux Docker 容器无法解析 host.docker.internal

Linux Docker 容器访问 host.docker.internal 提示无法解析。为现有 Compose 服务添加 host-gateway 映射,重建后分别验证容器 hosts、系统解析与宿主机服务连接,并处理监听地址不匹配。

应用放进 Linux Docker 容器后,访问宿主机上的接口失败。地址里写的是 host.docker.internal,例如用 curl 请求时出现:

curl: (6) Could not resolve host: host.docker.internal

在 Docker Desktop 上能用的这个名字,换到原生 Linux Docker Engine 后不一定自动存在。对于普通桥接网络中的容器,可以通过 extra_hosts 把它映射到 Engine 提供的 host-gateway 地址。名称映射生效后,还要确认宿主机服务是否监听那个地址。

以下操作适用于原生 Linux 上以 rootful 模式运行的 Docker Engine 20.10 及以上版本,命令使用 Compose V2。示例服务名是 app,请替换为自己的服务名。Rootless、host 网络、Swarm,以及 WSL 中跨到 Windows 主机的访问路径,不直接套用这里的判断。

确认访问的是哪台宿主机

在运行 Compose 的终端查看当前连接:

docker context show
docker version
docker info

关注 Docker 的服务端版本和运行环境。如果客户端连接远程 Engine,这里的宿主机就是远程 Engine 所在的机器;在笔记本上启动的接口不会因为这个名字而变成远程容器的本地服务。若设置过 DOCKER_HOST 或命令行连接参数,也要一并核对,不能只凭当前 context 的名称判断。

host-gateway 的 Linux 支持从 Engine 20.10 引入。只更新 Compose 插件或 Docker 客户端,不能让旧服务端获得这项能力。如果创建容器时提示 host-gateway 不是有效 IP,先核对实际服务端版本,再按已有升级流程处理,别把这个字符串直接写进系统 hosts 文件。

Docker Desktop 自带 host.docker.internal 解析。在 Desktop 环境遇到同样报错,应先确认容器确实运行在预期的 Desktop Engine 上。下面的配置针对原生 Linux Engine,不把两种环境混在一起。

在容器里检查名称映射

宿主机和容器各有自己的 /etc/hosts。在宿主机文件里找不到这个名字,不能说明容器配置有问题;往宿主机文件里补一行,也不会自动写入已有容器。

docker compose exec app cat /etc/hosts
docker compose exec app getent hosts host.docker.internal

第一条查看容器实际收到的静态映射。第二条在提供 getent 的镜像中检查系统名称解析结果。如果提示命令不存在,只说明镜像没有这个工具,不是域名解析失败;不要为了排查临时改动精简的生产镜像,可以结合 hosts 内容和应用自身的连接日志判断。

不要只用 nslookup 验收。它向 DNS 服务器查询,而 extra_hosts 写入的是容器 hosts 文件,DNS 查询失败不能据此否定 hosts 映射。hosts 已有记录但应用仍报解析错误时,还要考虑应用是否使用自己的 DNS 解析器,以及实际请求是否进入了另一个容器。

给现有 Compose 服务添加映射

在原有 Compose 文件的目标服务下合并这段配置。保留已有的镜像、卷、网络、环境变量和其他配置;若已经有 extra_hosts,只添加缺少的条目,并处理同名的冲突记录。

services:
  app:
    # 保留该服务原有的 image/build、volumes、networks 等设置
    extra_hosts:
      - "host.docker.internal:host-gateway"

这是一段需要合并到现有服务中的配置,不是完整的独立 Compose 文件。extra_hosts 与 image、build 同级,作用于运行中的服务容器;不要误放进 build,后者对应构建时的网络设置。

host.docker.internal 是应用访问时使用的名字,host-gateway 是 Engine 在创建容器时解析的特殊值。默认情况下,它指向默认桥接网络的宿主机 IPv4 地址;如果默认桥接网络有 IPv6 地址,也可包含对应 IPv6 地址。管理员还可以在 daemon 配置中覆盖这个值。因此,不要把网上常见的 172.17.0.1 写成所有机器通用的答案,也不要直接拿 Compose 项目网络的网关替代它。

修改前保存 Compose 配置副本,并确认需要保留的数据位于卷或绑定挂载中;只存在于容器可写层的数据,重建后不会保留。安排允许该服务短暂中断的时间,然后检查配置并重建目标服务:

docker compose config -q
docker compose up -d --no-deps --force-recreate app

先确认第一条成功再运行第二条。原部署如果使用了 -f、-p、--env-file 或 profile,以上所有 Compose 命令都要沿用相同参数和环境,否则检查和更新的可能不是同一个项目。也应确认配置里没有夹带尚未准备发布的镜像或其他改动。

--no-deps 将这次操作限制在指定服务,不顺带启动依赖服务;--force-recreate 明确要求重建容器,使创建时的 hosts 配置重新生成。不要仅执行 docker compose restart:重启现有容器不会把 Compose 文件修改应用进去。这里也不需要删除卷或重建整个项目。

分别确认解析和 HTTP 连接

容器重新运行后,再查看 hosts 和解析结果:

docker compose exec app cat /etc/hosts
docker compose exec app getent hosts host.docker.internal

应当能看到名称对应实际地址。如果映射没有出现,先核对服务名、项目及正在使用的 Compose 文件,不要接着修改宿主机 DNS。若 hosts 有记录而 getent 无法解析,在包含该工具的镜像里还需检查名称服务配置;应用的自定义解析方式也可能绕过系统 hosts。

容器已有 curl 时,可以用明确的 HTTP 端口和只读检查路径验证连接。下面的 8080 与 /health 都是示例,请改成宿主机服务实际提供的值:

docker compose exec app curl --noproxy '*' \
  --connect-timeout 5 --max-time 10 \
  -sS -o /dev/null -w '%{http_code}\n' \
  http://host.docker.internal:8080/health

--noproxy '*' 让这一次请求绕过代理,避免请求被交给无法解析内部名称的代理服务器;它不会修改应用的长期代理设置。连接阶段最多等待 5 秒,整个请求最多 10 秒。这个例子适用于目标确实提供 HTTP 的情况,不能用它判断只开放 HTTPS 或非 HTTP 协议的服务。

状态码符合预期后,还需核对响应内容或应用日志,确认业务恢复。401、403、404 表明已经收到 HTTP 响应,接下来要查认证、权限或路径。输出 000 时结合 curl 的错误文字判断,不能当成服务器返回的状态码。

名字能解析,连接仍被拒绝

如果报错从“无法解析”变成了 Connection refused 或超时,名称映射这一层可能已经修好,不应继续反复添加 hosts 条目。

普通桥接容器有自己的网络空间。宿主机服务若只监听 127.0.0.1,连接宿主机桥接地址不会自动转到那个监听端口;宿主机上访问 localhost 成功,也不能证明容器能连接它。

此时要核对宿主机服务的监听地址、端口和防火墙规则。根据该服务支持的配置,把监听范围限定到容器可达且确有需要的宿主机地址,并只允许需要的容器网段访问。不要为了省事直接绑定所有公网接口或关闭防火墙;数据库和管理接口还要保留原有认证。

连接被拒绝也可能是端口错误、服务未启动或防火墙主动拒绝;超时则还需检查路由和过滤规则。仅凭一种错误文字,不能确定唯一原因。若目标服务其实在另一个容器里,通常应让两者加入同一用户自定义网络,通过服务名和容器内端口通信,不必绕到宿主机。

撤销本次配置

如果本次改动引入异常,恢复保存的 Compose 配置,沿用同一项目参数重新执行配置检查和目标服务重建:

docker compose config -q
docker compose up -d --no-deps --force-recreate app

同样要在配置检查成功后再重建。如果还调整了宿主机服务的监听地址或防火墙,分别恢复对应的原配置,并检查原有客户端能否正常使用。撤销映射后,依赖该名称的新连接也会受影响;回退的目标是恢复改动前的服务状态,不是让依赖新映射的访问继续有效。