Story 8 · Immich
The Family Album That Came Home
“A photo you cannot find is not much safer than a photo you have lost.”
22 min read · by Syed Jafer K
1. The Email That Said Full
Yazhini tests payment software for a company in Chennai. At home she is the person everyone hands their phone to when something stops working.
In the first week of November three things stopped working at once.
Her own Google account sent an email that said the storage was full and that she might stop receiving mail. Her photos, eleven years of them, had filled it. Her husband Naveen had a paid plan on his account, and that one was at 96 percent.
Her father, Ramasamy, lives in Kumbakonam. His phone fell into a bucket during the Deepavali cleaning and did not wake up. The repair shop saved the SIM and nothing else. He had turned photo backup off two years earlier, because a message about storage kept appearing and he wanted it to go away. The phone had held the last pictures of Yazhini's grandmother, taken at her eightieth birthday.
The third thing was smaller. Her daughter Nila had turned one in September. Yazhini wanted a single picture from the birthday, the one with the cake and her father laughing. She searched her phone, then Naveen's phone, then two WhatsApp groups. It took forty minutes. The copy she found was the blurry WhatsApp one.
That night she listed where the family's photos lived.
| Place | What was there |
|---|---|
| Her Google account | 2015 to now, full |
| Naveen's Google account | 2017 to now, nearly full |
| An old external disk | College and wedding photos, in folders named New Folder (3) |
| WhatsApp groups | Compressed copies of everything, in no order |
| Her father's phone | Gone |
"We are paying two companies to hold photos we cannot find," she told Naveen. "And the ones that mattered most were on a phone in a bucket."
A colleague had shown her his home photo server at lunch a few months earlier. It had looked like the app she already used. She messaged him for the name. Immich.
2. Where Immich Came From
Immich is a free, open-source photo and video manager that you run on your own machine. It has a web app, an Android app and an iPhone app. The phone apps back up the camera roll to your server in the same way the big photo services do.
It was started in 2022 by an engineer named Alex Tran, who wanted a private place for his family's photos after his child was born. That is close to the reason most people install it.
The project grew fast. In 2024 the core team joined FUTO, an organisation that funds open software, so that they could work on Immich full time. The source is licensed under the AGPL-3.0. There is no paid edition and no locked feature. You can buy a supporter key if you want to fund the work, and the software behaves the same whether you do or not.
For a long time every page of the documentation carried a warning that the project was under very active development. The first stable release, version 2, arrived in late 2025. Version 3 followed in 2026. Yazhini installed 3.4.
Her colleague gave her one piece of advice before anything else. "Immich will hold your photos. It will not back them up for you. Decide where the second copy goes before you delete anything from Google."
3. What Immich Does
| Part | What it gives you |
|---|---|
| Mobile backup | The app uploads new photos and videos from the phone, in the foreground or in the background |
| Timeline | Every photo by date, with fast scrolling through years |
| Search | Search by what is in the picture, by text inside it, by place, by camera, by person |
| People | Faces are detected and grouped. You give each group a name once. |
| Places | A map built from the GPS data inside the photos |
| Albums | Private, or shared with other users on the server |
| Shared links | A link for someone with no account, with an optional password and expiry date |
| Partner sharing | Two users can see each other's whole timeline |
| Memories | Photos from this day in earlier years |
| External libraries | Folders that already exist on disk, shown in Immich without being moved |
| Users | Each person has their own login, their own library and an optional storage quota |
| Duplicates and trash | Finds near-identical photos, and keeps deleted items for 30 days |
All of the search and face work happens on your own machine. No photo is sent anywhere to be analysed.
Naveen read the table twice. "This is the app we already have."
"That is the point," Yazhini said. "My father should not have to learn anything."
4. What the Setup Looks Like
Immich is not one program. It is four containers that start together.
| Container | Job |
|---|---|
immich_server |
The web app, the API the phones talk to, and the background jobs such as thumbnails and video conversion |
immich_machine_learning |
The models for search, faces and text recognition |
immich_postgres |
PostgreSQL with a vector extension. Holds users, albums, face data and the path to every file. |
immich_redis |
A small in-memory store used as the job queue |
There are two places on disk that matter.
The first is the upload location. It holds the photos and everything made from them.
| Folder | Contents | Can it be rebuilt? |
|---|---|---|
library |
Originals, when the storage template is on | No |
upload |
Originals as they arrive | No |
profile |
Profile pictures | No |
backups |
Automatic database dumps | No |
thumbs |
Thumbnails and previews | Yes |
encoded-video |
Videos converted for playback | Yes |
The second is the database folder. It is small, usually one to three gigabytes, and it must sit on a local disk. The documentation is firm that it must never be on a network share.
The photos and the database need each other. The database without the files is a list of names. The files without the database are a pile of pictures with no albums, no people and no owners.
5. Before You Start
Yazhini did not want a cloud server for this. Photos take space, and space in the cloud is rented by the month. She bought a used mini PC for the shelf under the television.
- A machine. Immich asks for at least 6 GB of memory and 2 CPU cores, and recommends 8 GB and 4 cores. With 4 GB it runs only if machine learning is switched off. Her mini PC had a four-core Intel N100 and 16 GB.
- Two kinds of disk. A 500 GB SSD for the operating system and the database, and a 4 TB hard disk for photos, formatted as ext4 and mounted at
/mnt/photos. Thumbnails and converted videos add roughly 10 to 20 percent on top of the originals. - Linux and Docker. She installed Ubuntu Server and Docker Engine from Docker's own packages. The older
docker.iopackage and the hyphenateddocker-composecommand both cause errors with the Immich compose file. - A fixed address at home. She reserved
192.168.1.20for the mini PC in the router. - A plan for the second copy. Decided before the first photo went in. Section 15 has it.
6. Two Files and One Command
Immich publishes its compose file and an example environment file with every release. You download them. You do not write them.
sudo mkdir -p /opt/immich && sudo chown $USER /opt/immich
cd /opt/immich
wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env
She edited .env and left the compose file alone.
UPLOAD_LOCATION=/mnt/photos/immich
DB_DATA_LOCATION=/opt/immich/postgres
TZ=Asia/Kolkata
IMMICH_VERSION=v3.4.1
DB_PASSWORD=REPLACE_WITH_LETTERS_AND_DIGITS_ONLY
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
Three of those lines were her own decisions.
UPLOAD_LOCATIONpoints at the big disk, andDB_DATA_LOCATIONat the SSD.IMMICH_VERSIONcame asv3, which follows every new 3.x release. She changed it to the exact version, so that an upgrade happens only when she decides.DB_PASSWORDuses only letters and digits. Other characters can be misread by Docker when it parses the file.
docker compose up -d
docker compose ps
curl -s http://localhost:2283/api/server/ping
The first start downloads a few gigabytes of images. After that, all four containers showed as healthy and the last command answered {"res":"pong"}.
7. The First Admin and the Family
She opened http://192.168.1.20:2283 on her laptop and pressed Getting Started.
The first person to register on a new Immich server becomes the administrator. There is no default password. So she did this before telling anyone the address.
Under Administration, then Users, she created three more accounts.
| User | Storage label | Quota |
|---|---|---|
| Yazhini (admin) | yazhini |
None |
| Naveen | naveen |
None |
| Ramasamy | appa |
None |
| Guest | guest |
20 GB |
The storage label is a small setting that matters later. Without it, each user's folder on disk is named with a long random identifier. With it, the folder is named appa.
The guest account was for her cousin, who would upload wedding photos in January. The quota meant one enthusiastic videographer could not fill the disk.
Each user is separate. Naveen cannot see Yazhini's photos unless she shares them, and the administrator does not see other users' timelines either.
8. Folders by Year
By default Immich keeps uploaded files in folders named after internal identifiers. It works, but it is unreadable if you ever look at the disk without Immich.
Under Administration, Settings, Storage Template, she turned the template on and kept the suggested pattern.
{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}
With the storage labels from the last section, the disk now looked like this.
/mnt/photos/immich/library/
yazhini/2026/2026-09-14/IMG_20260914_183012.jpg
naveen/2026/2026-09-14/PXL_20260914_130455.mp4
appa/2024/2024-03-02/IMG-20240302-WA0007.jpg
"If Immich disappeared tomorrow," she told Naveen, "this is still a folder of photos sorted by date. Any computer can open it."
There is one rule that comes with it. Never rename, move or delete files in these folders by hand. The database remembers where every file is. Changes go through the app.
9. The Phones
She installed the Immich app on her phone. It asked for a server address, and she typed http://192.168.1.20:2283, then her email and password.
The cloud icon at the top opens the backup screen. She chose which phone albums to back up. Camera and Screenshots went in. WhatsApp Images stayed out, because the family groups would have added thousands of forwarded good morning pictures.
Then she turned on Enable Backup and left the phone on the charger. It uploaded 9,400 items overnight.
Three settings were worth changing.
| Setting | Her choice | Why |
|---|---|---|
| Background backup | On | New photos upload without opening the app |
| Use mobile data for videos | Off | A two-minute video is a large part of a daily data pack |
| Battery optimisation for the app | Unrestricted | Some phones stop background apps to save battery, and the backup then stops with them |
The app also has a Free Up Space option that removes photos from the phone once they are safely on the server. She did not touch it. At that moment the server was the only other copy, and one copy on one hard disk is not a backup.
10. Reaching Home From Outside
On the home Wi-Fi the phones could reach the server. Outside the house they could not, and her father's house was 280 kilometres away.
The usual answer is to open a port on the router and put Caddy in front, as in chapter 3. Yazhini tried and found the problem many home connections in India have. Her provider used carrier-grade NAT. The address her router showed was not a public address at all, so nothing outside could connect in.
She also was not sure she wanted the family's photos on a public address. A photo server with a login page on the open internet has to be kept patched without fail.
She used a private network instead. Tailscale builds an encrypted WireGuard network between your own devices, and it works through carrier-grade NAT.
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
sudo tailscale serve --bg 2283
The last command gave the mini PC an HTTPS address that only devices in her private network could open, something like https://album.example-tailnet.ts.net.
She was honest with herself about what this is. Tailscale's coordination service is run by a company. It introduces the devices to each other and never sees the photos, which travel encrypted between her own machines. People who want to run that part too can use Headscale, an open-source replacement for it.
The Immich app has a setting made for this. Under Settings, Networking, Automatic URL switching, she entered both addresses.
| When | The app uses |
|---|---|
On the Wi-Fi named Nila-Home |
http://192.168.1.20:2283 |
| Anywhere else | https://album.example-tailnet.ts.net |
Her father's phone got Tailscale and the Immich app during a video call that lasted an hour, most of it spent finding his glasses.
For readers who do have a public address and a domain, the Caddy block is the short one from chapter 3.
photos.example.org {
reverse_proxy 127.0.0.1:2283
}
Two things to know. Immich cannot be served under a sub-path such as /photos/. It needs its own domain or subdomain. And whatever proxy you use must allow very large uploads and long requests, because a single video can be several gigabytes. Caddy does both without extra settings.
11. Bringing Eleven Years Out of Google
The phone held only the last two years. Everything older was in Google Photos.
Google Takeout exports an account as a set of zip files. She asked for Google Photos only, in 50 GB parts, and waited a day for the email. Her account came out as three files and Naveen's as two.
A Takeout export is awkward. Each photo comes with a separate JSON file that holds the real date, the location and the description. Edited photos appear twice. Albums are folders with copies inside them. Uploading the pictures straight into any tool loses the dates, and a timeline with eleven years of photos all dated last Tuesday is useless.
The Immich documentation points to a community tool that understands this format, called immich-go. It reads the zip files directly, joins each photo to its JSON file, recreates the albums and skips what is already on the server.
She created an API key under her account settings in Immich, then ran it first in dry-run mode.
immich-go upload from-google-photos \
--server=http://192.168.1.20:2283 \
--api-key=PASTE_THE_KEY_HERE \
--dry-run \
/mnt/photos/takeout/yazhini/takeout-*.zip
The dry run printed a report with how many files it found, how many had a matching JSON file, and how many were duplicates. She read it, removed --dry-run and ran it again. It took most of a Saturday.
Then she did the same for Naveen, with an API key from his account, so that his photos landed in his library and not hers.
When it finished, she scrolled the timeline back to 2015 and checked ten photos she remembered. The dates were right. The Pondicherry album was there with its name.
She did not delete anything from Google yet.
12. The Old Hard Disk
The external disk had 140 GB of college and wedding photos in folders that she did not want to reorganise. Immich can show such a folder without copying or moving anything. This is an external library.
It was the one time she edited the compose file. She added the disk to the server container as a read-only mount.
services:
immich-server:
volumes:
- ${UPLOAD_LOCATION}:/data
- /etc/localtime:/etc/localtime:ro
- /mnt/olddisk/Photos:/mnt/olddisk:ro
docker compose up -d
Under Administration, External Libraries, she created a library owned by her own user, added the import path /mnt/olddisk and pressed Scan.
The photos appeared in her timeline in 2009 and 2013. The files stayed where they were, in New Folder (3). The :ro at the end of the mount means Immich can read the disk and cannot change it.
13. The Night the Mini PC Worked
After the imports the timeline looked complete, but the server was far from finished. Under Administration, Job Queues, the counters were in the tens of thousands.
| Queue | What it does |
|---|---|
| Generate Thumbnails | Small versions of every photo, so the timeline scrolls quickly |
| Transcode Videos | Converts videos that browsers cannot play |
| Smart Search | Describes each picture to a model so it can be searched in words |
| Face Detection and Facial Recognition | Finds faces, then groups the ones that look alike |
| OCR | Reads text inside pictures |
The little processor ran at full load for two nights and most of the day between them. That is normal for a first import, and it happens once. New photos after that are handled in seconds.
When the queues were empty, the People page had 212 faces grouped. She named the first six. Immich had already put her grandmother's photos together, from a studio portrait scanned in 2016 to the eightieth birthday.
Then she tried the search box.
birthday cake returned the picture she had lost forty minutes to in section 1. It was the full-size original, and it was the first result.
temple elephant found Kumbakonam in 2019. receipt found every bill she had ever photographed.
Naveen typed a search in Tamil and got nonsense. The default search model understands English best. Immich lets you choose a multilingual model in the machine learning settings. Changing it means running the Smart Search job again for every photo, so it is a decision to make early. She changed it that week and let the mini PC work one more night.
14. Sharing
The family used three kinds of sharing, and each fits a different need.
| Kind | Who it is for | What they used it for |
|---|---|---|
| Partner sharing | Another user on the server | Yazhini and Naveen see each other's full timeline, so Nila's photos are in one place whoever took them |
| Shared album | Chosen users on the server | An album called Nila, where all three adults can add photos |
| Shared link | Anyone, no account needed | The birthday album for relatives, with a password and an expiry date of 30 days |
Shared links have one limit in her setup. The link points at the server's address, and her server is reachable only inside the private network. Relatives outside it cannot open the link.
She accepted that. For the birthday album she selected the photos, downloaded them as a zip from Immich, and sent that. A family with a public domain would not have this limit, and would have a login page on the internet in exchange. She preferred her side of that trade.
15. Immich Is Not a Backup
By the third week every photo the family owned was on one hard disk under the television. Yazhini knew what one hard disk is worth. She had read chapter 4.
Immich helps with the database half. Every night at 2 a.m. it writes a compressed dump of the database into UPLOAD_LOCATION/backups and keeps the last 14. The setting is under Administration, Settings, Database Dump Settings.
That means one folder tree, /mnt/photos/immich, holds both the originals and a recent database dump. Copying that tree somewhere else is a full backup.
She made two copies.
A second disk at home. A USB disk plugged into the mini PC, updated every night.
An encrypted copy outside the house. The same crypt remote over Backblaze B2 that Selvi used in chapter 4. The storage company sees only scrambled names and scrambled data.
#!/bin/bash
# /opt/immich/backup.sh
set -euo pipefail
SRC=/mnt/photos/immich
TOPIC=https://ntfy.example.org/home-backups
trap 'curl -s -H "Priority: high" -d "Immich backup FAILED" $TOPIC' ERR
rsync -a --delete --exclude thumbs --exclude encoded-video \
"$SRC/" /mnt/usbcopy/immich/
rclone sync "$SRC" b2crypt:immich \
--exclude "thumbs/**" --exclude "encoded-video/**" \
--backup-dir "b2crypt:immich-old/$(date +%F)" \
--transfers 4
curl -s -d "Immich backup finished" $TOPIC
It runs at 3 a.m., one hour after the database dump, so the dump it copies is always fresh.
Three details in that script are deliberate.
- The thumbnails and converted videos are left out. Immich can rebuild them. Leaving them out makes the outside copy smaller and cheaper.
--backup-dirkeeps what would have been deleted. If someone empties an album by mistake and the sync runs, the files move to a dated folder and are not lost. This is the safety net from chapter 4.- The bell from chapter 1 rings either way. A quiet morning with no message means the script did not run at all.
The first upload to B2 took nine days on a home connection. After that, each night took minutes.
16. The Restore Drill
A backup that has never been restored is a hope. So on a Sunday she borrowed Naveen's old laptop, installed Docker and pretended the mini PC had been stolen.
- She downloaded the same
docker-compose.ymland wrote the same.env, with the sameIMMICH_VERSION. - She pulled one user's
libraryfolder and thebackupsfolder down from B2 into the new upload location, withrclone copy. - She started the containers. On a fresh install, the welcome page has a Restore from backup choice. She picked the newest dump from the list.
- Immich restored the database and restarted.
She logged in with her usual password. The timeline was there, with albums, people and names. The thumbnails were grey squares, because she had not backed them up. She ran the Generate Thumbnails job for missing items and they filled in.
The drill taught her two things that went into her notes.
- The folder layout must match. The database stores paths. The restored files have to sit in the same structure they had on the old machine.
- The version must match. A dump from 3.4 goes into 3.4. Upgrade after the restore, not during it.
Only after that Sunday did she go back to the phones and use Free Up Space. Her own phone got 38 GB back.
Then she moved her Google account's photos to the trash, kept the mail, and the storage warning went away.
17. Upgrading
Immich releases often. The upgrade is two commands, and the care is in what you do before them.
-
Read the release notes for every version between yours and the new one. Breaking changes are marked at the top.
-
Create a database dump by hand, from Job Queues, Create job, Create Database Dump. Wait for it to finish.
-
Change
IMMICH_VERSIONin.envto the new exact version. -
Pull and restart.
cd /opt/immich docker compose pull && docker compose up -d -
Open the web app and check the version at the bottom of the side menu. Open a photo, play a video, run a search.
-
Update the phone apps. The app and the server are released together, and an app that is far ahead of or behind the server will show a warning and may refuse to back up.
A move between major versions needs one more step. Going from 2 to 3, the release notes also asked for a newer docker-compose.yml, because the database image had changed. She downloaded the new file and put her one extra volume line from section 12 back in.
There is no way back. The database is migrated forward when a new version starts, and an older version cannot read it. The dump in step 2 is the only road home.
18. When Things Go Wrong
| Symptom | Likely cause |
|---|---|
docker-compose reports a validation error |
The old hyphenated command. Use docker compose. |
| Database container keeps restarting | The database folder is on a network share or a disk format without Linux permissions. Move it to a local ext4 disk. |
| Database refuses the password | Special characters in DB_PASSWORD. Use letters and digits only. |
| Server is slow or containers are killed | Too little memory. Below 6 GB, turn machine learning off. |
| Phone backup stops when the screen is off | Battery optimisation is stopping the app. Set it to unrestricted. |
| Large video uploads fail through a proxy | The proxy limits body size or times out. Raise both limits. |
Blank page under /photos/ |
Sub-paths are not supported. Use a subdomain. |
| Imported photos all show the import date | Takeout was uploaded without its JSON files. Use immich-go on the zip files. |
| Search returns nothing | The Smart Search queue has not finished, or the model was changed and the job was not run again |
| Photos missing after tidying folders by hand | Files were moved outside Immich. Restore them to the old path. |
| App warns about a version mismatch | Server and app are far apart. Update the older one. |
| Disk fills faster than expected | Thumbnails and converted videos add 10 to 20 percent. Check the storage page in Administration. |
| Shared link does not open for relatives | The server is on a private network only |
19. Epilogue: The Eightieth Birthday
Her father's old phone stayed dead. But in the Takeout export from Yazhini's own account there was a shared album she had forgotten. Her cousin had made it on the day of the eightieth birthday and had added everyone's pictures to it, including forty-one from her father's phone.
immich-go had brought the album across with its name. The face grouping had done the rest.
In December her father came to Chennai for a week. On the second evening Yazhini opened Immich on the television, tapped the face she had named Paatti, and handed him the remote.
There were 316 photos. A studio portrait in black and white, scanned badly. A wedding in Thanjavur in 1998. A blurred one from 2012 with a thumb over the corner. Then the birthday, the garland, the cake, and her grandmother laughing at something outside the frame.
He went through all of them without speaking. Nila climbed onto his lap halfway through and pointed at the screen.
At the end he asked one question. "If this box breaks, are they gone?"
"No, Appa. There is a copy on the second disk, and a locked copy outside the house. I have tested it."
He nodded and went back to the first photo.
At 3 a.m. the mini PC under the television copied the day's photos to both places, and a small bell rang on a phone that was face down on the bedside table.
Quick Reference Cheat-Sheet
| I want to | Do this |
|---|---|
| Get the files | Download docker-compose.yml and example.env from the latest release, rename the second to .env |
| Choose where photos go | UPLOAD_LOCATION in .env, on the big disk |
| Choose where the database goes | DB_DATA_LOCATION in .env, on a local SSD, never a network share |
| Pin the version | IMMICH_VERSION=v3.4.1, not v3 |
| Start it | docker compose up -d |
| Check it | curl -s http://localhost:2283/api/server/ping |
| Create the admin | Open port 2283 and register first |
| Get readable folders | Storage labels for users, then turn on the storage template |
| Back up a phone | Install the app, enter the server address, choose albums, Enable Backup |
| Use it away from home | A private network such as Tailscale, or Caddy with a subdomain |
| Switch address by Wi-Fi | Settings, Networking, Automatic URL switching in the app |
| Import Google Photos | Takeout zip files, then immich-go upload from-google-photos with --dry-run first |
| Show an existing folder | Mount it read-only and add it as an external library |
| See what the server is doing | Administration, Job Queues |
| Share with family on the server | Partner sharing or a shared album |
| Share with anyone | A shared link with a password and an expiry date |
| Back up | Copy UPLOAD_LOCATION without thumbs and encoded-video to a second disk and to an encrypted remote |
| Restore | Same compose file and version, files in the same layout, Restore from backup on the welcome page |
| Upgrade | Read the notes, dump the database, change the version, docker compose pull && docker compose up -d |
Key takeaways
- Immich is four containers and two folders. The upload location holds the photos. The database holds everything that makes them an album.
- The first person to register is the admin. Do it before anyone else knows the address.
- Turn on the storage template early. A library sorted into plain dated folders stays useful even without Immich.
- Import Google Takeout with a tool that reads the JSON files. Otherwise the dates and albums are lost.
- Immich is not a backup. It writes database dumps for you. Copying the photos somewhere else is your job.
- Free up space on the phone only after a restore drill. Until then the server is the only copy.
- Pin the version and upgrade on purpose. Read the release notes, make a dump first, and remember there is no downgrade.
Photos are the one kind of data a family cannot make again. Keep them where you can find them, and keep them twice.