1
0
0

03-Docker 网络与 Compose:容器怎么互相找到对方

2026-10-22
2026-10-22

📝 笔记摘要:Docker 网络与 Compose,容器互访与多容器编排

起两个容器,一个跑数据库一个跑应用,结果应用连不上数据库——这个坑几乎每个人都踩过。根因不是配置写错,是 Docker 有两套网络模型,行为不一样。

再往后是另一个问题:服务一多,docker run 命令长得没法看,参数互相依赖还得记住启动顺序。Compose 就是来解决这两件事的。这篇把网络讲透,再把 Compose 现在的正确用法过一遍。

Docker 系列 共 4 篇,这是第 3 篇
  01-Docker 基础 安装、概念与容器操作
  02-Docker 镜像与数据卷 分层结构、挂载与 Dockerfile
  03-Docker 网络与 Compose 容器互访与多容器编排 ← 本篇
  04 Docker 常见问题排查 守护进程、权限与镜像拉取

1. 先把网卡看清楚

ip addr

装完 Docker 之后机器上会多出几张虚拟网卡,每一张对应一个网络:

lo 回环 本机自己跟自己通信,127.0.0.1
eth0 物理网卡 云服务器的外网地址走这张
docker0 默认网桥 Docker 自动创建的,所有默认网络的容器都插在它上面
br-xxxx 自定义网桥 你用 docker network create 建的
容器里看自己的 IP
  docker exec -it 容器名 ip addr
  精简镜像里没有 ip 命令,装一下:
  Debian 系 apt-get update && apt-get install -y iproute2
  Alpine 系 apk add --no-cache iproute2
  实在不行就装 net-tools 用 ifconfig 看

2. 默认网桥:能通,但只能用 IP

默认给容器分配的网络叫 docker0,是桥接模式。容器有自己的 IP,宿主机上的服务通过端口映射访问它们,容器之间也能互相通信——但只能用 IP 访问。

默认网桥没有内嵌 DNS
  容器里 ping mysql 会报 unknown host
  但 ping 172.17.0.2 是通的,这说明网络本身没问题,缺的是域名解析
IP 还会变
  容器重启后重新分配,写死 IP 的配置就失效了
  这就是「服务重启以后连不上」的常见原因
没有按网络分组的能力
  所有默认网络的容器都在同一张网桥上,想隔离做不到

3. --link 已经废弃

老一代的做法是用 --link 把容器名字互相绑定:

docker run -d --name redis redis
docker run -d --name webapp --link redis:redis myapp

这招现在不要用了,原因很实在:

只能在默认网桥上用
  自定义网络上直接不生效
方向是单向的
  A 链接 B,B 知道 A,A 却不一定知道 B
  很多人在这里绕不过弯,明明加了 link 还是解析不了
改一下就得重建容器
  不能动态调整,容器起来之后就没法再加 link 了
官方已经标成 legacy
  自定义网桥把它的优点全占了,还没这些毛病

4. 自定义网桥:名字就是地址

docker network create --driver bridge --subnet 172.28.0.0/16 --gateway 172.28.0.1 mynet

docker run -itd --name centos2 --net mynet centos
docker run -itd --name centos3 --net mynet centos

docker exec -it centos2 ping -c 3 centos3
默认网桥与自定义网桥对照图:左边默认网桥 docker0 标为不能按名字访问、IP 不稳、无法按网络分组;右边自定义网桥标为能按容器名访问、依赖 Docker 在 127.0.0.11 起的内嵌 DNS、同一网络内 IP 稳定;下方列出 bridge、host、none、container 四种网络模式

Docker 在 127.0.0.11 起了一个内嵌 DNS
  同一个自定义网络里,容器名和别名都能解析
  这就是「按名字互相访问」的原理
服务名也能用
  Compose 起的服务名默认就是网络里的 DNS 名
解析的是容器名,不是服务名
  手动 docker run 的时候,只有 --name 和 --network-alias 能被解析
不同网络之间默认互相看不见
  这是隔离能力,不是限制
子网掩码决定容量
  /16 是 65534 个可用地址,/24 是 254 个
  网关就是网段里的第一个地址,通常写 172.28.0.1
  别跟已有的网段撞车,局域网和公司 VPN 的网段都要避开

5. 四种网络模式

bridge 默认
  容器有独立 IP,端口靠 -p 映射到宿主机
  日常就用这个,并且尽量用自定义网桥版
host 共享宿主机网络栈
  没有端口映射这回事,容器监听什么端口,宿主机就占用什么端口
  适合:容器要监听大量端口、或者要抓宿主机上进程的流量
  代价是没有网络隔离,端口冲突的概率高
none 完全隔离
  只有 loopback,没有网卡
  适合:跑完就结束的批处理任务
