1Panel 部署半年多之后,我第一次撞上“系统盘空间不足”的提示,是在一个周五下午。当时面板还在正常打开,但点开容器管理已经明显变卡,后来查了df -h,/根分区占用 98%,而 Docker 的可达数据区还不到 5GB。问题出在我一直默认系统盘够用,忽略了 Docker 的默认根目录/var/lib/docker才是所有镜像、容器层、命名卷的家。这篇文章我会把我后来在 1Panel 上做 Docker 容器存储路径迁移的完整过程写出来,包括迁移前怎么盘点、面板自动迁移和手动 daemon.json 修改两条路线、迁移后的常见故障和容量优化,适合正在用 1Panel、又觉得系统盘快撑不住的人照着走一遍。
1. 这次迁移到底在解决什么问题
1.1 容器跑起来之后,系统盘为什么永远不够用
大多数 VPS 或者物理机装系统的习惯是给/分配 30G 到 60G,剩下的空间全部留给数据盘。这个思路在传统部署时代问题不大,Nginx、PHP、MySQL 装完之后,代码可以很自然地放在数据盘。但用了 Docker 之后情况完全不一样:Docker 不会问你“镜像放哪里”,也不会问“卷放哪里”,默认全部集中在/var/lib/docker/。1Panel 作为一款典型的面板,无论是通过应用商店安装 OpenResty、MySQL、Redis,还是通过编排功能部署任意 Compose 项目,底层全部走 Docker,所以这些数据都会进系统盘。
具体到目录结构,值得你重点关注的就这么几个:
/var/lib/docker/overlay2/:容器可写层和镜像层,体积增长最凶,很多镜像明明只有几百 MB,多迭几层后一个项目能占到几个 G。/var/lib/docker/volumes/:命名卷。MySQL、PostgreSQL、Redis 的持久化数据在这一块。/var/lib/docker/containers/:容器的配置和日志。日志不轮转的时候,这个目录会悄悄长大。/var/lib/docker/image/:镜像元数据和层索引,拉取的镜像越多占用越高。
我见过一台只跑了 5 个容器的服务器,半年之后/var/lib/docker涨到 18GB,其中 MySQL 卷占了一大半,Nginx 的 stdout 日志又堆了 3 个 G。你如果只看 1Panel 首页的「容器」列表,根本发现不了问题,因为容器数量少不代表占用小。系统盘一满,Docker 就会开始报错,常见表现是容器无法创建、无法 pull 镜像,甚至已经运行的容器写日志都报 “No space left on device”。这种时候的第一反应通常是清理,清理本身没有错,但清理完过一阵子还会再满,因为根因是存储路径没挪走。
1.2 迁移、扩容、重新部署,三条路怎么选
遇到系统盘紧张,能走的路其实有三条,我分别说下适用场景。
第一条就是本文要讲的迁移存储路径。核心是把 Docker 的根目录从/var/lib/docker改到一个容量更大的数据盘挂载点,比如/data/docker。这条路的好处是容器、镜像、卷原封不动整体搬家,业务连续性看得到,适配 1Panel 这种“面板本身也跑在容器里”的架构最稳妥。
第二条是扩容系统盘。如果你的云厂商支持直接把根分区扩容量,这个操作确实最省心。但问题在于很多场景下系统盘规格已经买小了,扩容意味着重新购买或调整配额,而且小型 VPS 扩容之后需要重启实例,重启侧的连锁反应也不小。系统盘实在没有扩容空间,或者扩容费用明显不划算时,不建议硬扩。
第三条是重新导出导入容器,也就是把镜像 commit 出来、数据卷拷贝出来,换台机器重新部署。这只适合本来就想顺手重新梳理环境的场景。因为容器的环境变量、网络配置、绑定挂载都需要手动重建,数据很容易丢,整个过程堪比“搬家用三轮车分十趟拉”,不推荐为了单纯换存储路径去做。
我的建议很明确:如果新机器或现有机器上有一块独立的数据盘,而你只是容量吃紧,首选一定是一次性把/var/lib/docker迁到数据盘。系统盘继续留给操作系统,数据盘承接 Docker 所有数据,二者职责分离之后,比继续清理省心得多。
2. 动手前的三份清单:容器、卷与主机磁盘
2.1 先用 1Panel 盘点容器与存储占用
我已经见过不少朋友上来就改daemon.json,结果容器全部找不到了。根源不是命令写错,而是没搞清楚“哪些数据在 Docker 根目录里,哪些数据根本不归 Docker 管”。动手之前一定要做一次盘点。
打开 1Panel 的「容器」页面,逐项看这些信息:
- 容器名字、镜像名、创建时间:确认哪些是 1Panel 应用商店装出来的,哪些是自己用 Compose 或命令行建的。
- 挂载卷:面板的容器详情页会显示挂载点。如果挂载的是
/var/lib/docker/volumes下的命名卷,那这次迁移会带上它;如果挂载的是/home/www、/data/mysql这类主机绝对路径,那它是绑定挂载,数据本来就在宿主机磁盘的某个目录上,根目录迁移对它没有影响。 - 重启策略:容器是
always、unless-stopped还是no。迁移过程中 Docker 服务要重启,重启策略为always的容器会在 Docker 启动后自动拉起,这个特性后面要利用好。
命令行层面可以再补一条:
docker system df这个命令会告诉你镜像、容器、本地卷、构建缓存分别占了多大空间,是判断“到底谁把系统盘吃满”的最快方式。如果docker system df显示构建缓存都占了 5 个 G,那迁移后优化空间是非常可观的;如果数据卷本身就有 30G,那说明这次迁移本来就是刚需。
2.2 系统盘和数据盘的真实容量判断
用df -h先看清楚分区挂载:
df -h输出里关注/和/data(或者你打算放 Docker 数据的其他挂载点)的使用率。很多云厂商默认给数据盘起名/dev/vdb或者/dev/sdb,但你可能还没格式化挂载,这时候用lsblk -f看:
lsblk -f如果数据盘显示有文件系统但没挂载,先挂载到/data;如果显示没有任何文件系统,需要先格式化。格式化前一定确认盘里没有旧数据,这个操作不可逆。
挂载完之后,我还建议把挂载写进/etc/fstab,否则服务器重启后数据盘不会自动挂载,Docker 启动时会发现/data/docker目录不存在,然后直接在原位置新建一个空目录,看起来像“容器全没了”。我自己遇到过这个坑,重启之后数据库容器目录还在,就是卷数据读不出来,排查半天才发现是数据盘没有自动挂载。写 fstab 时尽量用 UUID 而不是/dev/vdb这种设备名,因为设备名在重启后可能变化:
# 先查 UUID blkid /dev/vdb # /etc/fstab 里加一行,按你自己的 UUID 和路径替换 UUID=xxxx-xxxx-xxxx /data ext4 defaults 0 22.3 迁移窗口里哪些容器必须停,哪些可以晚点处理
迁移存储路径意味着 Docker 服务本身需要停止一段时间,所以这不是一个“随时能在线操作”的流程,需要当成一次小型的维护窗口。停机窗口内 1Panel 面板也会不可访问,因为 1Panel 自身也是由容器启动的,Docker 停了它自然就停了。提前跟一起用这台服务器的人打个招呼,或者选择业务低峰期操作,这比任何技术细节都重要。
具体到容器怎么处理,分两种情况:
- 数据库、消息队列这类有状态服务,迁移前建议先执行优雅停止。以 MySQL 容器为例,直接
docker stop会比强杀更安全,InnoDB 有机会做 checkpoint。 - 无状态业务容器,比如 Nginx 前端、应用服务,可以随后停。甚至如果迁移速度够快,很多容器在 rsync 阶段不需要逐个停,只要在 Docker 服务停止前让它们“安静”下来就行。
如果容器设置了--restart always,一定要记住这个特性,Docker 服务启动后它们会自动恢复,不一定要手动逐个启动。不想让某些容器自动起来的,可以在迁移窗口前先执行docker update --restart no <容器名>,迁移完成之后再改回always。
同时,1Panel 本身的数据和配置值得你顺手备份一份。面板安装目录默认是/opt/1panel,把里面的配置和数据库复制一份到数据盘,迁移时无论出什么问题都有回头路。
3. 路径迁移主流程:面板自动迁移与手动双方案
3.1 方案 A:走 1Panel 的 Docker 存储目录迁移(自动)
如果你的 1Panel 版本新一点,面板里其实已经内置了 Docker 存储目录的迁移能力,不需要敲命令行。这是最省事的一条路,适合对命令行不熟悉或者想尽量避免误操作的人。
进入路径大致在「设置」→「容器设置」或「Docker 设置」,不同版本的菜单名略有差异,一般都能找到「存储目录」或者「Docker 存储目录」这样的字段。界面里会显示当前路径,通常是/var/lib/docker,你需要把它改成新路径,比如/data/docker,然后点保存或迁移。
保存之后面板会进入一个“迁移中”的状态,这个阶段你可能会看到 1Panel 页面刷新不了或者反复转圈,这是正常的,因为面板实际在后台做这些事:
- 停止所有容器(包括 1Panel 自身容器)
- 停止 Docker 服务
- 把
/var/lib/docker整个目录复制到新路径 - 更新 Docker 配置
- 启动 Docker 服务
- 拉起之前配置了自动重启的容器
整个过程其实就相当于把下面要讲的手动方案做了一遍封装,但体验好很多。注意迁移过程中千万别手动刷新页面或 SSH 重启服务,否则很容易复制到一半断掉。
如果保存之后提示失败,多半是目标路径不可写、目标磁盘空间不足、或者路径输入了不存在的目录。先在命令行用mkdir -p /data/docker创建好目录并确认权限,再回面板重试。面板方案对大多数人来说已经够用了,我身边有不少同事就是用这种方式完成的迁移。
3.2 方案 B:手动修改 daemon.json + rsync
手动方案依然有不可替代的价值:一方面不是所有 1Panel 版本的界面都自带迁移功能,另一方面手动操作你能知道每一步发生了什么,排错会更从容。这也是我更习惯的一套流程。
Step 0:准备目标目录
mkdir -p /data/dockerStep 1:按上一步的盘点结果,优雅停容器
如果你全部容器都能接受重启,直接全部停掉:
docker stop $(docker ps -q)不想全部都停的,至少要把数据库、消息队列这种写数据的容器先停了,无状态容器可以留到最后。
Step 2:停掉 Docker 服务
systemctl stop docker停 Docker 之后,1Panel 面板也跟着不可访问了,别慌。确认 Docker 已停止:
systemctl status dockerStep 3:用 rsync 做第一次同步
为什么推荐rsync而不是cp?因为 Docker 根目录里有海量的 overlay2 层,里面有大量小文件和符号链接,rsync 能更好地保留权限、时间戳、符号链接,而且支持断点续传和增量同步。第一次同步命令:
rsync -aXS --numeric-ids /var/lib/docker/ /data/docker/解释一下这几个参数:
-a:归档模式,保留权限、时间戳、符号链接等属性。-X:保留 xattr 扩展属性,某些容器镜像和 overlay 层会用到。-S:识别稀疏文件,避免把稀疏文件复制成完整大文件。--numeric-ids:保持 uid/gid 数字,防止在不同系统间同步时用户映射错乱。
这条命令执行完,回报一个总传输量。数据量大时等就完事了,1 个 G 和 30 个 G 速度完全不同,但 rsync 的可靠程度比cp高。
如果你在首次同步之后又启动过 Docker 或容器,重新生成了一点数据,可以在一切停稳后跑一次增量同步,把新产生的小量数据也带过去:
rsync -aXS --delete --numeric-ids /var/lib/docker/ /data/docker/注意--delete是有风险的,它会让目标目录里存在但源目录里不存在的文件被删除。只在第二次增量同步时用,第一次不要加。
Step 4:修改 daemon.json
编辑 Docker 配置文件:
vim /etc/docker/daemon.json如果文件本来就有内容,比如配置过镜像加速,不要覆盖原内容,只加一个字段:
{ "data-root": "/data/docker" }如果原本是空的,建议直接把我常用的完整配置贴上:
{ "data-root": "/data/docker", "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }其中日志轮转的配置后面会详细讲,迁移时一并加上可以省掉一次重启。
Step 5:启动 Docker 服务并验证
systemctl start docker systemctl status docker启动成功后验证 Docker 根目录是否为新的路径:
docker info --format '{{.DockerRootDir}}'如果输出/data/docker,说明配置生效了。再检查原来所有容器是否都还在:
docker ps -a docker volume ls卷列表里应该还是原来的那些卷名,并且 Volume Name 下面的挂载点到数据盘上去找,确实能看得到数据。容器虽然显示 exited,但那是正常的,因为还没启动。
Step 6:按依赖顺序启动容器和服务
如果你没有使用--restart always,手动启动:
docker start <容器名>1Panel 自身容器一般会随 Docker 自启,如果没起来,先在容器列表里找到1Panel这个名字再启动即可。数据库先起,业务容器再起,起完用docker ps检查状态。
Step 7:关于旧目录的处理
手动迁移完成后,旧目录/var/lib/docker只是不再被使用,它还在系统盘上占着空间。我强烈建议不要立刻删,先更名为:
mv /var/lib/docker /var/lib/docker.bak这样即使新路径出了问题,你还能一秒切回去。等新路径稳定运行一两周,再真正删除备份目录释放空间。系统盘空间紧张时,很多人在这里会心痒,但我见过太多迁移后第四五天发现少了一个卷、需要回滚的例子,留着旧目录就是留后悔药。
手动方案还有一个额外注意点:CentOS / RHEL 以及部分基于 SELinux 的系统,新路径可能需要重新打容器文件标签,否则启动容器或访问卷时会遇到 Permission Denied。执行:
chcon -R -t container_file_t /data/dockerUbuntu / Debian 默认没有 SELinux,忽略即可。
4. 迁移完成后的常见故障,哪些和路径有关哪些无关
4.1 容器消失、权限报错与 SELinux 问题
迁移之后最吓人的现象就是“容器全没了”,命令行执行docker ps -a只返回一个空表。这种情况几乎只有一个原因:daemon.json里的>docker system df
输出里能看到四个维度的占用:Images、Containers、Local Volumes、Build Cache。如果 Build Cache 很高,说明平时构建或拉取镜像积累了中间层,可以放心清理。
清理时我的建议是分两步走。第一步只清悬空镜像和构建缓存,不要清“所有未使用的镜像”:
docker system prune这个命令会删除悬空镜像(即没有被任何容器使用的、没有打 tag 的镜像)、停止的容器、无用的网络和构建缓存,比较温和。
第二步,确认整个环境里确实不需要保留的镜像,再执行:
docker system prune -a加了-a的清理会比较猛,会把所有没有被运行中容器使用的镜像都清理掉,包括一些你可能只是临时拉下来看一眼的镜像。执行前一定仔细看命令行提示列出的大小和数量,别手一抖全没了。
1Panel 本身也提供容器/镜像的清理入口,位置在「容器」→「镜像」或「清理」相关的页面,可以一键扫描并清理悬空镜像。不过命令行方式的可见性更强,我习惯用命令行确认,再回面板操作。
5.2 Docker 日志默认不轮转,这是磁盘杀手
很多容器跑着跑着,镜像没多大,但磁盘飙红,罪魁祸首是 Docker 默认的日志驱动json-file。默认情况下,容器输出到 stdout/stderr 的内容会无限制地追加到/var/lib/docker/containers/<容器ID>/<容器ID>-json.log,Nginx 的 access log、Java 应用的 console log,只要不轮转,日志文件能长到几十个 G。
迁移后是配置日志轮转最好的时机,因为容器数据和路径稳定了,再改一次配置比迁移途中改要安全得多。在/etc/docker/daemon.json里加上:
"log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" }含义是单个容器日志最大 10MB,保留最近 3 个日志文件,超过就轮转。这个配置对之后新创建的容器生效,对已经创建的容器不生效。已经在运行的容器如果想立刻套用,需要重新创建容器。在 1Panel 里,如果你是从应用商店安装的容器,可以通过「容器」→「编辑」或「重新创建」按钮操作,记得保留原有挂载和环境变量,否则会丢数据。
已经长到好几个 G 的旧日志文件,可以临时清空,不需要删除文件:
truncate -s 0 /var/lib/docker/containers/<容器ID>/*-json.logtruncate直接把文件“截断”成 0 字节,比rm安全,不会影响正在写日志的容器。但这不是长久之计,最终还是要靠日志轮转。
5.3 命名卷与绑定挂载的选择余地
完成这次迁移之后,你再创建新的容器时,应该更有意识地考虑数据放在哪。Docker 的数据持久化有两种主流方式,我把它们放在一起对比:
| 类型 | 存储位置 | 备份方式 | 迁移行为 | 适用场景 |
|---|---|---|---|---|
| 命名卷 | Docker 根目录下的/volumes | 需要复制卷目录 | 跟随 Docker 根目录整体迁移 | 临时数据、不太需要直接访问的中间数据 |
| 绑定挂载 | 宿主机任意绝对路径 | 直接复制路径内容 | 不受 Docker 根目录迁移影响 | 数据库、上传目录、配置文件 |
在 1Panel 应用商店部署容器时,很多应用默认会创建命名卷。命名卷的优势是 Docker 自己管权限,备份时复制/var/lib/docker/volumes目录即可;缺点是不够透明,如果你建了多个同名项目,时间长了很难一眼看出某个卷属于哪个应用。
绑定挂载的优势则是路径一目了然,比如你把 MySQL 数据放在/data/apps/mysql,备份、排查、扩容都很直观。对于有状态的核心业务数据,我建议尽量使用绑定挂载,把你的数据盘路径规划得清晰一些,比如:
/data/apps/mysql /data/apps/redis /data/apps/nginx-www /data/apps/backup这样即使以后某天又要迁移 Docker 根目录,核心业务数据的持久化路径不受影响,你只需要把数据盘挂到新机器,就能很快恢复服务。命名卷虽然方便,但会随着 Docker 根目录一起跑,路径迁移时牵一发动全身,适合放那些“丢了也不太心疼”的辅助数据。
结合这次的迁移经验,我对 1Panel 用户有一个明确建议:如果之前用的是命名卷,短期内没必要立刻改成绑定挂载,毕竟迁移已经完成了,没必要为了形式再造一次重构;但从现在开始,新建有状态应用时给数据路径做好规划,后面维护会轻松很多。
最后再分享一个我个人的操作习惯:新路径稳定之前,我不会删任何旧数据,也不会做过于激进的prune -a清理。等到新路径运行一周、日志和数据都验证无误后,再把/var/lib/docker.bak删掉,把那些确认不需要的镜像清掉。毕竟存储迁移这种事,一次求稳,后续求轻,这个顺序反了,麻烦的只会是你自己。