Deep dive
Replacing Google Photos with Immich
15,237 photos and videos, six ZIP files and one weekend: how my pictures moved from Google to my own home, and why the new storage was full by Sunday morning.
Friday night, almost half past eleven. Five freshly downloaded ZIP files sit in my Pictures folder, four of them nearly eleven gigabytes each. The sixth won’t arrive until Saturday morning. Together they come to 56 GB, and they hold everything I ever had in Google Photos. The oldest pictures are from 2002.
If you’ve ever switched phones, you know the nagging question afterwards: did all my photos really make it? This time the pictures are leaving Google for good and moving into my house. By 17:19 on Saturday, 15,237 photos and videos had moved. By 05:13 on Sunday, the storage was full. This post is about what happened in between.
It’s a deep dive for my series about my homelab, the small collection of computers I run at home myself. On the series map we’re on the “Services” layer, among the programs I actually use day to day.
Why bother?
Google Photos gets a lot right. Your phone uploads every picture, search finds faces and subjects, and sharing an album takes two taps.
Immich is a photo app you run yourself. It covers a surprising amount of what Google does: there’s a web interface, apps for Android and iOS, and Immich recognises faces and image content with its own computing power, without a single picture leaving the house.
For my homelab, that settled it. In part 1 I set myself four goals, two of which are “nothing reachable from the internet” and “everything restorable”. Photos are about as personal as data gets, so they should live exactly where those goals apply.
In return, disk space, memory and backups are my problem now. This weekend shows rather nicely how quickly you notice.
Where Immich lives
Before the first file can go up, Immich needs a home. In my homelab every service gets its own virtual machine, VM for short. That’s a computer my server merely simulates: it has its own disks, its own memory and its own operating system, but shares the real hardware with the other VMs, a bit like flats in an apartment building. The VM for Immich is called immich-01.
Nobody outside can reach that VM. My devices talk to it through NetBird, a private network that runs over the ordinary internet but only lets my own devices in. Every connection inside it travels through an encrypted tunnel. Part 2 explains how that works.
immich-01 has two virtual disks: 25 GB for the operating system and a separate data disk for the pictures. The data disk started at 50 GB, which turned out to be far too small for Google. Luckily the size is just a number in the text file the VM is built from (part 4). On Saturday before the import I changed it from 50 to 120 GB, with the VM still running.
Let’s zoom into the VM. Immich isn’t a single program; it’s four parts, each running in its own container. A container is a moving box for a program: it holds the program plus everything it needs, and you can start it, stop it or swap it for a newer version with one command. The tool that does this is called Docker. Here are the four containers and what they store:
| Container | Job | Data |
|---|---|---|
| Server | web interface, takes uploads from the app and the import | the photos and videos |
| Machine learning | recognises faces and describes image content for search | the downloaded recognition models |
| Database | remembers albums, people, capture dates and search information | the database itself |
| Queue | hands work out to the background jobs | none |
All of the containers’ data lives on the data disk. The photos are the heavyweight; the database is tiny by comparison. Those proportions will matter on Sunday morning.
What’s inside a Takeout export
Back to those six ZIP files. Google calls its export Takeout. Unpack it and you’ll find folders like “Photos from 2014” (in my case with German names), one folder per album, and next to almost every picture a second, small file with a name like IMG_20200610_111724.jpg.supplemental-metadata.json.
Those companion files are like a package insert for each photo. They’re written in JSON, a simple text format for data, and they hold the capture time, the title and more, such as the location. A screenshot, for example, carries no capture date of its own; without its package insert it would only know when it was exported. Your Immich timeline would collapse into a single day in September 2026.
It gets better. A photo and its package insert don’t even reliably share a ZIP. For one screenshot, the image sat in archive 2 and its insert in archive 1. Unpack and upload the archives one by one, and pairs like that fall apart.
Saturday, 17:02
So I didn’t unpack anything. The free tool immich-go reads all six ZIP files at once, finds the matching package insert for each picture, even for edited copies whose names changed, and uploads everything to Immich. Trimmed, the command looked like this:
immich-go upload from-google-photos \
--server "$IMMICH_URL" --api-key "$IMMICH_API_KEY" \
--sync-albums --pause-immich-jobs \
takeout-*.zip
The two switches on the third line are the interesting ones. --sync-albums recreates the Google albums in Immich. --pause-immich-jobs holds Immich’s background jobs while the import runs. Those are the chores Immich does after every new picture, such as rendering thumbnails and looking for faces. That way the VM doesn’t have to take in 50 GB and crunch at the same time; it works through the pile afterwards at its own pace.
The diagram shows the path from the export into the VM:
immich-go ran on my desktop and talked to immich-01 directly over NetBird. At 17:19, a little over 17 minutes later, the end of the log showed this tally:
| Result | Count |
|---|---|
| found: 14,420 images and 817 videos | 15,237 (50.3 GB) |
| newly uploaded | 14,100 |
| already in Immich (almost all from 2026) | 833 |
| duplicated within the export | 245 |
| from Google’s trash, skipped | 13 |
| errors | 0 |
Videos, by the way, are only about five percent of the files but take up 34 of the 50 GB. The 245 duplicates are album pictures that appear twice in the export, in the album folder and the year folder; immich-go uploads them once and adds them to the album. Left behind were 766 files that immich-go considered useless, nearly all of them .MP files from Pixel phones. To be honest, I haven’t looked at what’s in them yet.
Zero errors, albums in place, timeline correct. I was happy and called it a day.
Sunday, 05:13
To understand what happened overnight, you need to know how I back Immich up. There are two kinds of backup in my homelab, and part 6 covers them in detail.
The VM backup runs every night at 01:00 and copies the whole VM, all disks included, to a backup server I rent at Hetzner, outside the house. Think of it as a photo of the entire flat. The service backup runs at 03:30 and packs only the containers’ data into archives. It first stores those archives on the VM itself and then copies them, encrypted, to cloud storage that you rent by the gigabyte. Back then it packed all of Immich’s data, photos included.
So the night after the import, that’s exactly what it did. At 03:35 it packed the freshly filled library, 58 GB of it, and wrote the archive to the same data disk the photos lived on. That left the 120 GB disk nearly full.
At 05:13 the weekly update came along, which fetches new versions of the containers every Sunday. It takes one more backup first, got 4.7 GB in and stopped with “No space left on device”. The database could no longer write and crashed, and Immich showed nothing but an error page. Two push notifications landed on my phone, one for the failed update and one because Immich had stopped answering.
Delete two archives, restart database and server, and Immich was back. The more interesting question was why the photos were sitting on the same disk twice in the first place.
Photos are big and hardly ever change, and the VM backup suits that kind of data better anyway. It keeps track of which parts of the disk changed since the previous night and normally reads only those. Since that Sunday, the service backup saves only the database, about 250 MB. The photos leave the house with the VM backup, and Immich simply downloads its recognition models again when it needs them.
The diagram sums up how the VM is laid out today and which data takes which backup route:
The phone and signing in
With that, the old photos were safe. What’s left are the ones that arrive every day, and those come from the phone.
The phone runs the Immich app and, next to it, the NetBird app, which brings the phone into my private network. The Immich app’s server address is https://immich.home.example. That address doesn’t lead straight to the VM but to my reverse proxy. Think of it as a doorman: it receives every request for my services, handles the encryption towards the browser and passes each request on to the right VM, in this case immich-01.
Before the app may upload anything, I have to sign in. For that I use one central sign-in called Authentik instead of a password per service. It works like a hotel reception: I identify myself there once, and Immich trusts what Authentik confirms. Immich’s login page shows a “Sign in with Authentik” button for that.
Signing in briefly opens the browser, and a special return address leads the phone back into the app afterwards. The diagram shows both routes, first signing in, then uploading:
So every new photo travels encrypted through my private network, and still not a single door to the outside is open on my router at home. The flip side: when NetBird on the phone is switched off, the app can’t find the server.
Ten days later
Photos cost more than disk space. In mid-September my server was running short on memory, so I turned on ballooning for almost every VM. That lets the server take memory back from a VM while it isn’t using it, but only down to a floor. For immich-01 that floor was 2.5 of 4 GB.
A week later Immich was unreachable. I couldn’t log in to the VM either. Only a small helper inside the VM that answers independently of the network still responded, reporting 118 MB of free memory. The system log had the rest. When memory runs out completely, Linux simply ends programs. Late the evening before, for a quarter of an hour, it had ended the recognition program every one to three minutes, and at 00:01 it hit the Immich server itself.
A direct command to the server gave the VM its memory back right away. Then it got 6 GB at most and 5 GB at least, plus a reboot with 40 seconds of downtime, and the load dropped from over 24 to 1.9. One task is still open: I changed those values by hand on the server, so they still have to go into the text file the VM is built from. Otherwise the next run will quietly put the old values back.
What’s left
Those six ZIP files, by the way, are still in my Pictures folder. I haven’t deleted them, even though the pictures have long been in Immich and leave the house in a backup every night. Two things stuck with me. A Google export is more than a pile of photos; the timeline lives in the package inserts. And with large amounts of data I now do the maths twice, because the photos, the backups and some headroom all have to fit on the disk at the same time.
Immich is just one of the services on this layer. How a letter gets from the letterbox into a searchable archive there, with a sticker, a scanner and a local language model, is the story of part 3.