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.

October 30, 2026 · 10 min read

immichauthentikwireguarddockerproxmoxdebian

Photos moving from Google to ImmichPhotos fly out of a cloud on the left, the Google Takeout export, and land as a growing photo grid on a green glass plate running Immich. On the right a phone sends new photos through an encrypted NetBird tunnel; sign-in goes through Authentik.Google PhotosTakeout · 6 × ZIP · 56 GB15,237 photos & videosimmich-go · 17 minutesimmich-01phone appNetBirdSign-in: Authentik

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.

Series map: phone and laptop reach the homelab through Network & access; the Services layer with Paperless, Nextcloud, Immich and n8n is highlighted; below it the Foundation, next to it Operations with Code, Pipeline, Backup and Monitoring
You are here: Services

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:

ContainerJobData
Serverweb interface, takes uploads from the app and the importthe photos and videos
Machine learningrecognises faces and describes image content for searchthe downloaded recognition models
Databaseremembers albums, people, capture dates and search informationthe database itself
Queuehands work out to the background jobsnone

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:

The import: six Takeout ZIP files contain year folders, album folders and JSON package inserts. immich-go on my desktop reads the ZIPs directly, pairs picture and insert and spots duplicates. It uploads over NetBird to the VM immich-01, where the background jobs are paused while the import runs.
The export at the top, immich-go on my desktop in the middle, the VM at the bottom. Not a single folder gets unpacked.

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:

ResultCount
found: 14,420 images and 817 videos15,237 (50.3 GB)
newly uploaded14,100
already in Immich (almost all from 2026)833
duplicated within the export245
from Google’s trash, skipped13
errors0

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 VM immich-01: a 25 GB system disk and a 120 GB data disk. The data disk holds the photos and videos, the database and the recognition models. The VM backup at 01:00 copies the whole VM including the data disk to the backup server at Hetzner; the service backup at 03:30 saves only the database, encrypted, to cloud storage. The models are not backed up.
The photos travel with the whole VM, the database also goes with the service backup. Immich downloads the models again.

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:

The phone with the Immich app and NetBird first signs in at Authentik and returns to the app via the return address app.immich:///oauth-callback. Then it uploads photos to the reverse proxy, which passes them on to the VM immich-01. Everything runs inside my private NetBird network.
Sign in at Authentik first, then upload through the doorman. Every route runs through the private network.

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.