Move Docker to Separate Drive on Ubuntu

{"time":1776378046141,"version":"2.28.2","blocks":[{"type":"paragraph","data":{"text":"Every Linux server failure I’ve witnessed from disk issues starts the same way: the root filesystem slowly fills up. No alarms. No drama. Just creeping usage until something breaks."}},{"type":"paragraph","data":{"text":"On this Ubuntu server, / was sitting at a seemingly safe percentage — but something felt off. A quick disk usage scan revealed the real problem:"}},{"type":"code","data":{"code":"/opt 141G"}},{"type":"paragraph","data":{"text":"A single directory consuming 141 GB on the root partition is a serious warning sign. On Docker hosts, the usual culprit is container storage — images, volumes, and build cache quietly accumulating under /var/lib/docker. Left unchecked, this growth will eventually take down your server."}},{"type":"paragraph","data":{"text":"Rather than wait for the root partition to fill and crash critical services, I made a structural fix: I moved Docker completely off the root disk. Here is exactly how I did it — and why every Ubuntu server running Docker should do the same."}},{"type":"header","data":{"text":"How Docker Uses Disk Space","level":2}},{"type":"paragraph","data":{"text":"By default, Docker stores everything under a single directory on the root filesystem:"}},{"type":"code","data":{"code":"/var/lib/docker"}},{"type":"paragraph","data":{"text":"This directory holds images, running and stopped containers, named and anonymous volumes, the build cache, and the overlay filesystem layers that make up each container’s writable layer. On an active system running multiple services, this storage can easily grow into hundreds of gigabytes without any obvious warning."}},{"type":"paragraph","data":{"text":"The core problem is that /var/lib/docker lives on the same partition as your operating system. When that partition fills up, the consequences cascade quickly: Docker stops launching containers, systemd cannot write state files, log rotation fails, and SSH may stop accepting new connections. In severe cases, the server becomes completely unresponsive and requires direct console access to recover — a nightmare scenario in production."}},{"type":"paragraph","data":{"text":"The fix is straightforward: move Docker’s data directory to a dedicated separate disk so that container storage growth can never starve the OS of the disk space it needs to function."}},{"type":"header","data":{"text":"The Approach","level":2}},{"type":"paragraph","data":{"text":"Instead of periodically pruning images and hoping for the best, the right long-term solution is to relocate Docker’s data root to a secondary disk. Docker supports this natively through a single configuration option — data-root — in /etc/docker/daemon.json. No third-party tools required."}},{"type":"paragraph","data":{"text":"In this case, the secondary drive was already mounted at:"}},{"type":"code","data":{"code":"/mnt/1TB"}},{"type":"paragraph","data":{"text":"The new Docker data path was set to:"}},{"type":"code","data":{"code":"/mnt/1TB/docker-data"}},{"type":"header","data":{"text":"Step-by-Step: Move Docker to Another Drive on Ubuntu","level":1}},{"type":"header","data":{"text":"Step 1 — Confirm the Secondary Drive Is Mounted","level":2}},{"type":"paragraph","data":{"text":"Before making any changes, verify that your secondary disk is mounted and configured to remain mounted after a reboot. Skipping this check is the most common cause of failed migrations."}},{"type":"paragraph","data":{"text":"Check current mounts:"}},{"type":"code","data":{"code":"df -h"}},{"type":"paragraph","data":{"text":"Confirm that your secondary disk appears at the intended mount path. Then verify it has a UUID-based entry in /etc/fstab so it mounts automatically on every boot:"}},{"type":"code","data":{"code":"cat /etc/fstab"}},{"type":"paragraph","data":{"text":"If the drive is not listed in /etc/fstab, add it before continuing. Without a persistent mount entry, Docker will fail to start after the next reboot — and you may lose access to your containers entirely."}},{"type":"header","data":{"text":"Step 2 — Stop Docker","level":2}},{"type":"paragraph","data":{"text":"Stop both the Docker daemon and its socket to ensure no containers are running and no processes are actively writing to /var/lib/docker. Copying live data can result in corruption."}},{"type":"code","data":{"code":"sudo systemctl stop docker\nsudo systemctl stop docker.socket"}},{"type":"header","data":{"text":"Step 3 — Create the New Docker Directory","level":2}},{"type":"paragraph","data":{"text":"Create the target directory on the secondary disk and apply the same ownership and permissions that Docker expects:"}},{"type":"code","data":{"code":"sudo mkdir -p /mnt/1TB/docker-data\nsudo chown root:root /mnt/1TB/docker-data\nsudo chmod 711 /mnt/1TB/docker-data"}},{"type":"header","data":{"text":"Step 4 — Copy Existing Docker Data","level":2}},{"type":"paragraph","data":{"text":"Use rsync to copy everything from the original directory to the new location. The -a flag preserves permissions, ownership, timestamps, and symlinks. The -P flag displays real-time progress, which is helpful given how large Docker data directories can be:"}},{"type":"code","data":{"code":"sudo rsync -aP /var/lib/docker/ /mnt/1TB/docker-data/"}},{"type":"paragraph","data":{"text":"Important: The trailing slashes on both paths matter. Without them, rsync will create a nested docker subdirectory inside the target instead of copying the contents directly into it — causing Docker to fail on startup."}},{"type":"header","data":{"text":"Step 5 — Configure Docker to Use the New Location","level":2}},{"type":"paragraph","data":{"text":"Edit Docker’s daemon configuration file:"}},{"type":"code","data":{"code":"sudo nano /etc/docker/daemon.json"}},{"type":"paragraph","data":{"text":"Add the data-root setting pointing to the new directory:"}},{"type":"code","data":{"code":"{\n \"data-root\": \"/mnt/1TB/docker-data\"\n}"}},{"type":"paragraph","data":{"text":"If daemon.json already contains other settings — such as GPU runtime configuration or logging drivers — merge the data-root key into the existing JSON object rather than replacing the file. Save and close when done."}},{"type":"header","data":{"text":"Step 6 — Rename the Old Docker Directory as a Backup","level":2}},{"type":"paragraph","data":{"text":"Rename — do not delete — the original directory. This preserves a rollback path in case anything goes wrong during the first restart:"}},{"type":"code","data":{"code":"sudo mv /var/lib/docker /var/lib/docker.bak"}},{"type":"header","data":{"text":"Step 7 — Start Docker","level":2}},{"type":"code","data":{"code":"sudo systemctl start docker"}},{"type":"header","data":{"text":"Step 8 — Verify the New Location","level":2}},{"type":"paragraph","data":{"text":"Confirm that Docker is now reading from the new data root:"}},{"type":"code","data":{"code":"docker info | grep \"Docker Root Dir\""}},{"type":"paragraph","data":{"text":"The output should show:"}},{"type":"code","data":{"code":"Docker Root Dir: /mnt/1TB/docker-data"}},{"type":"paragraph","data":{"text":"Then verify that your containers, images, and volumes all migrated successfully:"}},{"type":"code","data":{"code":"docker ps -a\ndocker images\ndocker volume ls"}},{"type":"paragraph","data":{"text":"If anything looks missing or Docker fails to start, stop the service, restore /var/lib/docker.bak to /var/lib/docker, and troubleshoot before proceeding."}},{"type":"header","data":{"text":"Step 9 — Clean Up After Verification","level":2}},{"type":"paragraph","data":{"text":"Once you have confirmed that everything is running correctly from the new location, remove the backup to reclaim space on the root partition:"}},{"type":"code","data":{"code":"sudo rm -rf /var/lib/docker.bak"}},{"type":"paragraph","data":{"text":"Do not rush this step. Run your containers for at least a full day — ideally through a complete reboot cycle — before deleting the backup. There is no recovering the backup once it is gone."}},{"type":"header","data":{"text":"Production-Safe Boot Protection","level":1}},{"type":"paragraph","data":{"text":"There is one critical issue that most migration guides overlook: boot order. If Docker starts before the secondary drive has finished mounting, it will silently recreate /mnt/1TB/docker-data as an empty directory on the root partition. Your containers will appear to have vanished, and you will be right back to filling up the root disk — except now the actual data is stranded on the secondary drive."}},{"type":"paragraph","data":{"text":"To prevent this, create a systemd drop-in override that explicitly tells Docker to wait for the secondary mount before starting:"}},{"type":"code","data":{"code":"sudo mkdir -p /etc/systemd/system/docker.service.d\nsudo nano /etc/systemd/system/docker.service.d/override.conf"}},{"type":"paragraph","data":{"text":"Add the following:"}},{"type":"code","data":{"code":"[Unit]\nRequiresMountsFor=/mnt/1TB"}},{"type":"paragraph","data":{"text":"Reload systemd to apply the change:"}},{"type":"code","data":{"code":"sudo systemctl daemon-reload"}},{"type":"paragraph","data":{"text":"With this override in place, Docker will not start until the secondary drive is fully mounted. If the drive fails to mount for any reason — hardware fault, filesystem error, or misconfigured fstab — Docker stays stopped rather than silently corrupting your setup. This single safeguard has saved many production environments from confusing, hard-to-diagnose failures."}},{"type":"header","data":{"text":"Final Result","level":1}},{"type":"paragraph","data":{"text":"After completing this migration:"}},{"type":"list","data":{"style":"unordered","items":["The root partition stays stable regardless of how many images, containers, or volumes you accumulate over time.","Docker storage is fully isolated on its own dedicated disk, where it cannot interfere with OS operations.","Disk growth becomes predictable and easy to monitor independently.","Server stability improves significantly because the OS partition is no longer at risk from container sprawl."]}},{"type":"paragraph","data":{"text":"This is not a cleanup tactic or a temporary fix — it is a deliberate infrastructure decision that eliminates the root cause of the problem entirely."}},{"type":"header","data":{"text":"Takeaway","level":1}},{"type":"paragraph","data":{"text":"If you run Docker on Ubuntu and care about server uptime, do not leave /var/lib/docker on your root partition. Move it to a separate mounted volume using the data-root option, enforce a systemd mount dependency so Docker cannot start without the disk, and protect your OS partition from runaway container storage growth."}},{"type":"paragraph","data":{"text":"Disk space problems rarely announce themselves. They accumulate quietly in the background until something critical fails at the worst possible moment. Moving Docker to a dedicated drive is one of the simplest, most effective infrastructure improvements you can make — and it only takes about 20 minutes to implement correctly."}}]}