Two focused, best-in-class tools instead of one all-in-one suite: Immich for photos and video, Radicale for contacts and calendars. Here's the setup, and how to migrate years of existing data over without losing anything.
An all-in-one platform (Nextcloud, for example) can do photos, files, contacts, and calendars all at once — but splitting into two focused tools instead gets you a noticeably better experience for less overhead:
If your backup solution only backs up VM/container disk images (not arbitrary NFS-mounted paths — this is true of Proxmox Backup Server, for example), a simple NFS bind-mount for your photo library means it's never actually covered by your backup job — a real risk for genuinely irreplaceable photos.
Instead, provision a real virtual disk on your NAS-backed storage pool and let your hypervisor manage it as a normal disk image:
# Proxmox example
pct set <vmid> --mp0 nas-storage-pool:500,mp=/mnt/photo-library,backup=1
This gets you a real disk image on the NAS, formatted normally and mounted like local storage inside the
container — no NFS UID-mapping headaches, and backup=1 makes it a genuine backup target. Keep
the database's own data directory (Postgres, in Immich's case) on the container's local storage, never on
network storage — database files over NFS risk locking issues and corruption.
The same setuid-rsync-over-SSH restriction covered in the password
manager guide applies here too — use a tar stream over SSH instead:
ssh <nas> 'cd /path/to/photos && sudo tar --exclude=@eaDir --exclude=.DS_Store -cf - .' \
| ssh <immich-host> 'cd /import/staging && tar xf -'
Exclude any vendor-internal thumbnail-cache directories (Synology's @eaDir, for example) —
they're pure noise your new tool will regenerate its own thumbnails for anyway.
To actually ingest the files (not just copy them), use immich-go rather than a raw file copy — it reads EXIF/embedded dates and uploads through Immich's real API so thumbnails and search indexing run normally:
immich-go upload from-folder \
--server http://localhost:2283 --api-key <your-api-key> \
--folder-as-album FOLDER --no-ui \
/import/staging
Always run with --dry-run first at any real scale — it's cheap insurance and can catch
permission issues (e.g. a non-admin API key can't pause background jobs, a step immich-go attempts by
default) before touching anything for real.
Most vendor NAS calendar/contact apps expose standard CalDAV/CardDAV endpoints directly, even without an
obvious "export" button in the UI — check the app's underlying web server config for existing
/caldav/ and /carddav/ routes. If they exist, use
vdirsyncer, a standard two-way
CalDAV/CardDAV sync tool, to do a one-time pull from the old source into Radicale.
vdirsyncer's SSL verification option only accepts a real CA file
(not a boolean to disable verification), and the standard public root for that vendor's cert type doesn't
match, pin the exact certificate's fingerprint instead of trying to chase down the right CA chain:
echo | openssl s_client -connect <nas-ip>:5001 -servername <nas-ip> 2>/dev/null \
| openssl x509 -noout -fingerprint -sha256
Verify the migration by comparing item counts on both sides directly — not just a "sync exited cleanly" check — before considering it done.
The actual cutover is per-device: repoint each phone and computer's CalDAV/CardDAV account from the old server to Radicale. Most desktop OSes have this under their native "Internet Accounts" / account settings as a generic CalDAV/CardDAV option; Android has no built-in client and needs a third-party app (e.g. DAVx⁵).