container 共享另一个容器的网络栈
  适合:把 sidecar 跟主容器绑成一套,比如代理和被代理的进程
  用得少,但知道有这东西就行

6. 一个容器连多个网络

docker network connect mynet 已有容器
docker network connect 另一个网络 已有容器
docker network ls
docker network inspect mynet

默认网桥里的服务连不进自定义网桥里的服务
  两张网桥是隔离的,容器只有一张网卡的时候过不去
解法就是把容器接到目标网络上
  docker network connect 会给它加一张网卡
典型的用法
  一个容器既要在业务网里跟别的服务通信,又要在管理网里被面板访问
  那就把它同时接进两张网
一个容器可以属于多个网络,但一个网络里的容器默认只认自己那张网
  方向性很强,不是全互通的
清理不用的网络
  docker network prune 删掉没容器在用的网络
  删网络不影响容器,但容器会掉出那张网

7. Spring Boot 项目发布

把项目打成镜像跑起来,标准做法是 Dockerfile 加多阶段构建。

FROM eclipse-temurin:17-jdk AS build
WORKDIR /app
COPY pom.xml .
RUN mvn -B dependency:go-offline
COPY src ./src
RUN mvn -B clean package -DskipTests

FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /app/target/*.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
CMD ["--server.port=8080"]

这里正好演示了 ENTRYPOINT 和 CMD 的组合
  ENTRYPOINT 定死 java -jar /app/app.jar,改不了
  CMD 是默认参数 --server.port=8080,docker run 后面能替换
  docker run 镜像 --server.port=9090 → 实际执行 java -jar /app/app.jar --server.port=9090
  这比把参数拼在 docker run 后面干净,配置跟着镜像走
多阶段构建的价值
  编译阶段用 -jdk,运行阶段用 -jre
  最终镜像里没有 Maven、没有源码、没有编译缓存
  镜像小一大截,攻击面也小一大截
先 COPY pom.xml 再装依赖
  这样只要依赖没变,go-offline 那层就命中缓存,不用重新下
  顺序反了每次构建都要重下一遍依赖
openjdk 官方镜像已经不能用了
  稳定标签全被移除了,FROM openjdk:8 会报 manifest unknown
  换 eclipse-temurin
jar 名字带版本号的话,COPY target/*.jar 可能匹配到多个
  要么写死文件名,要么构建时用 -DfinalName=app 固定
跑起来
  docker run -d -p 9999:8080 --name myapp 你的镜像:标签
  宿主机端口和容器端口可以不一样,上面这条是宿主机 9999 转容器的 8080

8. Compose 解决什么

服务一多,docker run 就没法看了。而且它有天然的顺序问题:应用要先等数据库起来。

Compose 用一个 YAML 文件描述一整套容器
  镜像、端口、挂载、环境变量、依赖关系,全写在一个文件里
  一份文件就是一套环境的定义,进版本库、改得动、看得懂
网络是自动配的
  同一个 compose 项目里的服务自动进同一张自定义网桥
  服务名就是 DNS 名,配置里写 mysql 就能连上,不用查 IP
  不用自己 docker network create
服务之间可以用服务名互相访问
  这一点是 Compose 相对 docker run 最大的优势
  应用重启、数据库换 IP,配置一个字都不用改

9. 装 Compose

现在只有 V2,命令是 docker compose(中间空格)
  V2 是 Docker CLI 的一个插件,用 Go 写的
  老的 docker-compose(中间连字符)2023 年 7 月就停止维护了
  那是独立的 Python 程序,新装的 Docker 只带 V2
  网上还能搜到一堆 curl 下载 1.25.5 的教程,那些都是 V1,别照抄
最省事的装法
  Debian / Ubuntu:sudo apt-get install docker-compose-plugin
  CentOS:sudo yum install docker-compose-plugin
手动装的话,要放到 CLI 插件目录
  放到 /usr/local/bin 是错的,那样装出来的是 V1 的用法
装完验一下版本
  docker compose version 注意中间是空格
  命令报「not a docker command」就是插件没装到正确位置

sudo mkdir -p /usr/local/lib/docker/cli-plugins
sudo curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 \
  -o /usr/local/lib/docker/cli-plugins/docker-compose
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
docker compose version

10. 写一个 compose.yaml

name: myapp

services:
  mysql:
    image: mysql:8.4
    container_name: myapp-mysql
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=app
    ports:
      - "127.0.0.1:3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
    networks:
      - app-net
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  api:
    build: ./api
    container_name: myapp-api
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
    ports:
      - "8080:8080"
    environment:
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/app
    networks:
      - app-net

networks:
  app-net:
    driver: bridge

volumes:
  mysql-data:
    driver: local

顶层的 version 字段已经废弃了
  那是 V1 时代的产物,V2 会忽略它并且打印一行警告
  直接删掉,别留着
  文件名用 compose.yaml,旧的 docker-compose.yml 也能认
服务名就是 DNS 名
  数据库地址写 mysql,不是 localhost
  写 localhost 是从容器里看宿主机,语义完全不对
  写 IP 更糟,重启就变
depends_on 不等于「等它能用了」
  默认只保证启动顺序,不管健康状态
  数据库容器起来了但 MySQL 还在初始化,应用就连不上
  用 condition: service_healthy 加上健康检查,这才是真的等
  这个特性 V2 才完整支持,V1 只能表达顺序
端口绑定 127.0.0.1 更安全
  "3306:3306" 是绑到所有网卡,公网机器上等于把数据库暴露出去
  只给本机用就写 "127.0.0.1:3306:3306"
密码走环境变量,不写进文件
  ${MYSQL_ROOT_PASSWORD} 从宿主机环境读
  同目录放个 .env 文件写真实值,这个文件要加进 .gitignore
数据用具名卷
  写在 volumes: 里的才是具名卷,直接写路径的是绑定挂载
服务多的话给 name 顶层字段
  项目名决定了网络名和卷名前缀,写死了多套环境容易撞

11. 常用命令

docker compose up -d            # 创建并后台启动整套
docker compose ps               # 看这套的状态
docker compose logs -f api      # 跟某个服务的日志
docker compose exec api bash    # 进某个服务
docker compose build            # 重新构建镜像
docker compose pull             # 拉最新镜像
docker compose restart api      # 重启单个服务
docker compose up -d --build api    # 改了代码只重建这个服务
docker compose config           # 校验文件并输出最终配置
docker compose down             # 停止并删除容器
docker compose down -v          # 连具名卷一起删

down 不删卷,down -v 删
  数据库数据在具名卷里,down 之后还在
  down -v 是不可逆的,执行前先确认要删什么
改完代码用 up -d --build 服务名
  只重建受影响的那个服务,不用整套重来
docker compose config 是最好的检查工具
  它会展开所有变量、合并继承关系
  YAML 缩进错了它会直接报错,比 up 报错了再回头查快得多
构建缓存让重建很快
  只要 Dockerfile 里的步骤没变,层就复用
  所以 COPY 要放在最后,别让它一改就把前面全推倒
Compose 也能管多个 compose 文件
  docker compose -f base.yaml -f prod.yaml up -d
  后一个文件覆盖前一个的同名字段,这是拆配置的标准做法

💡 补充几个容易忽略的:
Compose 里的镜像分两种
  image: 是直接用现成镜像,比如 mysql、redis
  build: 是用 Dockerfile 现建,后面可以跟 context 和 dockerfile
  两个可以一起写:有 image 就用它,同时按 build 构建并打上这个标签
YAML 里带变量的值要加引号
  端口号写成 "5212:5212",别写 5212:5212
  不加引号 YAML 会把它当成一个十进制数或者 sexagesimal,某些端口号会被解析成奇怪的值
宿主机路径建议用相对路径
  ./cloudreve/uploads:/cloudreve/uploads
  相对路径是相对 compose 文件所在目录的,整个项目目录可以随便搬
多个 compose 项目之间不要用同一张外部网络
  Compose 会自动创建 项目名_网络名 这样的网络,名字带项目前缀就是为了隔离
用 profiles 做可选服务
  docker compose --profile debug up -d 才起带 debug profile 的服务
  调试用的数据库、监控面板可以用这招隔离开
Compose 也能管非 Docker 的东西
  每个服务可以带 network_mode: host,容器直接用宿主机网络
  适合那种非要用固定 IP、不能用端口映射的老服务
配套的服务可以对照着看
  Redis 部署与高可用 讲了容器化之后怎么配主从和哨兵
  RabbitMQ 从入门到落地 里也有完整的容器部署
  Maven 私有仓库 用 compose 起了一个 Nexus
  EmuLinker-K 街机联机服务器 是 compose 拉起一组容器的实际例子

🔥 一句话总结:默认网桥 docker0 没有内嵌 DNS,容器之间只能用 IP 访问,而 IP 还会变;自己建一张自定义网桥,Docker 就在 127.0.0.11 起了个内嵌 DNS,容器名直接当地址用;--link 是 legacy,方向还是单向的,别再用;Compose 自动给同一项目里的服务建网,服务名就是 DNS 名,数据库地址写服务名而不是 localhost 或 IP;Compose 只有 V2,命令是 docker compose(空格),老教程里下载 1.25.5 那套是 V1、别照抄;顶层 version 字段已废弃,depends_on 默认只管顺序不管健康,要等就绪得配 condition: service_healthy。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!