零、核心疑惑

在使用 Docker Compose 时,“容器间通信通常把‘服务名’当作域名来用” (比如直接 ping nacos)。

但习惯了纯 docker run 命令的人会产生疑惑:

  1. 纯 Docker 里根本没有“服务名”概念,难道不应该用“容器名”吗?
  2. 如果在 Compose 里自定义了网络,且代码里坚持用“容器名”通信,行不行?

一、 纯 Docker 环境下的网络通信

在没有 Docker Compose 的情况下(纯靠 docker run),能不能用容器名通信,取决于使用了什么网络:

  • 默认 Bridge 网络(不推荐)
    • 场景:直接 docker run 不加任何网络参数。
    • 规则:Docker 内置 DNS 不支持通过容器名互相解析。只能通过 IP 访问,或者使用过时的 --link 参数。
  • 自定义网络(推荐)
    • 场景:先 docker network create my-net,启动时指定 --network my-net --name my_nacos
    • 规则:在自定义网络中,Docker DNS 会自动将 容器名(Container Name) 注册为域名。同网络下的其他容器直接访问 my_nacos:8848 即可通畅。

Docker 在自定义网络中内嵌了一个 DNS 服务器(固定 IP 为 127.0.0.11),当容器去 ping 容器名或服务名时,其实都是由这个内置 DNS 拦截并解析成真实容器 IP 的。

结论 1: 在纯 Docker 环境的自定义网络下,“容器名”就是域名。


二、 Docker Compose 下的“双保险”解析

当引入 docker-compose.yml 后,Compose 会在启动时自动创建一个自定义内部网络。在这个网络中,Docker 内部 DNS 会提供“双重解析”机制:

假设 docker-compose.yml 如下:

services:
  nacos-service:         #【服务名 Service Name】
    image: nacos/nacos-server
    container_name: my_nacos_container  #【容器名 Container Name】

在这个环境下,同一个 Compose 项目中的其他容器想要访问 Nacos:

  • 方式 A:访问 nacos-service(服务名) ➔ 通!
  • 方式 B:访问 my_nacos_container(容器名) ➔ 也通!

结论 2: 在 Compose 语境下,既可以用服务名,也可以用容器名,两者都会被解析到同一个容器 IP 上。


三、 既然都能通,为什么官方强烈推荐用“服务名”?

虽然在 Compose 中用自定义的“容器名”通信完全合法,但会带来严重的局限性

1. 丧失“水平扩展(Scale)”能力(最核心的区别)

  • 如果写死容器名:容器名在全局必须是唯一的。如果在配置里写死 container_name: my_nacos_container,那么这个服务永远只能单机运行。一旦执行扩展命令(如 docker compose up --scale nacos-service=3),会因为容器重名而报错。
  • 如果使用服务名:Compose 会自动生成带后缀的不重复容器名(如 项目名_nacos-service_1/2/3)。此时代码里依然请求的是 nacos-service,Docker 内部 DNS 会自动对这 3 个容器进行轮询(Round-Robin)负载均衡

2. 代码与部署解耦

  • 使用服务名,代码层面的配置(如 spring.datasource.url=jdbc:mysql://mysql-service:3306)不需要关心实际跑起来的容器到底叫什么名字,哪怕项目夹杂了目录前缀、随机后缀,都不影响通信。

四、 例外:什么时候更适合用“容器名”?

既然“服务名”这么好,那为什么 Docker Compose 还要保留 container_name 这个配置项呢?在以下特殊场景下,指定并使用“容器名”反而更优:

1. 外部独立容器或脚本的介入

假设有一个没有被编排进 Compose 的旧应用(单独 docker run 跑起来的),手动将它加入了当前 Compose 的网络中。或者需要编写外部的 Shell 脚本去定期备份数据库。

  • 如果没定义容器名,Compose 会生成类似 mall_nacos-service_1 这样带有动态后缀的名字,外部脚本很难精准定位。
  • 此时明确指定 container_name: my_nacos_container,外部独立容器通过固定的容器名去通信或执行命令(如 docker exec my_nacos_container ...),会更加直观、稳定且可控。

2. 明确的单例中间件

如果非常确定某个服务(比如本地开发测试用的 MySQL、Redis)永远只会跑一个实例,绝不存在集群扩展需求。此时指定一个简短的 container_name,会让终端敲 docker ps 或查看日志时更加清爽。


五、 总结

根据不同场景,总结最佳实践方案如下:

  1. 场景:企业级微服务部署、可能有集群扩展需求
    • 做法:在 YAML 中不要指定 container_name(让 Docker 自动生成),服务间调用统一只用 Service Name
  2. 场景:个人本地开发、明确永远只跑单节点(如单机 MySQL 测试)
    • 做法:可以在 YAML 中指定 container_namedocker ps 看得更顺眼,通信时用容器名或服务名皆可。
  3. 场景:有外部独立的 Docker 容器需要连入 Compose 网络
    • 做法:对于未被 Compose 管理的旧容器,它可能“认不出” Compose 注入的服务别名。此时让它通过明确的自定义容器名来直连,更加直观稳定。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