
刚开始用 Docker Compose 的时候很多人都会遇到一个非常典型的问题。本地开发时后端连接 PostgreSQLlocalhost:5432没有任何问题。但是把后端也放进 Docker 以后代码还是localhost:5432结果直接报错connection refused然后很多人第一反应是数据库是不是没启动其实数据库可能完全正常。真正的问题是localhost在 Docker 里代表的东西和你想象的不一样。今天就只讲一个知识点Docker Network一、先看一个真实场景比如一个后台项目有三个服务Go Backend PostgreSQL RedisDocker Compose 大概是services:app:image:my-app:latestpostgres:image:postgres:15redis:image:redis:7启动dockercompose up-d现在一共有三个容器app postgres redis如果 Go 后端需要连接 PostgreSQL很多人会这样写localhost:5432但这是错的。二、Docker 里的 localhost 到底是谁我们平时在自己电脑上写localhost代表当前这台电脑但是容器本质上拥有自己独立的网络环境。可以简单理解成宿主机 │ ├── app 容器 │ └── localhost │ ├── postgres 容器 │ └── localhost │ └── redis 容器 └── localhost所以你进入app容器以后localhost代表的是app 容器自己而不是postgres 容器因此localhost:5432实际上是在问app 这个容器自己的 5432 端口有没有 PostgreSQL当然没有。所以就会connection refused三、正确写法是什么在 Docker Compose 中可以直接使用Service Name也就是服务名。例如services:app:image:my-apppostgres:image:postgres:15那么 app 访问数据库时postgres:5432就可以了。Redisredis:6379所以后端配置应该类似database:host:postgresport:5432redis:host:redisport:6379而不是database:host:localhostredis:host:localhost四、为什么服务名可以直接访问因为 Docker Compose 会给这些服务创建一个内部网络。例如networks:app-network:driver:bridge然后services:app:networks:-app-networkpostgres:networks:-app-networkredis:networks:-app-network最终Docker Network ┌─────────────────────────┐ app │ │ ┌────┴────┐ │ │ ▼ ▼ postgres redis └─────────────────────────┘Docker 会在这个内部网络中提供服务发现能力。所以postgres实际上会被解析到 PostgreSQL 容器的 IP。你不需要知道172.18.0.3这种具体地址。五、这就像 Docker 内部自带了 DNS我们平时访问baidu.com不用知道百度服务器的真实 IP。因为 DNS 会帮我们解析。Docker Compose 也是类似。你写postgresDocker 会帮你找到postgres 容器你写redisDocker 会找到redis 容器所以可以简单理解Docker Network Service Name Docker 内部服务发现这也是为什么 Docker Compose 项目里很少会把容器 IP 写死。六、千万不要写死容器 IP比如你执行dockerinspect postgres看到172.20.0.4然后后端配置172.20.0.4:5432短时间确实可能能用。但是容器一旦重新创建dockercompose downdockercompose up-d它可能变成172.20.0.6于是程序又连不上了。所以 Docker 项目里最好不要写172.20.0.4而应该写postgres让 Docker 自己完成解析。七、宿主机端口和容器端口又是什么关系这里还有一个特别容易混淆的问题。比如postgres:image:postgres:15ports:-15432:5432这句话表示宿主机 15432 ↓ 容器 5432如果你在 Mac、Windows 或 Linux 本机连接 PostgreSQLlocalhost:15432这是正确的。但是如果app 容器访问 PostgreSQL应该写postgres:5432注意区别。八、一定要搞清楚这两种访问方式第一种宿主机访问容器Mac ↓ localhost:15432 ↓ PostgreSQL Container:5432所以localhost:15432第二种容器访问容器app container ↓ postgres:5432 ↓ PostgreSQL container所以postgres:5432记住这个区别以后很多 Docker 网络问题就简单了。九、以 ShiyuAdmin 为例比如项目里有shiyu-app shiyu-postgres shiyu-redis它们加入shiyu-network大概是services:shiyu-postgres:image:postgres:15networks:-shiyu-networkshiyu-redis:image:redis:7networks:-shiyu-networkshiyu-app:networks:-shiyu-networknetworks:shiyu-network:driver:bridge所以shiyu-app访问 PostgreSQLshiyu-postgres:5432访问 Redisshiyu-redis:6379整个过程完全不需要知道容器 IP。十、为什么数据库已经映射端口了还要 Docker Network有人可能会问既然已经ports:-15432:5432为什么 app 不直接宿主机IP:15432访问数据库理论上某些场景可以绕一圈实现。但一般没必要。正常方式app ↓ Docker Network ↓ postgres:5432如果绕宿主机app ↓ 宿主机网络 ↓ 15432 ↓ postgres:5432明显复杂了一层。Docker 容器之间本来就在内部网络里。所以直接postgres:5432通常更合理。十一、甚至数据库都可以不暴露给宿主机如果 PostgreSQL 只给后端使用其实生产环境甚至可以不写ports:-15432:5432只写postgres:image:postgres:15networks:-app-network这样app仍然可以postgres:5432连接数据库。但是外部localhost:15432就无法访问。这样反而更安全。因为数据库没有必要直接暴露到服务器公网。十二、Redis 也是一样Redis 经常会看到ports:-6379:6379但如果 Redis 只给Backend使用。生产环境很多时候也没有必要暴露6379只需要app ↓ redis:6379即可。这其实也是 Docker Network 一个很重要的作用不只是让容器能通信也可以控制哪些服务需要真正暴露到外部。十三、怎么查看 Docker Network执行dockernetworkls你会看到NETWORK ID NAME DRIVER xxxxxx bridge bridge xxxxxx project_default bridge xxxxxx shiyu-network bridge查看具体网络dockernetwork inspect shiyu-network可以看到加入这个网络的容器。比如shiyu-app shiyu-postgres shiyu-redis还能看到对应的内部 IP。十四、怎么测试两个容器能不能通信可以先进入应用容器dockerexec-itshiyu-appsh然后尝试pingshiyu-postgres如果镜像里面没有 ping也可以尝试getent hosts shiyu-postgres或者连接端口。例如nc-zvshiyu-postgres5432Redisnc-zvshiyu-redis6379如果可以解析服务名shiyu-postgres说明 Docker 内部 DNS 基本正常。十五、两个不同 Compose 项目默认能通信吗默认情况下项目 A和项目 B一般会创建不同的 Docker Network。比如projectA_default projectB_default所以Project A Container不能理所当然直接访问Project B Service如果确实需要跨项目通信可以给两个项目加入同一个external network例如先创建dockernetwork create shared-networkComposenetworks:shared-network:external:true然后服务加入services:app:networks:-shared-network这样不同 Compose 项目之间也可以互相通信。十六、一个最小 Demo下面这个例子可以直接理解 Docker Network。services:web:image:nginx:alpinenetworks:-demo-networkredis:image:redis:7networks:-demo-networknetworks:demo-network:driver:bridge启动dockercompose up-d然后进入 webdockercomposeexecwebsh解析 Redisgetent hosts redis你会发现redis能够被解析到一个 Docker 内部 IP。这就是服务发现。十七、Docker Network 真正要记住的只有三句话第一句容器里的 localhost只代表当前容器。第二句Compose 容器之间优先使用 Service Name 通信。第三句宿主机端口映射是给宿主机或外部访问用的。所以以后看到ports:-15432:5432不要理解成所有地方都应该访问 15432而应该分场景。宿主机localhost:15432容器内部postgres:5432这两个完全不是一回事。最后很多 Docker 项目连接数据库失败最后排查半天发现不是PostgreSQL有问题。也不是Redis有问题。只是配置里写了一句localhostDocker 容器不是简单地把你的程序换个地方运行。每一个容器都有自己的进程空间 文件系统 网络空间所以一旦进入 Docker 世界就一定要重新理解localhost到底是谁。记住宿主机访问 PostgreSQL localhost:15432 Docker 容器访问 PostgreSQL postgres:5432把这一个知识点搞明白Docker Compose 里的很多网络问题基本就已经解决一半了。