✦ Longform Writeups ✦

For a Dollar (and a bit more) banner

For a Dollar (and a bit more)

This whole saga started when my Google Photos storage slammed into that already conservative 15GB free limit. How long has that been stuck at 15 anyways? My meme folder alone was eating up 5 gigs, and I had absolutely no intention of paying for Google One. But rolling my own backup solution meant reinventing the wheel for problems Google had already solved and streamlined for me.

The first problem was storage. I was following, or at least trying to follow the 3-2-1 backup method, which requires a decent chunk of space. But considering the current economy with all the world's HBM output seemingly diverted to AI Slopcenters. Storage is absurdly expensive right now. I definitely don't see myself upgrading anytime soon. That meant I'm stuck with my existing 500GB external SSD enclosure, a rented VPS with 150GB of free space, and Backblaze B2. Considering my entire photo library is already 160 gigs, the architecture had to be surgical.

“NAND is Too Expensive” Architecture

When setting this up, I had essentially two core tenets. One, every photo needs to exist in at least two locations for redundancy, distributed or not. Two, B2 is my absolute source of truth. All photos need to be on there. Everything else is negotiable.

So what exactly am I trying to back up? First, my existing photo library, which sits on my external SSD where I regularly shuffle things around. Second, my camera's RAW files, which I export to a dedicated folder on that same external SSD. And third, my phone's photos.

My old setup wasn't amazing, but it worked. I just ran a manual rclone script to push the entire photo folder, RAWs and all, from the SSD straight to B2 while Google Photos handled all mobile backups. But replacing Google Photos changes everything. I needed a way to continuously back up my phone, but I also needed to delete photos off my phone because its internal storage was choking. If I deleted them locally, I still needed the convenience of a web client to view them from anywhere. And if I was going to go through the effort of finding a Google Photos replacement, it better display my entire old photo archive, too.

That's how I landed on Immich. It's essentially a self-hosted Google Photos that can ingest an existing external library, complete with a mobile app for backups. Genius! Exactly what I wanted. But it has some quirks that made my setup a lot more complicated than I expected.

Fighting Immich's Hatred of Folders

quote computer gif

Problems of an event based categorization system as opposed to date based classification

The Immich mobile app backs up photos, but at a very primitive level. I'm someone who obsesses over structure. There's a reason I constantly shift photos around in my older library. I like things properly categorized. My entire archive is built around nested folders, each with its own distinct category. I mirror this exactly in my phone's file system which is reflected in the gallery app, using grouped albums for high-level categories and sub-albums underneath them.

Immich couldn't do any of this. It only supports single-level albums through its Storage Templates. No albums inside albums. They have proper templating for date-based sorting, but absolutely nothing for folders. Using their native backup would obliterate my existing image archive. To put the final nail in the coffin, the mobile app doesn't even support synced file deletion. If I delete a photo locally, that change doesn't propagate to the server. It's a one-way copy, not a true sync.

Ideals are only on White Boards

The ideal way to handle my phone's photos would be uploading them straight to Backblaze and then pulling them down to the server. But finding a reliable, free app for continuous cloud syncing was a dead end. So, I pivoted: upload the photos to the server first, then sync them to B2. This preserves my folder structure and lets me import the images into Immich as an External Library, entirely bypassing their native backup system.

This is where Syncthing comes into the picture (hehe, get it). Their app is phenomenal. It offers true one-way syncing and watches for file changes, meaning it uploads files the second they change without any manual scheduling. What more could you ask for? I spun up a Syncthing instance on my VPS via Docker Compose and pointed it at my phone's DCIM folder, screenshots folder, memes folder etc. Because Syncthing mirrors my phone 1:1, if I delete a bad photo on my phone, that change propagates to the server too.

Eventually, when my phone runs out of storage, all I have to do is move the DCIM photos on the server over to my legacy photo folder, delete them from my phone, and boom, clean internal storage. And I don't lose access, because I can still view them anytime through the Immich web UI.

phone to b2 diagram

Phone -> B2

Keep Your Massive RAWs Off My Server

While solving the mobile issue, I hit another wall. My PC houses my legacy photo archive and my massive camera RAWs. I absolutely did not want those RAWs on the VPS; they're huge, and putting them in Immich is useless. But I still wanted the folder and album reorganizations I do on my PC to reflect on the server. I couldn't just rclone the entire library to the VPS because the RAWs would hitch a ride. My old strategy of blindly rcloning the whole folder to B2 wasn't going to cut it anymore.

So, I wrote a script on my PC to selectively send all the legacy photos to the server, while firing the camera RAWs straight to B2. I didn't automate this because I refused to have yet another background process hogging my PC's resources. Instead, I tied the script to an alias in my Fish shell. Now, whenever I make changes to my local library, I just type imgsync and everything goes exactly where it belongs.

pc legacy and camera to b2 diagram

Legacy and Camera -> B2

Sweeping it Under the Midnight Rug

From there, an rclone cronjob runs on the server every hour, pushing both the phone backup directory and the legacy photo directory up to B2. That separation matters. I feed them into Immich as two distinct External Libraries specifically to dodge sync conflicts, even though they live happily merged together in the B2 bucket.

full picture diagram

Full Architecture

That same hourly cronjob also triggers the immich-folder-album-creator script, which automatically generates Immich albums based on the actual folders in my external library, because again, Immich doesn't support nested folders (YET!). Immich reindexes all these changes at midnight, which is totally fine. None of these photos are time-sensitive anyways. The system works.

server folders

Server folders

full picture diagram

Immich Albums. Contains albums from Legacy Dir

Truth be told, this is a far more convoluted system than just paying for Google One. Sure it only costs me close to a $1/month for B2 to store around 150 gigs but that's without factoring the $13/month server. At the end of the day though, I can sleep without worring too much. I don't have to worry about Google overlords doing who knows what with my pictures and a monthly subscription that quietly doubles when you're not paying attention. All thanks to the incredible work the people behind Immich, Syncthing and Rclone have done.

Now, if my phone ever goes missing, the only thing I have to mourn is the monetary loss of the device not the sentimental value of the photos trapped inside it.


Charts made using FigJam. Banner made using Figma. Logo credited to Immich.