Docker数据目录迁移指南:从/var/lib/docker到数据盘完整实操

发布时间:2026/9/9 11:42:04
Docker数据目录迁移指南:从/var/lib/docker到数据盘完整实操 做Docker运维或者自己折腾服务器的人几乎都会遇到同一个问题磁盘满了。我用Docker跑了一堆服务镜像、容器、卷、构建缓存全堆在系统盘里结果/分区被撑到100%连ssh进去敲命令都卡。后来我仔细看了一眼才发现Docker默认把所有数据都放在/var/lib/docker也就是跟着系统盘走。系统盘一般就几十G根本经不起几个镜像折腾。那时候我第一反应是把整个系统盘扩容但云服务器扩系统盘流程麻烦还要重启实例。后来想明白最干净利落的办法其实是把Docker的root目录整体迁走放到数据盘或者容量更大的挂载点上去。这个文章我就把整个迁移过程、方案选型和踩坑记录完整写出来全程基于我实际操作的经历跟着做基本都能成。这个内容适合谁一类是跟我一样把Docker装在系统盘、后来被磁盘占用逼疯的人另一类是刚装好Docker、想一开始就把数据目录规划到数据盘上的新用户。不管你是哪种这篇都能给你一个可以直接复制的迁移路径。1. 迁移前先搞清楚为什么要动Docker的root目录1.1 Docker数据都藏在哪里Docker安装完成后默认的数据根目录是/var/lib/docker。这个目录下面分了好几个子目录各自存的东西不一样containers运行中的容器文件系统包括容器的可写层、配置、日志。image镜像的分层数据这是磁盘占用的绝对大头。volumesDocker volume里存放的数据数据库容器如果挂了持久化卷数据就在这里。overlay2容器运行时的联合文件系统层镜像被容器引用之后运行层也在这里。tmp、buildkit、network等临时文件、构建缓存、网络配置。平常我们用docker system df看到的镜像占用、容器占用、构建缓存占用最后加起来全都落在/var/lib/docker里。系统盘如果只有40G随便拉几个带系统镜像的依赖再跑两三个有数据卷的数据库磁盘瞬间就告急。1.2 不迁移的话会出什么事磁盘满了之后首当其冲的是构建和拉镜像。docker pull会直接报no space left on device构建镜像时中间层写不进去也会报错。比这更麻烦的是容器的日志文件——如果你没限制日志大小容器运行时间长了以后日志文件会一直在/var/lib/docker/containers/里涨直到占满磁盘。磁盘占满还会影响整个宿主机的稳定性因为系统临时目录、日志服务、包管理器这些都在同一个分区。我之前遇到过一次tmp目录写满结果ssh都登录不上只能走管理后台重启。所以Docker的root目录迁移本质上是个磁盘空间规划问题不是“闲着没事折腾”。1.3 什么时候做迁移最划算我的经验是两个最佳时间点刚装完Docker、还没拉几个镜像的时候。这时候数据量小迁移只要同步几百MB基本秒级完成。磁盘快满但还有少量余量的时候。比如还剩几个G空间可以完成一次数据同步。如果已经100%满了不仅同步麻烦连Docker本身都可能起不来。如果你属于后者而且空间已经一点不剩那得先手动清一些东西腾出空间比如docker system prune、删掉没用的镜像再走下面的迁移流程。别想着硬迁同步过程中需要临时空间空间为0就是死局。2. 三种主流的Docker root目录迁移方案对比2.1 软链接方案最古老也最省事方案思路把/var/lib/docker整个目录移动到新位置然后在新位置创建软链接到原路径。systemctl stop docker mv /var/lib/docker /data/docker ln -s /data/docker /var/lib/docker systemctl start docker这个方案的好处是Docker本身根本不知道路径变了因为/var/lib/docker还是一个有效路径只是它指向了新位置。很多老教程用的就是这个方法。但它有几个明显问题。第一mv命令如果跨文件系统实际上是先复制再删除数据量大的时候耗时很久而且如果在mv中途中断两边数据状态没法保证。第二软链接本身是个隐患有些审计工具、备份脚本会识别出这是个链接而不是真实目录万一哪天有人误删了软链接Docker就直接找不到数据了。第三如果未来要重启机器软链接依赖的挂载点必须提前挂载好否则Docker启动时数据还是找不到。2.2 修改daemon.json方案最推荐干净利落Docker守护进程本身支持通过配置文件修改数据根目录这个配置就是/etc/docker/daemon.json关键参数是>{ data-root: /data/docker }配置完成后重启Docker新数据就全部落到新位置。这种方案的优势在于结构清晰、配置稳定Docker原生识别新路径没有软链接这种“中间商”。后续做备份、迁移、扩容都只需要针对真实路径操作不会有什么歧义。docker-compose、docker desktop这些工具底层的daemon配置也是走同一个机制。比如你本机装了Docker Desktop在设置界面里其实也有一项可以调整磁盘镜像位置本质就是改data-root。Linux服务器上没有图形界面手动改daemon.json是最直接的方式。2.3 直接挂载数据盘到/var/lib/docker适合新装场景如果你的服务器有一块独立数据盘而且刚好没想好挂到哪可以直接把这块盘挂到/var/lib/docker。比如mkfs.ext4 /dev/vdb mount /dev/vdb /var/lib/docker这样Docker默认路径不变但底层的存储实体已经换到了新盘。这种方案的好处是路径意义清晰/var/lib/docker看起来跟默认完全一样。缺点是只适合新装或者低负载场景——如果原目录里已经有大量数据你得先把旧数据搬到新盘再挂载这个操作序列和迁移一样麻烦那就不如直接改daemon.json了。2.4 我对三套方案的最终选择在我自己的机器上最终选的是修改daemon.json。原因很简单不依赖软链接、迁移逻辑清晰、配置可持久化。就算以后换机器、换磁盘只要复制整个data-root目录再在新机器上改同一个配置项数据就无缝接上了。提示不管选哪个方案迁移前都建议先做一次数据完整性确认尤其是正在运行数据库容器的机器。最理想的情况是停掉容器再迁避免文件写入过程中出现数据不一致。3. 动手迁移前必须确认的五件事3.1 确认目标磁盘确实挂载了以及文件系统类型我们要把数据迁到哪得先确认这个位置的真实情况。用df -h看一下[rootserver ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/vda1 40G 38G 0G 100% / /dev/vdb 200G 10G 190G 1% /data从上面输出能看到/已经100%满了而/data还有190G可用。那就把目标目录定为/data/docker。还要确认文件系统是ext4还是xfs。Docker的数据目录对文件系统类型没有强制要求但这会影响后续的性能和配额配置比如xfs支持pquota可以做磁盘配额。用df -T就能看到类型。[rootserver ~]# df -T /data Filesystem Type Size Used Avail Use% Mounted on /dev/vdb xfs 200G 10G 190G 1% /data3.2 确认当前Docker数据占用了多少空间迁移前先给数据量摸个底。用du直接统计目录大小du -sh /var/lib/docker正常情况下这个输出会接近docker system df里的总占用。如果差异特别大要检查一下是不是有容器日志占用了一大块或者构建缓存占了不少。还需要用docker system df看下Docker自己统计的各类占用[rootserver ~]# docker system df TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 12 5 3.521GB 1.812GB (51%) Containers 8 3 220.4MB 178MB (81%) Local Volumes 5 3 1.781GB 1.287GB (72%) Build Cache 24 0 2.182GB 2.182GB这里的信息非常关键。如果Images和Build Cache占比很高迁移后系统盘空间会直接释放一大截如果Local Volumes占大头说明有容器挂了很大的持久化卷迁移后务必要验证卷数据是否完整。3.3 确认Docker配置里有没有其他路径依赖有些人的daemon.json里可能已经配置了graph、docker-root等旧字段。graph是老版本Docker里的参数名新版本里已经淘汰了统一用>setenforce 0如果setenforce 0之后问题消失那基本就能断定是SELinux的上下文问题。解决办法是用chcon或者semanage fcontext给新目录设置与/var/lib/docker相同的标签。4. 完整迁移过程从备份到验证的详细操作4.1 停掉Docker服务和所有相关容器迁移的第一步是停止Docker服务这一步没得商量。为什么必须停因为如果Docker还在运行/var/lib/docker里的文件会不断被写入我们同步到一半的数据就会出现不一致拷贝完成后Docker进程认为数据还是完整的但实际数据库文件可能已经处于中间状态。systemctl stop docker如果你的机器上还有其他容器编排工具比如docker-compose在跑建议先停掉compose项目再停Docker这样容器会有优雅终止的机会docker compose -f /path/to/docker-compose.yml down systemctl stop docker其实Docker服务停止后所有容器进程也会被强制终止相当于容器直接断电。直接停Docker的问题在于容器内的应用可能来不及做正常的退出处理比如数据库来不及刷新缓存。所以稳妥起见如果你有状态服务MySQL、Redis、PostgreSQL先停服务更稳妥。另外还要确认Docker服务真的停掉了不能只看systemctl输出。因为有些环境下Docker用了socket激活或者其他守护方式挂起直接看进程是否存在更稳妥ps -ef | grep dockerd systemctl status docker如果进程还在就得kill掉残留进程再继续。4.2 拷贝数据推荐rsync而不是直接用mv很多人迁移时习惯用mv /var/lib/docker /data/docker。这里有个很大的坑如果新旧目录在同一个文件系统上mv只是改个名字瞬间完成但如果跨文件系统mv的行为其实是“复制到新位置然后删除原位置”和cp没有本质区别。更致命的是mv在跨文件系统拷贝时中途出错或者被中断是不会保证数据完整性的它从头到尾就是一个缺乏断点续传能力的复制。所以我强烈推荐用rsync来做这一步。它的优势是支持断点续传、进度显示、校验和对比还可以保留权限、属主、时间戳、硬链接等属性。同步命令mkdir -p /data/docker rsync -avxP --numeric-ids /var/lib/docker/ /data/docker/解释一下这些参数的含义-a归档模式保留权限、属主、时间戳、软链接等所有属性。这一步很关键Docker对文件属主和权限是有要求的比如很多目录和文件是root拥有漏掉权限同步会导致容器启动异常。-vverbose输出详细信息方便观察进度。-x不跨文件系统。这个参数我特意加上是为了防止/var/lib/docker下如果挂了其他独立挂载点rsync会误把挂载点里的内容也同步过去。-P等于--partial --progress显示进度并且支持断点续传。--numeric-ids以数字形式保留UID/GID避免不同机器上用户映射不一致导致属主错乱。同步过程中如果数据量大输出会很长建议用screen或者tmux挂在后台跑防止ssh断开导致rsync中断。中断了也没关系重新执行同一个rsync命令它只同步差异部分不需要从头再来。同步完成后用以下命令做一次快速对比校验rsync -avxn --numeric-ids /var/lib/docker/ /data/docker/加一个-ndry-run模拟执行参数如果输出为空说明源目录和目标目录的文件完全一致没有任何差异文件。这一步可以在不真正执行同步的情况下检查两边是否一致。注意rsync -avxn只对比文件名、大小、时间戳等元数据没有实际对比文件内容。如果对数据完整性要求极严可以用-c参数进行逐文件校验和对比但是这会消耗大量时间。常规迁移用-n做元数据校验足够。4.3 修改Docker配置文件data-root数据拷贝完成后修改Docker的配置文件。用vim打开/etc/docker/daemon.jsonvim /etc/docker/daemon.json如果文件本来就不存在那就是新创建一个。一个完整的配置示例{ data-root: /data/docker, log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }这里我额外加上了日志大小限制这是我踩过坑之后强烈建议加上的配置。容器日志如果不限制大小长时间运行后会累积几个G甚至几十个G把磁盘撑爆。限制到单文件10M、最多保留3个日志轮转会自动清理旧文件避免日志成为磁盘杀手。改完配置文件后先验证一下JSON格式是否正确python3 -m json.tool /etc/docker/daemon.jsonJSON格式出问题的话Docker服务启动会直接报错轻则服务起不来重则卡在启动循环里。多花十秒钟验证一下能省很多排查时间。4.4 启动Docker并验证迁移结果配置完成后启动Dockersystemctl start docker systemctl status dockerDocker启动后用以下命令依次验证docker info | grep Docker Root Dir docker images docker ps -adocker info输出的Docker Root Dir字段会直接显示当前数据根目录[rootserver ~]# docker info | grep Docker Root Dir Docker Root Dir: /data/docker能看到/data/docker就说明Docker已经使用新目录了。然后再用docker images看镜像列表是否完整用docker ps -a看容器列表是否完整。之前建的容器、拉过的镜像、创建的卷都应该原封不动地出现在列表里。4.5 验证容器和数据卷的完整性只看到容器列表还不够尤其是数据库类容器必须真正启动一次然后检查数据内容是否正常。我以前迁移完MySQL容器后列表里显示容器存在但是容器启动后数据库一直连不上最后发现是卷里的数据文件有权限问题。验证方法其实很简单docker start 容器名 docker logs --tail 50 容器名启动后看日志有没有报错。如果是MySQL可以进入容器执行简单的SQL查询如果是Redis就执行PING。确认业务数据没问题后才算是真正迁移完成。4.6 清理旧目录释放系统盘空间所有验证都通过之后旧目录/var/lib/docker里的数据就没有用了可以清理掉释放系统盘空间。这一步同样不建议直接rm -rf旧目录尤其是数据量很大的时候。因为删除文件本身也需要耗时中途如果出现问题可能导致目录残留。比较稳妥的方式是先重命名再删除mv /var/lib/docker /var/lib/docker.bak rm -rf /var/lib/docker.bak先重命名如果Docker运行一切正常过几天再彻底删除备份目录。万一迁移后有什么隐藏问题还能把旧目录改名回来快速回滚。提示旧目录改名之后建议观察至少24小时再删。迁移完短期内一切正常不代表持久卷里的数据一定没问题很多数据一致性问题会在容器重启或者写操作频繁时才暴露。5. 实战记录一台40G系统盘的服务器迁移全过程5.1 迁移前这台机器长什么样我有一台2C4G的云服务器系统盘40G数据盘200G系统盘被Docker塞满了。df -h看过去是这样的[rootserver ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/vda1 40G 36G 0.7G 98% / /dev/vdb 200G 30G 170G 15% /data/已经用到98%只剩0.7G随时可能爆。再看Docker的占用[rootserver ~]# docker system df TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 25 18 11.2GB 5.4GB (48%) Containers 14 10 521.4MB 420.1MB (81%) Local Volumes 8 6 26.4GB 2.1GB (8%) Build Cache 12 0 3.8GB 3.8GB (100%)总量加起来已经超过40G了但为什么实际占用是36G而不是40G因为docker system df统计的是逻辑大小实际磁盘块还要算上文件系统开销。反正结论是系统盘快爆了必须迁。5.2 执行的完整命令序列我把当时实际执行的命令整理成一份完整序列供参考# 1. 查看当前数据量 du -sh /var/lib/docker # 2. 停掉Docker systemctl stop docker # 3. 确认Docker进程已退出 ps -ef | grep dockerd # 4. 创建目标目录 mkdir -p /data/docker # 5. 使用rsync同步数据 rsync -avxP --numeric-ids /var/lib/docker/ /data/docker/ # 6. 对比源目录和目标目录差异 rsync -avxn --numeric-ids /var/lib/docker/ /data/docker/ # 7. 备份原配置文件 cp /etc/docker/daemon.json /etc/docker/daemon.json.bak # 8. 修改配置文件 vim /etc/docker/daemon.json # 9. 校验JSON格式 python3 -m json.tool /etc/docker/daemon.json # 10. 启动Docker systemctl start docker # 11. 检查Docker根目录 docker info | grep Docker Root Dir # 12. 检查镜像和容器列表 docker images docker ps -a # 13. 启动几个核心容器验证数据 docker start mysql docker logs --tail 30 mysql docker exec -it mysql mysql -uroot -p -e show databases; docker start redis docker exec -it redis redis-cli PING # 14. 确认一切正常后处理旧目录 mv /var/lib/docker /var/lib/docker.bak rm -rf /var/lib/docker.bak整套流程走下来Docker的root目录就从/var/lib/docker切到了/data/docker。系统盘从98%直接降到不到20%数据盘多占了40G。5.3 docker compose项目怎么处理如果项目里有用docker compose管理的一堆服务迁移后compose文件的配置基本不用动因为compose文件只是定义容器结构真正落地到磁盘的数据都在Docker root目录里。但有一点要注意compose项目里用到的named volume这些卷的数据存储路径在Docker root目录下的volumes文件夹里迁移后它们会跟着一起走。启动compose项目前建议做一次docker compose config检查配置确认没有使用宿主机绝对路径挂载。如果你的compose文件本来就是用- ./data:/app/data这种方式挂载宿主目录那这部分数据跟Docker root目录无关迁移不影响。5.4 迁移后磁盘空间有多少进账迁移完成后我再看df -h[rootserver ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/vda1 40G 6.2G 31.8G 17% / /dev/vdb 200G 70G 130G 35% /data系统盘从几乎爆满降到17%数据盘多了差不多40G的数据。后来我又写了个定时清理脚本定时执行docker system prune -af --filter until168h清理构建缓存和悬空镜像数据盘的占用也一直保持稳定。6. 迁移过程中最容易踩的五个坑6.1 容器启动失败指向卷文件权限错乱迁移后容器启动不了日志里报Permission denied或者单纯起不来优先查目录权限。rsync虽然用了-a参数保留了权限但如果你之前用的是mv跨文件系统拷贝某些目录的权限可能变了。排查方法ls -ldn /data/docker/volumes/* ls -ldn /var/lib/docker/volumes/*两边的权限和属主对比一下如果出现不一致用chown和chmod修正。特别是MySQL这类容器它对数据目录的权限非常敏感权限稍有不对就直接拒绝启动。6.2 Docker服务起不来检查daemon.json语法Docker服务启动失败最常见的原因就是daemon.json写错了。JSON文件里多了一个逗号、少了一个引号都会导致解析失败。这时候看日志journalctl -u docker --no-pager | tail -50日志里会明确提示daemon.json解析错误的位置。用python3 -m json.tool验证一遍格式正常后再重启。另外要注意daemon.json里的路径不要加尾斜杠比如/data/docker/和/data/docker虽然看起来差不多但不同版本Docker对尾斜杠的处理方式不一致统一不加尾斜杠最稳。6.3 同步数据时占用太高导致线上业务卡顿如果你的机器上还有其他服务在跑rsync同步大量数据时会占用比较高的磁盘IO和CPU。我试过一次全速同步直接把业务服务的IOPS拉满数据库查询延迟飙升。后来我给rsync加了限速参数把同步时间拉长但保证了业务不受影响rsync -avxP --numeric-ids --bwlimit50000 /var/lib/docker/ /data/docker/--bwlimit50000表示限制带宽为50MB/s实际效果取决于磁盘性能。如果你对IO特别敏感甚至可以把限速设成1000010MB/s慢慢同步但业务不断。6.4 磁盘空间明明够但rsync提示空间不足这种情况我真遇到过。目标磁盘明明还有几十G空间但rsync同步到一半提示No space left on device。查了半天发现是目标文件系统inode不够了。检查命令df -i /datadf -i看的是inode使用率。如果inode使用率是100%即使还有剩余空间也写不了任何新文件。解决办法是格式化目标盘时加大inode密度或者在目标盘上换一种文件系统比如xfs或者把数据拆分同步。如果数据已经写了一半需要先清理掉部分文件再继续。6.5 迁移后旧目录怎么都删不掉rm -rf /var/lib/docker理论上很快但如果目录里文件特别多删除也要花很久。我试过几万个文件删了十几分钟期间IO还被打满。更稳妥的做法是直接在系统盘上等它慢慢删或者用ionice降低删除操作的IO优先级避免影响其他服务。ionice -c 3 rm -rf /var/lib/docker.bak这个命令把删除进程的IO调度级别降为idle磁盘繁忙时会自动让出资源给其他服务。如果你的业务对IO很敏感这个技巧很实用。7. 迁移后如何确认一切正常验证清单完整跑完迁移流程后我会按下面这个清单逐项验证全部通过才算迁移成功检查项命令通过标准Docker根目录docker info | grep Docker Root Dir显示为新路径镜像完整性docker images迁移前存在的镜像全部在列容器列表docker ps -a迁移前存在的容器全部在列容器可启动docker start 容器容器状态变为Up卷数据ls /data/docker/volumes/卷目录齐全大小合理日志无报错journalctl -u docker --no-pager | tail -30无致命错误磁盘空间df -h系统盘空间明显释放数据内容业务侧验证数据库能查、缓存能读、服务能访问这个清单每次迁移我都会执行一遍不是为了走流程而是因为有一次我跳过了容器内容验证直接删了旧目录结果有个容器的卷数据其实没同步过来最后只能从备份里恢复。那次的教训很深刻所以现在每一步验证我都不会省。8. 如果迁移后想回滚怎么办虽然改daemon.json的方案很少需要回滚但万一新目录有问题回滚流程也很简单systemctl stop docker vim /etc/docker/daemon.json # 注释掉或删除>