Docker Compose发布端口后,你写的那条ufw规则一次都没被执行过
本文目录
- 防火墙规则条条生效,为什么端口还是全世界可连?
- ufw的规则挂在哪条链上
- 发往容器的包不是发给本机的
- 为什么这个坑格外阴
- 三条命令,自己验一遍
- 这个洞是新出现的,还是一直都在?
- 一张开了十年的单
- 十年里社区的应对方式
- Docker Engine 28修好了吗?
- 28版实际改的默认值
- 28版明确没改的部分
- 28版的两个回退开关
- 端口该怎么发布才不越过防火墙?
- 把发布地址钉死在回环
- 往DOCKER-USER链里写规则
- 用ufw-docker把两套规则并轨
- 干脆不发布端口
- 四种做法怎么选
- 顺手把网络也拆开
- compose.yaml没写的字段,Docker替你填了什么值?
- expose写了等于没写
- restart的四个档位差在重启之后
- 默认不写restart等于不重启
- 与其背默认值,不如让工具打印出来
- 容器都起来了,Nginx为什么还在回502?
- depends_on的“ready”不是你理解的ready
- healthcheck加条件依赖才是真等待
- 健康检查要检到业务层
- 磁盘是怎么被容器日志悄悄写满的?
- json-file驱动出厂没有上限
- 磁盘满了之后先坏的不是容器
- 在daemon层设上限,而不是逐个服务设
- 数据卷的属主为什么改不动?
- 命名卷的初始化只发生一次
- 三条可用的出路
- 这些默认值最后是怎么变成SEO事故的?
- 自建服务被抓进索引
- 健康检查缺失导致的502会被记账
- 日志爆盘的连锁反应最贵
- 上线前照着这份清单过一遍
- 网络与暴露面
- 启动与依赖
- 日志与数据
- 常见问题解答
- 把Docker升级到28以上,是不是就不用管ufw的事了?
- 直接在daemon.json里设置iptables为false,能不能一劳永逸?
- 只在compose里写expose不写ports,端口会暴露到公网吗?
- depends_on写了服务名,为什么应用还是连不上数据库?
- 容器日志已经把磁盘写满了,现在怎么救?
- compose文件里的version字段还需要写吗?
- 权威参考资料
摘要:你在VPS上敲的ufw deny从来没有拦住过任何一个访问容器的请求,因为那些包根本不走ufw所在的那条链。这不是配错了,是Docker官方文档里白纸黑字写着的“两者互不兼容”。Docker Engine 28确实收紧了默认值,但它收的是另一个洞——已发布端口照旧穿墙而过。下面把包的实际走向、28版修与没修的边界、四种收口做法各自能活多久,以及compose.yaml里另外五个替你做过决定的默认值,一次讲清楚。
先说一个让人不太舒服的事实:如果你在一台VPS上同时装了ufw和Docker,然后用docker compose up -d起了一堆服务,那么你在ufw里写的所有deny规则,对这些服务的已发布端口是完全无效的。不是部分无效,是压根没有被执行到。
更让人不舒服的是,这件事没有任何报错。ufw的status输出漂漂亮亮,规则一条不少,容器跑得好好的,日志干干净净。唯一的异常,是全世界都能连上那个你以为只对本机开放的端口。
保哥去年帮一个做汽车改装件的DTC客户做服务器体检,就是在这里翻的车。他们把一套自建的图片处理服务跟主站放在同一台VPS上,compose文件里写着ports: - "9000:9000",ufw里只放行了22、80、443三个端口。结果扫一遍公网,9000端口对全球开放,里面躺着三万多张还没打水印的产品原图。
防火墙规则条条生效,为什么端口还是全世界可连?
这个问题的答案不在ufw,也不在compose,而在Linux内核处理数据包的顺序上。
ufw的规则挂在哪条链上
ufw本质上是iptables的一层封装。你写的ufw allow 443,最终变成filter表INPUT链上的一条规则。INPUT链管的是目的地是本机的包——包走到网络栈,内核发现收件人就是这台机器自己,才交给INPUT链去审。
关键就在这句“目的地是本机”。
发往容器的包不是发给本机的
当你用-p 9000:9000发布一个端口,Docker会在nat表的PREROUTING链上写一条DNAT规则。包一进网卡,还没轮到任何过滤逻辑,目的地址就已经被改写成容器在bridge网络里的内网地址了。
改写之后,内核再去做路由判断:这个包的收件人不是本机,是172.17.0.x。于是它走的不是INPUT链,而是FORWARD链。而ufw默认压根不在FORWARD链上写业务规则。
Docker官方那篇《Packet filtering and firewalls》把这件事说得毫不含糊,原话是:Docker和ufw使用防火墙规则的方式让两者互不兼容;当你用Docker发布容器端口时,流量在到达ufw的设置之前就已经被转走了。
官方文档的措辞值得琢磨——它用的是incompatible(互不兼容),不是bug。也就是说这不会被“修”,它就是设计。
为什么这个坑格外阴
常见的配错,你多少能从某个地方看出端倪:服务起不来、日志报错、端口连不上。而这个坑的所有症状都是反的——一切正常,只是安全边界不存在。
更麻烦的是它的验证方式有陷阱。很多人在服务器上curl 127.0.0.1:9000测一下,通了,觉得正常;再curl一下公网IP,也通,觉得“大概是我放行过吧”。两次测试都从本机发起,两次都绕开了真正的判据。唯一有效的验证是从另一台机器打过来,或者干脆用在线端口扫描服务从外网扫一遍。
如果你还没做过服务器层面的端口盘点,可以先照着ufw防火墙的端口放行与云安全组配置那套流程把基线摸清楚,再回来处理Docker这一层。
三条命令,自己验一遍
这件事不需要相信任何人的转述,十分钟能自己跑完。找一台测试VPS,装好ufw和Docker,然后:
# 第一步:把默认策略关死,只放行SSH
ufw default deny incoming
ufw allow 22/tcp
ufw enable
# 第二步:起一个发布了端口的容器
docker run -d --name t -p 8080:80 nginx:alpine
# 第三步:从另一台机器打过来
curl -m 5 -I http://你的公网IP:8080/正常情况下你会拿到一行HTTP/1.1 200 OK。ufw的默认策略是deny,8080没有被放行,然而它通了。
想看清包被谁截走了,再加一条:
iptables -t nat -L DOCKER -n --line-numbers输出里会有一条形如DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80的规则。这条规则在nat表的PREROUTING阶段执行,而ufw的规则在filter表的INPUT阶段——前者比后者早,且改完目的地址之后包就不再属于INPUT的管辖范围了。
顺手记一句:验证完记得docker rm -f t,别把测试容器留在那儿。这类“测完忘了删”的容器,在保哥做过的服务器体检里出现频率高得离谱。
这个洞是新出现的,还是一直都在?
一直都在,而且有据可查。
一张开了十年的单
moby仓库那张标题为local network container access vulnerability的问题单,开单时间是2015年6月19日。它描述的是一个相邻但不同的场景:局域网里的其他机器,可以绕过端口发布限制直接访问容器。
这张单子活得比很多创业公司都久。它一直挂到2025年,Docker Engine 28发布时才被正式收口。
十年里社区的应对方式
在官方默认值改动之前,社区拿出的方案基本是三类:一类是关掉Docker的iptables自动管理("iptables": false),代价是容器的出站网络也一起废掉;一类是往DOCKER-USER链里手写规则;还有一类是专门的适配脚本。
chaifeng/ufw-docker这个专门解决该冲突的项目属于第三类,它的做法是往ufw的after.rules里插一段自定义链,把Docker的转发流量重新拉回ufw的判据下。这个项目的README第一句话就是“修复Docker和UFW的安全缺陷而不必禁用iptables”,标题本身已经说明这个问题存在了多久、有多普遍。
Docker Engine 28修好了吗?
修了,但修的不是你以为的那个。这一节值得慢慢读,因为2025年之后写的大量文章在这里给出了错误的安心感。
28版实际改的默认值
Docker Engine v28默认收紧容器网络的官方公告发布于2025年2月28日。它改的是这样一件事:以前如果宿主机的filter-FORWARD链策略是ACCEPT、并且开了net.ipv4.ip_forward,那么没有发布的容器端口,也能被同一个局域网里的其他机器通过容器IP直接访问到。28版给这类流量加上了显式的DROP。
换句话说,28版堵的是“我压根没发布,你凭什么连得上”这个洞。这确实是2015年那张单描述的场景。
28版明确没改的部分
同一篇公告里有一句话,是整件事的分水岭:已发布的端口将继续照常工作(比如-p 8080:80)。
“照常工作”的意思,就包括照常绕开ufw。28版把未发布端口的意外暴露堵上了,但对已发布端口与主机防火墙的冲突,一个字没动,也不打算动——因为在Docker的设计里,你写-p就是在明确表达“我要把这个端口开出去”的意图。
这就构成了一条很实用的判据:
| 场景 | Docker 27及以前 | Docker 28及以后 |
|---|---|---|
| 未发布端口,局域网直连容器IP | 可能连得上 | 默认被DROP |
已发布端口(-p),ufw拒绝该端口 | 依然可连 | 依然可连 |
已发布端口,绑定到127.0.0.1 | 只有本机可连 | 只有本机可连 |
| 宿主机自身端口(如SSH) | ufw正常生效 | ufw正常生效 |
表里第二行是这篇文章存在的全部理由。升级到28并不会让你的compose文件变安全一点点。
28版的两个回退开关
如果你的架构确实依赖“从局域网直连容器IP”这个老行为——比如内网监控直接刮容器的指标端口——28版给了两个显式的回退路径。一个是给某个网络单独开口:
docker network create -d bridge \
-o com.docker.network.bridge.gateway_mode_ipv4=nat-unprotected \
my_unprotected_net另一个是在/etc/docker/daemon.json里全局关掉这个DROP策略:
{
"ip-forward-no-drop": true
}需要提醒的是,第二个开关是全局的,一开就等于回到27版的行为。真要用,也应该优先用第一个,把影响范围锁在单个网络里。
端口该怎么发布才不越过防火墙?
四种做法,按“需要多少纪律才能长期成立”从低到高排。
把发布地址钉死在回环
这是成本最低、也最该成为默认习惯的一条。Compose规范的services章节对短语法的定义是:HOST部分是可选的[IP:](port | range),如果不设置,它会绑定到所有网络接口,也就是0.0.0.0。
于是这两行的安全含义天差地别:
services:
imgproxy:
ports:
- "9000:9000" # 绑0.0.0.0,全世界可连
- "127.0.0.1:9000:9000" # 只有本机可连绝大多数内部服务——图片处理、队列面板、搜索引擎、数据库——都应该走第二种,然后由Nginx做反向代理对外暴露。这样安全边界重新回到了你熟悉的位置:Nginx配置和ufw,两个你本来就在维护的东西。
往DOCKER-USER链里写规则
Docker专门留了一条名为DOCKER-USER的链给你,它在Docker自己那堆规则之前被求值,是官方认可的插手位置。典型写法是只允许某几个来源访问转发流量:
iptables -I DOCKER-USER -i eth0 ! -s 10.0.0.0/8 -j DROP这条规则的意思是:从eth0进来、源地址不在内网段的转发包,一律丢弃。
它的问题是持久化。DOCKER-USER里的规则能扛住容器重启,扛不住宿主机重启,除非你用iptables-persistent之类的机制把它存下来。保哥见过不止一次的事故形态是:规则写对了,验证也通过了,三个月后机房断电重启,端口重新裸奔,而没有任何人会收到通知。
用ufw-docker把两套规则并轨
前面提到的那个脚本,本质上是把DOCKER-USER的手写规则封装成了ufw风格的命令。装好之后你可以这样写:
ufw-docker allow imgproxy 9000好处是心智模型统一了,团队里其他人不需要知道iptables链的存在。代价是多了一个第三方依赖,Docker升级后偶尔需要重新跑一次同步。
干脆不发布端口
最彻底的一种:内部服务之间通过compose自建的网络用服务名互访,一个端口都不往宿主机发布。Nginx如果也在compose里,它直接用http://imgproxy:9000访问,宿主机上看不到9000这个监听。
这条路的前提是你的反向代理也进了容器。如果Nginx装在宿主机上,那就退回到回环绑定那一档。想理解容器网络这层隔离到底是怎么做出来的,可以看看Linux网络命名空间与veth虚拟网络隔离那篇的底层拆解。
四种做法怎么选
| 做法 | 改动成本 | 宿主机重启后 | 需要的纪律 | 适合谁 |
|---|---|---|---|---|
| 回环绑定 | 改一行compose | 自动成立 | 记得每次都写 | 所有人的默认选项 |
| DOCKER-USER手写 | 写iptables规则 | 要显式持久化 | 持久化+文档+交接 | 有专职运维的团队 |
| ufw-docker脚本 | 装一个第三方工具 | 随ufw规则保留 | Docker升级后复查 | 多人协作、口径要统一 |
| 完全不发布端口 | 反向代理也要进容器 | 自动成立 | 整套架构一致 | 新项目、能重来一次的 |
表里最值得注意的是第三列。四种做法里只有两种是“配好就不会自己失效”的,另外两种都需要一个人记得某件事——而运维事故的绝大多数,都发生在那个人换岗之后。如果非要挑一条,回环绑定加反向代理是唯一一条既便宜、又不依赖记忆力的路。
顺手把网络也拆开
还有一层常被跳过的收口:让不该互相说话的服务压根不在同一张网里。compose默认给整个项目建一张网,所有服务都在里面,也就是任何一个容器被攻破,其余全部可达。
services:
nginx:
networks: [frontend]
app:
networks: [frontend, backend]
db:
networks: [backend]
networks:
frontend:
backend:
internal: true这样一来nginx碰不到db,而internal: true让backend这张网彻底没有出网能力——数据库容器连不上外网,也就没法被用来往外传数据。这一行的防守价值,比大多数人给它的注意力要高得多。
compose.yaml没写的字段,Docker替你填了什么值?
端口只是最扎眼的一个。compose文件的设计哲学是“不写就用合理默认”,问题在于“合理”是对开发环境说的,不是对生产说的。
expose写了等于没写
很多教程把expose说成“只对内网开放端口”,听起来像是ports的安全版本。规范里的实际说法要冷淡得多:如果镜像的Dockerfile已经暴露了端口,即便compose文件里不设expose,它对同网络的其他容器也是可见的。
同一个网络里的容器之间,端口本来就是互通的,expose不增加也不减少任何东西,它更接近一份文档注释。真要限制容器之间的访问,得靠拆分网络,让不该说话的两个服务压根不在同一张网里。
restart的四个档位差在重启之后
规范里四个值的定义是这样的:
| 值 | 规范定义 | 宿主机重启后 |
|---|---|---|
no | 默认策略,任何情况下都不重启容器 | 不起来 |
on-failure[:n] | 退出码表示出错时重启 | 不起来 |
always | 一直重启直到容器被删除 | 起来 |
unless-stopped | 不看退出码都重启,但服务被手动停止或删除后不再重启 | 起来,除非你之前手动停过 |
差别集中在最后一列。always的always是字面意义上的always——包括你上周手动停掉它、准备第二天排查问题,结果半夜机房重启,它又自己爬起来了。做灰度或者留了坏容器待查的场景,unless-stopped才是你要的那个。
默认不写restart等于不重启
这条单独拎出来说,是因为它太容易漏。规范写得很清楚:no是默认策略。一个compose文件如果没写restart,服务器一重启,你的服务就是不起来的,而且没有任何东西会提醒你。
把开机自启这件事纳入巡检,比记住写这一行更靠谱。可以顺手接进用cron把独立站服务器运维自动化那套定时任务里,让机器自己每天确认一遍该活的都活着。
与其背默认值,不如让工具打印出来
上面这些默认值还会继续变——Compose规范本身在演进,Docker Engine的版本也在往前走。与其记住某个版本的某个默认值,不如养成一个更省心的习惯:
docker compose config这条命令会把你的compose文件连同所有变量插值、所有继承关系一起展开,打印出Docker实际会用的那份完整配置。你没写的字段,凡是有默认值的都会在这份输出里现形。
更实用的用法是把它接进上线流程:改完compose文件先docker compose config看一眼展开结果,再决定要不要up。.env没读到、变量拼错、覆盖文件顺序反了这几类问题,在这一步全都会暴露出来,而在up之后它们只表现为“服务行为跟预期不一样”。
再补一条相关的:docker compose config --services只列服务名,--images只列镜像。上线前用--images核对一遍镜像标签,能挡住相当一部分“本地跑的是新版、服务器上还是latest缓存”的事故。
容器都起来了,Nginx为什么还在回502?
这是compose第二大坑,也是最容易被误判成“网络问题”的一个。
depends_on的“ready”不是你理解的ready
规范里对depends_on的描述用了一个很有迷惑性的词:Compose保证依赖服务先于依赖方启动,并会等待依赖服务“就绪”后再启动依赖方。
问题是这个“就绪”的默认判据是service_started——容器进程起来了就算就绪,跟里面那个服务能不能接请求毫无关系。MySQL容器起来到真正能接受连接,中间通常有十几秒;这十几秒里,你的应用容器已经按照compose的承诺开始跑了,连数据库连不上,直接退出或者进入重试。
规范用ready这个词,而它的默认含义是started,这大概是compose文件里最贵的一处措辞歧义。
healthcheck加条件依赖才是真等待
要拿到真正的就绪语义,得同时写两件事——被依赖方的健康检查,和依赖方的条件依赖:
services:
db:
image: mysql:8.4
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
start_period: 40s
app:
depends_on:
db:
condition: service_healthystart_period这个参数经常被漏掉,它的作用是给容器一段宽限期:在这段时间内检查失败不计入retries。MySQL首次初始化数据目录可能要三四十秒,没有start_period的话,健康检查会在初始化完成之前就把重试次数耗光,容器被判定为unhealthy。
健康检查要检到业务层
写健康检查还有个常见的偷懒做法:检查端口通不通。端口通只说明进程在监听,说明不了它能不能处理请求。
对Web服务,检查点应该落到一个真正会走完业务逻辑的轻量路径上,比如一个会读一次数据库的/healthz。对独立站来说这件事还有额外的意义——502不只是用户看不到页面,它同时会被搜索引擎记在账上,这层账怎么算,TTFB与多层缓存对Core Web Vitals和抓取预算的双重影响那篇讲得比较细。
磁盘是怎么被容器日志悄悄写满的?
这一条排在“端口暴露”之后,是保哥见过的第二高频容器事故。
json-file驱动出厂没有上限
Docker默认的日志驱动是json-file,它把容器stdout和stderr的每一行都写进宿主机的一个JSON文件里。json-file日志驱动的官方参数说明里,max-size的默认值是-1,意思是不限制。
不限制的后果,在一个日志话痨型的应用上会来得很快。一个每秒打十行日志、每行200字节的服务,一天写掉约1.6GB。跑一个月,50GB。而很多入门级VPS的系统盘就是50GB。
磁盘满了之后先坏的不是容器
磁盘写满时,最先出事的往往是MySQL和Nginx,而不是那个话痨容器。MySQL无法写binlog直接拒绝写入,Nginx无法写access log开始返回错误。你在告警里看到的是“数据库挂了”,真凶在另一个目录。
排查入口很固定,一条命令能看出量级:
du -sh /var/lib/docker/containers/*/*-json.log | sort -h | tail -5在daemon层设上限,而不是逐个服务设
compose里可以给单个服务配logging,但更省事的是在/etc/docker/daemon.json里一次设全局默认:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "3"
}
}要注意这个设置只对之后新建的容器生效,已经在跑的容器需要重建才会应用。改完记得docker compose up -d --force-recreate过一遍,否则你只是给未来买了保险,当下的炸弹还在。系统层面的日志治理思路可以参考用logrotate与journald管好服务器日志,两套机制的边界正好互补。
数据卷的属主为什么改不动?
这个坑不常见,但一旦踩上,排查时间通常以小时计。
命名卷的初始化只发生一次
当你把一个命名卷第一次挂到容器的某个目录上,而这个卷是空的,Docker会把镜像里该目录的内容连同属主和权限一起复制进卷里。这是一次性行为——卷一旦非空,后续挂载不再复制任何东西。
于是这个场景就成立了:你把镜像从node:18升到node:22,新镜像里应用目录的属主UID从1000变成了1001,但卷里保存的还是1000。容器起来之后写文件报权限拒绝,你去镜像里查,UID明明是对的。
三条可用的出路
第一条是显式指定运行用户,user: "1000:1000",让容器去迁就卷而不是反过来。第二条是在宿主机上直接chown卷目录,命名卷的实际路径在/var/lib/docker/volumes/卷名/_data。第三条最干脆——把卷删了重建,前提是里面的数据可以重新生成或者你已经备份过。
备份这件事在容器场景下比在传统部署下更容易被忽略,因为“数据在卷里”这个说法听起来就像已经安全了。实际的备份策略可以照着rsync增量备份与快照的服务器实操那套来做,把卷目录纳进去就行。
这些默认值最后是怎么变成SEO事故的?
把上面几条串起来看,它们通向的不只是运维故障,还有一条更安静的损失路径。
自建服务被抓进索引
绑在0.0.0.0上的服务,只要有一个页面返回HTML,就存在被抓取和收录的可能。图片处理服务的调试页、队列监控面板、搜索引擎的管理界面——这些页面的共同点是没有noindex,没有robots规则,也没人想过要给它们加。
它们进索引的后果分两层:一层是暴露基础设施细节,另一层更麻烦——这些页面会稀释站点在搜索引擎眼里的主题聚焦度,还会占用本来该给商品页的抓取额度。
回到开头那个汽车改装件的客户。当时的处理顺序是这样的:先把9000端口从0.0.0.0改到回环、重建容器,再用站长工具查有没有被收录过的IP:9000形式的地址——查出来七条,全是图片处理服务的调试页。然后对这七条做移除请求,同时在Nginx层对来自IP直连的请求统一返回444。三周后复查,收录归零。
值得记下来的是这件事的时间成本分布:改配置花了十分钟,清理已经进索引的痕迹花了三周。这个比例在这类事故里相当典型,也是为什么“上线前扫一遍端口”这种听起来很无聊的动作值得被写进流程。
健康检查缺失导致的502会被记账
依赖顺序没配好导致的间歇性502,用户可能刷新一下就过去了,爬虫不会。连续的5xx会让抓取速率下调,而恢复是缓慢的、不对称的——掉下去很快,涨回来要按周算。
日志爆盘的连锁反应最贵
磁盘写满导致的全站不可用,在监控里往往表现为一段完整的空白。这种故障的特点是同时命中可用性、抓取、和用户体验三项。真正让人肉疼的是,起因只是某个compose文件少写了四行logging配置。
如果你的站点跑在容器里且用WordPress,环境一致性和数据持久化那部分可以对照WordPress独立站的Docker容器化部署一起看,那篇讲的是怎么搭起来,这篇讲的是搭起来之后哪几个默认值会咬人。
上线前照着这份清单过一遍
把前面所有判据压成一份可以直接执行的自查表。每一条都能在两分钟内验证完。
网络与暴露面
- 用另一台机器扫一遍公网IP的全端口,而不是在本机
curl。 - compose里每一条
ports都确认过是否真的需要对外,需要对外的走Nginx。 - 所有内部服务的
ports加上回环地址前缀,或者干脆删掉ports只留网络内互访。 - 如果用了DOCKER-USER规则,验证过宿主机重启之后规则还在。
启动与依赖
- 每个服务都写了
restart,且区分清楚always和unless-stopped。 - 有状态依赖的服务写了
condition: service_healthy,不是光写服务名。 - 数据库类容器的健康检查带
start_period,值不小于首次初始化时间。 - 健康检查探到业务层,不是只探端口。
日志与数据
daemon.json里配了max-size和max-file,且已经force-recreate过一次。- 命名卷的实际磁盘占用有人在看,不是等告警。
- 卷目录进了备份范围,且做过一次真实的恢复演练。
- 镜像升级前确认过运行用户UID有没有变。
最后补一条不属于任何分类、但优先级最高的:这份清单本身要有人定期跑。容器的问题很少在部署当天暴露,它们更喜欢在某次不相干的重启之后,安安静静地出现。宿主机层面的加固基线,可以对照SSH密钥登录与fail2ban的服务器加固实操一并做掉,两层放在一起才算完整。
常见问题解答
把Docker升级到28以上,是不是就不用管ufw的事了?
不是。28版堵的是未发布端口被局域网直连的洞,官方公告里明确写了已发布端口继续照常工作。只要你还在compose里写ports并绑到0.0.0.0,ufw对那个端口依然没有发言权。升级值得做,但它解决不了这篇文章讨论的主要问题。
直接在daemon.json里设置iptables为false,能不能一劳永逸?
能让ufw重新说了算,但代价大到通常不划算。关掉之后Docker不再维护NAT规则,容器的出站访问、容器之间的通信、端口发布全部需要你自己写iptables来实现。除非你有很强的网络自定义需求并且团队里有人长期维护这套规则,否则更推荐回环绑定加反向代理这条路。
只在compose里写expose不写ports,端口会暴露到公网吗?
不会。expose不做任何端口映射,宿主机上不会出现监听。但也别把它当成安全措施——同一个Docker网络里的容器之间本来就能互相访问端口,写不写expose都一样,它的实际作用接近一行注释。真要隔离,得靠拆网络。
depends_on写了服务名,为什么应用还是连不上数据库?
因为短语法的默认条件是service_started,容器进程起来就算满足,跟数据库能不能接受连接是两件事。要真正等待,需要给数据库服务写healthcheck,并在应用服务里用长语法写condition: service_healthy。另外记得配start_period,否则首次初始化期间的失败会把重试次数提前耗光。
容器日志已经把磁盘写满了,现在怎么救?
先用du定位到最大的那个*-json.log,然后用truncate -s 0把它清零——不要直接rm,文件句柄还被进程持有,删了空间不会释放。清完立刻去daemon.json里配上max-size和max-file,再docker compose up -d --force-recreate让新配置对现有容器生效。这两步顺序反了的话,磁盘会很快再满一次。
compose文件里的version字段还需要写吗?
不需要了。Compose规范已经把version标记为废弃字段,新版docker compose遇到它会打印一条警告。顺带一提,带连字符的旧版docker-compose是Python写的独立工具,已经在2023年7月停止维护,现在应该用作为Docker CLI插件的docker compose。
权威参考资料
本文标题:《Docker Compose发布端口后,你写的那条ufw规则一次都没被执行过》
本文链接:https://zhangwenbao.com/docker-compose-published-ports-ufw-bypass-defaults.html
版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0