这篇记录一个很容易踩的 Docker DNS 坑:宿主机 DNS 已经从旧地址切到新地址,但已经存在的 Docker 容器里仍然保留旧的 nameserver,或者 Docker internal DNS 仍然使用旧的上游 DNS,导致容器内域名解析失败。
这类问题经常表现为:宿主机上 curl、nslookup 都正常,但容器内服务突然访问不了域名、WebHook 失败、推送通知失效、服务健康检查异常,或者 docker pull 走旧代理失败。
现象
一次 DNS / 网关迁移后,宿主机 /etc/resolv.conf 已经改为新的 DNS:
nameserver 192.0.2.12宿主机解析和访问业务域名都正常:
nslookup notify.example.com
curl -kfsS --connect-timeout 5 -m 10 https://notify.example.com/v1/health健康接口返回正常:
{"healthy":true}但某些 Docker 容器里的业务仍然异常。进一步检查发现,容器内 DNS 还是旧地址。
普通容器或 network_mode: host 容器里可能直接看到旧 DNS:
nameserver 192.0.2.6容器内解析失败:
;; connection timed out; no servers could be reachedbridge 网络容器里通常看到的是 Docker 内置 DNS:
nameserver 127.0.0.11
options ndots:0
# ExtServers: [host(192.0.2.6)]也就是说,容器里虽然写的是 127.0.0.11,但 Docker 内置 DNS 的上游仍然是旧的 192.0.2.6。
应用日志里可能出现类似错误:
gaierror [Errno -3] Temporary failure in name resolution或者:
lookup api.example.com: no such host如果 Docker daemon 也配置过旧代理,还可能导致 docker pull 失败,例如:
proxyconnect tcp: dial tcp 192.0.2.6:9666: connect: no route to host根因
Docker 容器的 DNS 配置不是宿主机 /etc/resolv.conf 的实时映射。
Docker 在创建容器时,会根据当时宿主机的 DNS 配置生成容器自己的 /etc/resolv.conf:
network_mode: host的容器,常见表现是容器里直接写入当时的nameserver。- bridge 网络容器,容器里通常写
nameserver 127.0.0.11,这是 Docker internal DNS;但它背后的上游 DNS 仍然来自容器创建时 Docker 看到的宿主机 DNS。
所以当宿主机 DNS 从旧地址改成新地址后,已经存在的旧容器不会自动刷新 DNS。
这也是为什么:
- 宿主机解析正常;
- 新创建的临时容器解析正常;
- 旧的业务容器仍然解析失败。
快速判断是不是这个问题
先看宿主机当前 DNS:
cat /etc/resolv.conf再看目标容器内 DNS:
docker exec <container> cat /etc/resolv.conf如果业务容器内仍出现旧 DNS 地址,或者 bridge 容器里出现类似:
# ExtServers: [host(192.0.2.6)]就说明它还没刷新。
直接在容器内做解析测试:
docker exec <container> sh -c 'nslookup notify.example.com 2>&1 || true'如果容器没有 nslookup,可以用 Python 测试:
docker exec <container> python - <<'PY'
import socket
try:
print(socket.getaddrinfo('notify.example.com', 443)[0][4][0])
except Exception as e:
print(type(e).__name__, e)
PY也可以对比新创建的临时容器。下面用已存在的镜像举例,避免现场临时拉镜像时又被旧 DNS / 旧代理影响:
docker run --rm --network bridge --entrypoint sh <existing-image>:<tag> -c 'cat /etc/resolv.conf; nslookup notify.example.com 2>&1 | head -20'如果新容器已经拿到新 DNS,而旧容器还是旧 DNS,基本就能确认是容器 DNS 没刷新。
修复方法
推荐:强制重建受影响容器
进入对应 compose 项目目录:
cd /path/to/compose-project
docker compose up -d --force-recreate <service>例如:
docker compose up -d --force-recreate app--force-recreate 会重新创建容器,Docker 会按当前宿主机 DNS 重新生成容器 /etc/resolv.conf。
这个方式比直接 down 温和一些,通常不会先拆掉整个 compose 项目的网络。
也可以:down 后再 up
如果你确认 compose 项目内的数据都挂载在 volume 或 bind mount 上,也可以:
docker compose down
docker compose up -d这同样会删除旧容器并重新创建,因此一般也会刷新 DNS。
注意:down 会停止并删除该 compose 项目的容器和默认 network。如果某个服务把数据放在容器内部而不是挂载目录里,删除容器会丢数据。因此生产服务优先用 up -d --force-recreate <service>。
哪些命令不会可靠刷新 DNS
下面这些通常只是重启旧容器,不会重新生成容器 DNS:
docker restart <container>
docker compose restart <service>重启 Docker 服务或重启整台主机也不建议当成可靠修复方法。它可能让部分容器重新启动,但不等价于重新创建容器,不能保证旧容器的 DNS 配置刷新。
修复后验证
检查容器 DNS 是否变成新地址:
docker exec <container> grep nameserver /etc/resolv.confbridge 网络容器还可以检查 Docker internal DNS 的上游提示:
docker exec <container> grep -A5 ExtServers /etc/resolv.conf || true期望看到新 DNS,例如:
nameserver 192.0.2.12
# ExtServers: [host(192.0.2.12)]检查域名解析:
docker exec <container> sh -c 'nslookup notify.example.com 2>&1 | head -20'检查业务健康接口:
curl -kfsS --connect-timeout 5 -m 10 https://notify.example.com/v1/health检查容器状态:
docker ps --format 'table {{.Names}}\t{{.Status}}' | grep -E 'NAMES|<service-name>'如果是 Web/API 服务,也可以从宿主机请求本地监听端口:
curl -fsS --connect-timeout 5 -m 10 http://127.0.0.1:<port>/health别忘了 Docker daemon 的旧代理
如果之前给 Docker daemon 配过代理,DNS / 网关迁移后还要检查代理配置是否仍指向旧地址。常见位置包括:
/etc/docker/daemon.json/etc/systemd/system/docker.service.d/*.conf/etc/default/docker/etc/conf.d/docker/etc/profile.d/*.sh/root/.docker/config.json
排查命令:
grep -RInE 'HTTP_PROXY|HTTPS_PROXY|NO_PROXY|proxy|socks5|http://' /etc/docker /etc/systemd/system/docker.service.d /etc/default /etc/conf.d /etc/profile.d /root/.docker 2>/dev/null如果确认新代理地址和端口,应把旧地址改掉;如果不再需要代理,则删除代理配置。改完后再重启 Docker 服务。
注意:重启 Docker 服务影响面比重建单个容器大,生产环境先确认有哪些容器会被影响。
经验总结
迁移 DNS / 网关后,Docker 环境建议按这个顺序检查:
- 宿主机
/etc/resolv.conf是否已经变成新 DNS。 - 业务容器
/etc/resolv.conf是否仍保留旧 DNS。 - bridge 容器的
127.0.0.11上游ExtServers是否仍是旧 DNS。 - 容器内能否解析关键域名。
- 受影响服务是否需要
docker compose up -d --force-recreate <service>。 - Docker daemon 是否还残留旧代理配置。
关键结论:改宿主机 DNS 后,旧容器不一定自动更新;要刷新容器 DNS,最可靠的是重建容器,而不是普通 restart。