Moving Docker from HDDs to an external SSD on an ARM64 NAS running UGOS Pro (Debian 12) looked simple. It took two failed approaches to find out the OS itself was the obstacle.
The Setup
A UGREEN NAS running UGOS Pro - a customized Debian 12 on ARM64 - hosting several Docker containers. All Docker data sitting on slow mechanical HDDs: both the data-root (overlay2 layers) and bind-mount volumes. Goal: migrate everything to an external SSD connected via USB to eliminate HDD I/O noise from containers.
First Attempt: rsync -aHX
Standard approach for Docker migrations. Copy everything, stop Docker, delta-sync, update daemon.json, restart.
rsync -aHX /volume1/@docker/ /mnt/external-ssd/@docker/
Copy succeeded. Docker started. Containers launched. Two minutes later - every container crashed with permission errors. All binaries inside the overlay2 layers had been reset to 600. Not executable. Just broken.
Second Attempt: rsync Without Xattrs
Suspected xattr copy was the problem. Excluded them:
rsync -aH --no-xattrs /volume1/@docker/ /mnt/external-ssd/@docker/
Same result. All files 600 again.
Root Cause: UGOS Pro's Kernel VFS Hook
UGOS Pro adds two proprietary extended attributes to every file on its native filesystem:
-
ug.archive_bit- tracks archive state for backup tooling -
system.ugacl_self- stores the NAS's custom ACL data
The custom ARM64 kernel includes VFS-level hooks tied to these xattrs. The hook behavior:
When ug.archive_bit is written to any filesystem - including ext4 on the SSD - the kernel resets that file's permissions to 600.
This fires in both rsync modes:
- With
-X: rsync explicitly copiesug.*xattrs to the destination ? hook fires - Without xattrs: rsync calls
chmod()after writing files ? the kernel hook intercepts the chmod and appliesug.*to the new file ? hook fires again
No rsync flag combination avoids this. The hook is below userspace.
The Fix: tar with --xattrs-exclude
The solution is tar with explicit xattr exclusion. Unlike rsync, tar applies the file mode atomically during extraction - the kernel doesn't get a separate chmod syscall to intercept.
Excluding ug.* ensures they're never written to the destination:
sudo tar \
--xattrs \
--xattrs-include='*.*' \
--xattrs-exclude='ug.*' \
-cpf - -C /source/path directory-name \
| sudo tar \
--xattrs \
--xattrs-include='*.*' \
--xattrs-exclude='ug.*' \
-xpf - -C /destination/path
The pipe avoids writing to disk twice. The --xattrs-include='*.*' is required - without it, --xattrs-exclude is silently ignored.
Expected warning during extraction:
tar: system.ugacl_self: Operation not supported
Harmless. The ext4 SSD doesn't support this xattr type, tar reports it and continues normally.
Migration Steps
Prepare the SSD
Add to /etc/fstab for persistent mount:
UUID=<your-uuid> /mnt/external-ssd ext4 defaults,noatime,nofail 0 2
The nofail flag is critical - without it, a disconnected SSD during boot will hang the NAS.
sudo mkdir -p /mnt/external-ssd
sudo mount -a
Phase 1: Migrate Docker data-root
# Stop Docker completely - both socket and service
sudo systemctl stop docker.socket docker.service
# Copy with tar pipe
sudo tar \
--xattrs --xattrs-include='*.*' --xattrs-exclude='ug.*' \
-cpf - -C /original/docker/path @docker \
| sudo tar \
--xattrs --xattrs-include='*.*' --xattrs-exclude='ug.*' \
-xpf - -C /mnt/external-ssd
# Update daemon.json
echo '{"data-root": "/mnt/external-ssd/@docker"}' \
| sudo tee /etc/docker/daemon.json
# Restart and verify
sudo systemctl start docker
docker info | grep 'Docker Root Dir'
docker ps
Phase 2: Migrate bind-mount volumes
The symlink approach avoids recreating any containers - Docker sees the same path, it just resolves to the SSD:
sudo systemctl stop docker.socket docker.service
sudo tar \
--xattrs --xattrs-include='*.*' --xattrs-exclude='ug.*' \
-cpf - -C /original/volumes/path . \
| sudo tar \
--xattrs --xattrs-include='*.*' --xattrs-exclude='ug.*' \
-xpf - -C /mnt/external-ssd/volumes
# Replace original directory with symlink
sudo mv /original/volumes/path /original/volumes/path.bak
sudo ln -s /mnt/external-ssd/volumes /original/volumes/path
sudo systemctl start docker
docker ps
Verify:
ls -la /original/volumes/path
readlink -f /original/volumes/path
Result
All containers running from SSD. HDD I/O dropped significantly. Mechanical drives now only handle the OS, logs, and background system processes. The symlink approach meant zero container reconfiguration.
The Rule for UGOS Pro
Any time you copy files from UGOS Pro's native filesystem to another filesystem (ext4, btrfs, tmpfs), use tar --xattrs-exclude='ug.*'. rsync cannot be made safe for this operation regardless of flags.
This applies to:
- Docker data-root migrations
- Backup operations targeting external drives
- Any bulk copy from UGOS-managed paths to a non-UGOS filesystem
The warning system.ugacl_self: Operation not supported will appear during extraction - it's harmless.
Tested on UGOS Pro with a custom ARM64 kernel. If you hit this on a different NAS OS with proprietary xattr hooks, the same pattern applies - find the vendor xattr prefix and exclude it from tar.
Top comments (0)