"服务器磁盘突然 100%,网站挂了"——这是运维群里出现频率极高的场景,而罪魁祸首里,Docker 容器日志占大头。默认情况下,Docker 用 json-file 驱动记录容器里进程输出到标准输出的每一行,并且不设上限,日积月累就有单个日志文件大到几 GB、十几 GB 的情况。更糟的是,很多人第一次遇到这个问题时手忙脚乱地删容器、删日志,反而丢了现场或误删数据。这篇文章讲清 Docker 日志的存储机制,给出"事前预防"(配置全局与容器级日志轮转)和"事中清理"(安全地释放空间)两套方案,并明确哪些删除操作有风险。目标是让你以后不再被日志撑满磁盘搞崩服务器。
一、先搞懂 Docker 日志是怎么存的
Docker 默认的日志驱动是 json-file。容器里进程写入 stdout/stderr 的每一行,都会被 dockerd 收集,以 JSON 格式追加写入宿主上的日志文件。文件位置在:
# 容器日志文件的实际路径(<容器ID> 是完整 ID)
ls -lh /var/lib/docker/containers/<容器ID>/<容器ID>-json.log
# 快速找出占用最大的几个容器日志
du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -rh | head -10
# 用 docker 自己的视角看各容器日志大小
docker ps -s # 看容器可写层大小(不含日志)
特点很清楚:json-file 驱动默认不轮转、不限制大小,只要容器在跑、只要有输出,文件就一直长大。这就是"日志撑满磁盘"的根本原因。理解了这一点,解决思路就两个方向——要么限制它长多大(事前配置),要么把它清掉(事中处理)。
先看现象确认是不是日志的锅:
# 看磁盘整体使用
df -h
# 看 Docker 各类资源占用
docker system df -v
# 揪出最大的日志文件
du -ah /var/lib/docker/containers 2>/dev/null | sort -rh | head -20
二、事前预防:配置日志轮转
最稳妥的做法是一开始就把日志上限配好。Docker 支持两处配置:全局的(对所有新容器生效)和容器级的(针对单个容器)。
全局配置(推荐)
在 /etc/docker/daemon.json 里配置默认的日志驱动和上限:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
参数含义:max-size 是单个日志文件的上限,超过就轮转(生成 .1、.2 后缀);max-file 是最多保留几个文件(含当前文件)。上面的配置意味着:单个日志最大 100 MB,最多保留 3 个,也就是一个容器最多占用约 300 MB 日志。这个量级对绝大多数服务够用,也能有效防止磁盘被打满。
改完要重载并重启 Docker:
sudo systemctl daemon-reload
sudo systemctl restart docker
风险提示:重启 Docker 会停止所有容器(取决于配置和 restart 策略,通常会停再拉起),务必在业务低峰期操作,并确认容器设置了 restart 策略以便自动恢复。同时,全局配置只对"新建的容器"生效,已经运行的老容器不会自动应用新配置,需要重建容器才生效——重建前确认数据在卷里,不会丢。
容器级配置
个别容器输出量特别大(比如访问日志很密的 Nginx),可以单独给它更严的限制,而不必全局收紧:
docker run -d --name web \
--log-driver json-file \
--log-opt max-size=50m \
--log-opt max-file=5 \
nginx:1.25-alpine
容器级配置优先于全局配置。max-size 支持的单位有 k、m、g,写 50m 即 50 MB。
换成更省空间的驱动
如果不想让日志以 JSON 形式存两份(原始行 + 元数据),可以换驱动:
| 驱动 | 特点 | 适用 |
|---|---|---|
| json-file | 默认,docker logs 可用,体积较大 |
通用,需 docker logs 查看 |
| local | Docker 自研,压缩存储,体积更小,docker logs 可用 |
推荐替代 json-file |
| syslog | 转发到系统 syslog | 集中日志收集 |
| journald | 转发到 systemd journal | 系统统一管理日志 |
| none | 不记录 | 不需要日志的容器 |
local 驱动是很好的选择,它同样支持 max-size/max-file,但存储格式更紧凑:
{
"log-driver": "local",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
需要提醒的是:切换日志驱动会影响 docker logs 的行为和已有日志的读取,syslog/journald 这类驱动下 docker logs 可能不再直接可用(要去系统日志里看)。切换前评估好你依赖哪种查看方式。
三、事中清理:安全地释放空间
如果磁盘已经被撑满,需要立刻清理。下面按"从安全到危险"排列,危险操作都标注了风险。
方式一:用 Docker 命令清理(推荐)
# 清理已停止的容器、未使用的网络、悬空镜像、未使用的构建缓存
docker system prune
# 更激进:连未使用的镜像和卷一起清(危险,见下方提示)
docker system prune -a --volumes
# 只清构建缓存
docker builder prune
# 只清未使用的卷(危险:会删除卷里的数据)
docker volume prune
风险提示:docker system prune -a 会删除所有未被容器使用的镜像,下次启动可能需要重新拉取;--volumes 会删除未被使用的卷,里面的数据不可恢复。执行带 -a 或 --volumes 的命令前,务必先用 docker volume ls、docker images 核对,确认没有还需要的数据。千万别在生产机器上不假思索地跑 prune -a --volumes。
方式二:truncate 单个日志文件(慎用)
日志文件是正在被 dockerd 写入的,直接 rm 删除文件并不会释放空间——因为文件句柄还被进程持有,磁盘空间要等进程关闭句柄才回收。正确做法是用 truncate 把文件清空:
# 把指定容器的日志文件截断为 0 字节(释放空间,文件保留)
# 注意:这是危险操作,会永久丢失该日志的历史内容,先确认不再需要
sudo truncate -s 0 /var/lib/docker/containers/<容器ID>/<容器ID>-json.log
风险提示:truncate -s 0 会永久丢弃该文件里的所有历史日志,且不可恢复。排查线上问题时日志是重要证据,执行前务必确认这些日志不再需要,或已经备份。另外,rm 后再 truncate 是无效的(文件都没了),正确顺序是先判断清楚再用 truncate 而非 rm。还有一种情况:如果你已经用 rm 删过,空间可能没释放,需要重启对应容器或重启 Docker 让句柄释放。
方式三:日志轮转工具
对 json-file 驱动,Docker 自身不提供外部 logrotate,但可以用系统 logrotate 对日志文件做轮转(注意与 Docker 的 max-size 机制不要冲突,通常二选一即可)。更推荐直接用第二节的 max-size/max-file,因为它由 Docker 管理、最不容易误伤。
如果确实要接 logrotate,思路是配一个规则定期 rotate、压缩、删除旧文件,并在 postrotate 里给 Docker 发信号重开文件。但这类自定义配置容易出错,建议只在有明确需求且有测试环境时采用。
四、磁盘监控:把问题挡在前面
日志清理只是补救,真正的解法是监控,让磁盘在到 80% 时就告警。几个实用做法:
# 方案 A:用 df 做简单阈值检查(放 cron 里,超阈值发告警/记录)
df -h /var/lib/docker | awk 'NR==2 {gsub("%","",$5); if ($5+0 > 80) print "磁盘告警: " $5 "%"}'
# 方案 B:监控单个容器日志增长速度
du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -rh | head -5
# 方案 C:接入监控系统(Prometheus node_exporter + 告警规则)
# /var/lib/docker 所在分区的可用空间、inode 使用率都建议做告警
经验阈值:磁盘使用率 80% 告警、90% 严重告警;同时监控 inode 使用率(大量小文件也会把 inode 耗尽,df -i 查看)。inode 用满的表现和磁盘满很像,都会导致无法写文件,排查时别忘了看。
df -h # 看空间
df -i # 看 inode
docker system df -v # 看 Docker 层面的占用明细
五、一份可落地的日志治理清单
把上面的内容整理成一套可执行的做法:
| 步骤 | 动作 | 说明 |
|---|---|---|
| 1 | 配置 daemon.json 的 max-size/max-file | 全局兜底,防患于未然 |
| 2 | 对高输出容器单独收紧限制 | 如 Nginx 访问日志单独设更小上限 |
| 3 | 应用自身控制日志级别 | 生产别开 debug,减少无效输出 |
| 4 | 关键日志集中收集 | 用 syslog/日志系统收集,本地只留少量 |
| 5 | 磁盘与 inode 监控告警 | 80% 告警,提前介入 |
| 6 | 定期清理悬空资源 | docker system prune(不含 -a --volumes) |
| 7 | 紧急时 truncate 而非 rm | 并做好"日志不再需要"的确认 |
第 3 条常被忽视但很关键:如果应用把 debug 日志全打开,日志量可能是正常时的几十倍。治本要从源头的日志级别控制做起,容器层面的限制只是兜底。
常见问题
Q:为什么我删了日志文件,磁盘空间没释放?
因为文件句柄还被 dockerd 持有。用 rm 删除后空间不会立即回收,要等进程关闭句柄(如重启容器或 Docker)。正确做法是用 truncate -s 0 清空内容,而不是删除文件本身。
Q:配置了 max-size 后老容器还是很大?
全局配置只对新建容器生效。已经在运行的老容器需要重建(docker rm 后重新 run,或用 Compose up -d --force-recreate)才会应用新配置。重建前确认数据在卷里。
Q:docker system prune -a 能随便用吗?
不能。它会删掉所有未被当前容器使用的镜像,下次启动要重新拉取,离线环境可能直接拉不到。带 --volumes 还会删卷数据。生产机器上执行前必须先看清将删除什么。
Q:local 驱动和 json-file 有什么区别?
local 是 Docker 自研的日志驱动,同样支持 max-size/max-file 和 docker logs,但存储更紧凑,通常占用更小。想省空间又不想改查看方式时,推荐切到 local。
Q:日志已经涨到几十 GB,最快怎么救急?
先 df -h 确认是日志导致,du 定位到具体容器;如果是可丢弃的日志,用 truncate -s 0 清空对应文件立即释放空间;然后立刻配置 max-size 防止再次发生。整个过程优先保住业务,别直接重启宿主机。
总结
默认的 json-file 日志驱动不设上限,这是容器日志撑满磁盘的根本原因;解决它要"事前限制"加"事后清理"两条腿走路。
事前在 daemon.json 配 max-size/max-file 做全局兜底,对高输出容器单独收紧,必要时把驱动换成压缩存储的 local;注意全局配置只对新容器生效,老容器要重建。
事中清理优先用 docker system prune(不含 -a --volumes);对正在写的日志文件,用 truncate -s 0 清空而非 rm 删除,否则空间不会释放。
所有删除类操作都有风险:prune -a --volumes 可能删掉需要的镜像和数据,truncate 会永久丢弃历史日志,执行前必须确认清楚,优先在低峰期操作。
治本还靠监控和应用自身的日志级别控制:磁盘与 inode 都做 80% 告警,生产环境别长期开 debug,让日志量从源头降下来,容器限制只作最后一道防线。
企业QQ咨询




