Side reading
Storage layout, and moving a category later
The directory structure that lets you add a disk without rebuilding, and the migration done carefully once.
The layout #
/srv/homeserver/
data/ irreplaceable, backed up always
photos/
appdata/ live or generated application state
immich-db/
dumps/ database dumps, written before each backup
One rule: data holds user content; appdata holds state managed by applications. That name does not decide backup policy by itself. Chapter 11 selects all of data plus validated logical dumps, rather than copying a live database directory. Authentik's named database volume follows the same dump rule. Prometheus and Loki history are disposable unless you deliberately change that decision.
The split makes later questions explicit. Back up data and appdata/dumps. Exclude only confirmed generated content. Move a category under data to another disk without moving the operating system or databases with it.
Why paths belong in variables #
Every path an application uses should come from one place: an environment file, a compose variable, a playbook variable. Never typed twice.
PHOTOS_ROOT=/srv/homeserver/data/photos
ARCHIVE_ROOT=/srv/homeserver/data/archive
Moving a category then means changing one value and redeploying, rather than finding every occurrence.
Adding a disk #
The migration, done carefully. Roughly an hour for a large library, most of it copying.
1. Attach and identify it.
$ lsblk -o NAME,PATH,SIZE,MODEL,SERIAL,FSTYPE,UUID,MOUNTPOINTS
$ ls -l /dev/disk/by-id/
Choose the stable /dev/disk/by-id/<device>-part1 path by matching the physical disk's model, serial and size. Record it as <new-partition>. Stop if you cannot identify it unambiguously.
2. Make a filesystem, if it is new.
$ lsblk -f <new-partition>
$ sudo wipefs --no-act <new-partition>
$ findmnt --source <new-partition>
mkfs destroys existing filesystem metadata. Continue only for the deliberately empty new partition, when findmnt shows it is not mounted and you have accounted for every signature reported by wipefs --no-act:
$ sudo mkfs.ext4 -L archive <new-partition>
3. Mount it somewhere temporary, by UUID.
$ sudo mkdir -p /mnt/new
$ sudo mount UUID=<the uuid> /mnt/new
$ findmnt /mnt/new
4. Stop what writes to the directory. Not optional. Copying a live tree gives you a copy of a moving target.
$ sudo docker compose -f /home/admin/homeserver/immich/compose.yaml stop
5. Copy, preserving everything.
$ sudo rsync -aHAX --numeric-ids --info=progress2 /srv/homeserver/data/archive/ /mnt/new/
-a preserves permissions, times and symlinks. -H hard links. -A ACLs. -X extended attributes. --numeric-ids keeps the numbers rather than resolving names, which matters for exactly the reasons the permissions annex describes.
Note the trailing slash on the source. With it you copy the contents; without it you copy the directory into the target and end up one level deeper.
6. Verify before you trust it.
$ sudo rsync -aHAXnci --delete --numeric-ids \
/srv/homeserver/data/archive/ /mnt/new/
$ sudo du -sh /srv/homeserver/data/archive /mnt/new
No rsync change lines means contents and the metadata represented by these flags match. For an irreplaceable migration, also compare a checksum manifest or let the next restic backup read and verify the destination before deleting the source.
7. Mount it in the right place, permanently. By UUID, never by device name: /dev/sdb is not stable across reboots and a drive that moves to /dev/sdc will mount the wrong thing or nothing.
$ sudo umount /mnt/new
$ sudo mv /srv/homeserver/data/archive /srv/homeserver/data/archive.old
$ sudo mkdir /srv/homeserver/data/archive
$ sudo cp -a /etc/fstab /etc/fstab.before-archive
$ sudoedit /etc/fstab
Add one reviewed line, substituting the UUID you read with blkid:
UUID=<the uuid> /srv/homeserver/data/archive ext4 defaults,noatime 0 2
Then validate and mount it:
$ sudo findmnt --verify --verbose
$ sudo mount -a
$ findmnt /srv/homeserver/data/archive
$ ls /srv/homeserver/data/archive
8. Start the affected application and check it actually sees its files.
9. Keep the old copy for a week, then remove it.
Test fstab before you reboot
A bad line in /etc/fstab can stop the machine booting, and you will be recovering it at a console.
findmnt --verify catches many mistakes before mounting. sudo mount -a applies the file without rebooting. Run both and confirm the expected source, target and filesystem. Add nofail only after deciding that booting without the data is safer than stopping for recovery; an application writing into an empty underlying directory can be worse than a failed boot.
Removable drives #
Do not put persistent data on a path that automounts, like /media/<user>/<label>. Those paths depend on desktop services, change with the label, and are not mounted when nothing is logged in.
Give the drive a permanent mount point in /srv and a UUID entry in fstab.
The one that is harder #
A database directory can be moved the same way, and there is nothing special about it as long as the database is stopped. What you must not do is copy it while it runs, for the reasons chapter 11 gives.
Stop the container, copy, remount, start. Or, more safely: dump it, move the directory, restore into the new location.