I Moved 54,000 Photos to a Self-Hosted Library and It Nearly Melted My Server
Migrating a decade of personal photos from Mylio to Immich: the full story — duplicate hell, thermal shutdowns, and a nightly rclone backup that would later save everything.
Why Mylio Had to Go
Mylio had been the photo solution for years. Decent sync, cross-device, handled a large library without too much friction. Then it started doing the thing subscription software does when it gets comfortable — degrading quietly.
The Samsung S23 Ultra became a recurring problem. The Mylio app would go offline, silently. The fix was to reinstall — which triggered a re-sync that created duplicates and ghost entries. Do that a few times and your library starts looking like a horror movie. The same photo appearing twice, three times, dated differently, no clear canonical version.
Add the subscription cost on top of that and the math was simple. A self-hosted solution with full control, no app reinstall loop, and no monthly invoice was the obvious next move. Immich was the answer.
The Samsung kept going offline. The reinstall created duplicates. The duplicates required cleanup. The cleanup created more problems. The subscription kept charging. Time to leave.
What the Library Actually Looked Like
Before touching anything, it helped to understand the scale of the problem. This wasn’t a casual camera roll — it was a structured library going back to the early 2000s, sourced from multiple devices, exports, and backup drives over the years.
The source was a Western Digital My Passport drive containing Mylio’s original file structure — 102,645 files including photos and XMP sidecar files going back decades. OneDrive and Google Photos 2026 exports added another 14,000+ assets on top of that.
The Stack
Immich runs on a home lab server called Bunty — an Ubuntu machine running CasaOS with Docker. The photo library lives on an attached external USB drive mounted at /mnt/external/immich/. Everything is on the local network with Tailscale for remote access.
The Migration — What Actually Happened
The plan was straightforward: transfer the originals from the Passport drive to Bunty, then use the Immich CLI to upload year by year. The reality involved a few hard-learned detours before getting there.
Exporting from Mylio re-encoded JPEGs and removed DateTimeOriginal. Every photo landed in Immich with today’s date. 5,491 assets had to be deleted and re-ingested from originals.
The original files on the WD Passport had intact EXIF. SCP’d 379 GB directly from the Passport to Bunty. 11.5 hour transfer. EXIF verified clean before uploading to Immich.
Used the Immich CLI to upload one year folder at a time, verifying counts between each run. Caught and cleaned duplicates as they appeared rather than doing a single bulk import and sorting out the mess later.
1,893 OneDrive assets arrived with wrong dates. Built a tiered Python recovery script: folder name parsing recovered 242, filename pattern matching recovered 281. 1,322 were unfixable scans and hash-named exports.
Photos from 2003 through 2026, correctly dated, fully ingested. XMP sidecar files automatically skipped by the CLI. Library integrity confirmed.
The Thermal Problem
After the upload completed, Immich needed to generate thumbnails for 145,000 assets. This is CPU-intensive machine learning work — face detection, thumbnail generation at multiple resolutions, blurhash generation. On an Intel IdeaPad running 24/7 as a home server, this created a temperature problem.
Running thumbnail generation and video transcoding simultaneously pushed core temps to 80°C. The system shut down. The jobs are resumable, so no data was lost, but it required a more disciplined approach to job scheduling.
The solution was sequencing. Thumbnails at concurrency 1 overnight. Face detection after thumbnails completed. Video transcoding as a solo overnight job. Slow but stable — and the jobs resume cleanly after any interruption.
145,000 thumbnails don’t generate themselves. They generate slowly, in order, at concurrency 1, while you sleep, so the server doesn’t shut itself off.
Setting Up the Nightly Backup
Once the library was stable, the backup pipeline went in immediately. The lesson from years of home lab work: the backup you set up after the disaster is the one you wish you had before it. This time, it went in first.
The approach uses rclone to sync the Immich library to Google Drive daily. A dedicated Google Cloud OAuth client was created to avoid severe API throttling — the default shared rclone credentials cap out around 13 KiB/s. With a dedicated client, sustained transfer speeds hit 8–9 MiB/s.
Source: /mnt/external/immich/library/ → Destination: gdrive:LAWNET_NETWORK_BACKUP/IMMICH_BACKUP/
Thumbnails, encoded video, and upload staging directories are excluded. Originals only. Saves significant storage and transfer time — the things that can’t be regenerated are the only things that matter.
Runs nightly on Bunty. Uses rclone sync — destination matches source. ntfy.sh push notification on completion or failure.
Immich generates daily .sql.gz DB dumps automatically. These land in the backup folder and get swept up by the rclone sync. Up to 14 daily snapshots retained.
After the initial 387 GiB sync completed, subsequent nightly runs take minutes — only changed and new files transfer. The GDrive backup became the silent insurance policy running in the background every night at 2 AM.
What Worked
End State
Two days of migration work. A couple of thermal incidents, a Python EXIF correction script, and one very slow overnight thumbnail generation run later — the library was live. 145,000 assets, correctly dated, fully indexed, backed up nightly to Google Drive.
Immich replaced Mylio entirely. No subscription. No app reinstall loop. No duplicate ghosts. The S23 Ultra backs up automatically over the local network. The library is searchable, face-tagged, and mapped.
The backup ran every night at 2 AM without incident. For a while.
Part 2 is about what happens when the external drive dies mid-write, takes the database with it, and why that nightly rclone sync is the only reason this story has a good ending.



Leave a Reply