Self-host Immich for family photo backup

Run Immich on a home server with Docker Compose to give the household a Google Photos style timeline, mobile auto-backup, and shared albums.

last verified 2026-05-04 · difficulty 4/5 · Self-host ·A weekend
  • A host with at least 4 CPU cores and 8 GB RAM. Immich is happy to use as much RAM as you give it. 4 GB works only if you disable machine learning, which kills the smart search and face features.
  • Disk space for your library plus 10 to 20 percent for thumbnails and transcodes.
  • A 64-bit Linux OS. Ubuntu 24.04 LTS or Debian 12 are the path of least resistance. Docker on a full VM is supported. Docker inside an LXC container is discouraged by upstream.
  • Docker Engine plus the Compose plugin (docker compose, not the old docker-compose).
  • A local filesystem for the Postgres data and the upload location. EXT4, ZFS, BTRFS, or XFS are fine. Do not put Postgres on NFS or SMB. Photos themselves on a network share are okay if you accept slower scans.
  • A way to reach the server. LAN-only with a hostname like immich.local is the simplest. Remote access needs a reverse proxy, a VPN like WireGuard or Tailscale, or a tunnel.
  • A backup target separate from the host. External USB disk, a second NAS, an offsite box, an object storage bucket. This is not optional.

Immich is a self-hosted photo and video manager with native iOS and Android apps, face recognition, and a timeline that feels close to Google Photos. It runs as a small stack of Docker containers (server, machine learning, Postgres, Redis) and stores your library on whatever disk you point it at.

This guide is for someone who wants the household photo library off Google or iCloud, has a small home server or NAS that can run Docker, and is willing to spend a weekend on setup plus a few hours on backup discipline. Honest tradeoffs up front: the first machine learning pass on a few thousand photos will pin your CPU for hours. iOS background backup is improving but still pauses more often than Google Photos does. There is no progressive web app, so phones really do need the native app. And Immich is pre-1.0 in spirit, even past v2: releases are good about flagging breaking changes, but you have to read them. If “I’ll update it next year” is your style, pick a more boring tool.

Pick the host

Choices, roughly in order of how painless they are:

  • A NAS that runs Docker. Synology DSM, TrueNAS SCALE, UGREEN, QNAP. Best when the NAS already has the photos and the CPU is x86-64 from the last decade. ARM NAS units with under 4 GB RAM will struggle.
  • A dedicated mini-PC. Intel NUC, Beelink, refurbished SFF desktop. Excellent fit. Plenty of CPU for ML, easy to upgrade RAM and disk.
  • A VM on an existing hypervisor. Proxmox, ESXi, Hyper-V. Give it 4 vCPU, 6 to 8 GB RAM, a 32 GB OS disk on local SSD, and mount the photo storage separately. Run Docker inside the VM, not in an LXC.
  • A Raspberry Pi 4 or 5 (8 GB). Works, with caveats. ML is slow. The first scan of a large library will take a long time. Use an SSD over USB 3, never the SD card, for both the OS and Postgres.

Whatever you pick, put the Postgres data on local SSD. Network-attached Postgres is the most common way to ruin an Immich install.

Install Docker

On a fresh Ubuntu or Debian host, use the official Docker repository so you get a current Compose plugin. The short version:

# Remove any old packages
sudo apt-get remove docker docker-engine docker.io containerd runc

# Install prerequisites and add Docker's signing key
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg \
  | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the repo (replace 'debian' with 'ubuntu' on Ubuntu)
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Add your user to the docker group so you don't need sudo for compose
sudo usermod -aG docker $USER
newgrp docker

Verify:

docker --version
docker compose version

Both should print a version. If docker compose version errors out, the Compose plugin did not install. Re-check the package name (docker-compose-plugin).

Pull the Immich compose stack

Immich publishes a docker-compose template and an .env template with each release. Use them. Do not copy random gists.

mkdir -p /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

Open .env and edit at minimum:

# Where uploaded photos and videos go. Absolute path is fine.
UPLOAD_LOCATION=/srv/immich/library

# Where Postgres stores its data. Must be on local disk.
DB_DATA_LOCATION=/srv/immich/postgres

# Set a strong password. A-Z, a-z, 0-9 only. No special characters.
DB_PASSWORD=replace-this-with-a-long-random-string

# Optional but recommended
TZ=Europe/Zurich

Pin the version if you want predictable upgrades. The default is v2, which floats. To pin:

IMMICH_VERSION=v2.7.5

Create the directories you referenced and make sure they are writable by the user that runs Docker:

sudo mkdir -p /srv/immich/library /srv/immich/postgres
sudo chown -R $USER:$USER /srv/immich

If you are bind-mounting an existing photo folder owned by another user (common on a NAS), see the permission note in the pitfalls section before you go further.

Bring it up

cd /opt/immich
docker compose up -d

First start pulls a few GB of images. Watch the logs until the server reports it is listening:

docker compose logs -f immich-server

Look for Immich Server is listening on .... Ctrl-C out once you see it.

Health check:

docker compose ps
curl -s http://localhost:2283/api/server/ping

ping returns {"res":"pong"} when the server is healthy.

First-run admin account

Open http://<host-ip>:2283 in a browser. The first user you register becomes the admin. Pick a strong password and store it in your password manager. There is no built-in password reset email in Phase 1 setups; if you forget it you will be running CLI commands at midnight.

After registering:

  1. Go to Administration > Users and create accounts for the rest of the household. Each user gets their own private library plus access to any shared albums.
  2. Go to Administration > Settings > Storage Template and pick a layout. The default groups by upload date, which is fine. If you want filenames that survive a future migration to plain folders, use something like {{y}}/{{MM}}/{{filename}}.
  3. Go to Administration > Jobs and let the initial jobs settle. If you have not uploaded anything yet there will not be much to do.

Mobile app and auto-backup

Install Immich from the App Store, Play Store, F-Droid, or the GitHub releases page. Sign in with the server URL (for example http://immich.example.local:2283) and the user account.

iOS

  1. Open the app, sign in.
  2. Tap the cloud icon on the bottom bar to enter Backup.
  3. Pick the album to back up. Most people pick “Recents” or the camera roll.
  4. Toggle Background backup on if you want it. iOS will only run it when the app has been used recently and the phone is on Wi-Fi and charging, depending on your settings. Foreground backup (when you open the app) is more reliable.
  5. In iOS Settings > Immich, allow Background App Refresh and grant full Photos access. Without these, background backup will quietly stop.

Android

  1. Open the app, sign in.
  2. Tap Backup, pick the albums to include.
  3. Enable Background backup. Android handles this much better than iOS.
  4. In Android Settings, exempt Immich from battery optimization. Without this, Android will kill the backup worker after a few minutes.

The household should treat the mobile app as the primary upload path. The web UI is fine for bulk drag-and-drop from a desktop, but the apps are where the day-to-day photos arrive.

External library vs Immich-managed

Two ways to get existing photos into Immich.

  • Immich-managed (uploaded). Photos live under UPLOAD_LOCATION in a structure Immich controls. Uploads from the apps and web UI go here. Renaming, deleting, and organizing all happen inside Immich. This is the default and it is what most households want.
  • External library. Immich scans an existing folder you mount into the container and tracks what it finds without moving the files. Useful when you already have a tidy decade of folders by year and event and you do not want Immich to restructure them. Mount the folder read-only with :ro to prevent accidental deletion from the UI.

Tradeoff: external libraries keep your existing structure and let other tools touch the files. Albums and tags you add inside Immich do not get written back to the files; they live only in the database. Immich-managed is the smoother experience, especially for non-technical users in the household.

If you go external, add a volume to the immich-server service in docker-compose.yml:

services:
  immich-server:
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /srv/photos:/mnt/library:ro

Then in the UI go to Administration > External Libraries, create one, and add the import path /mnt/library. The path must match what the container sees, not the host path.

Backups

This section is the difference between a working install and a working photo library you can still recover from in two years.

What you need to back up:

  1. The upload location. Whatever you set as UPLOAD_LOCATION. This is the actual photos and videos. Without it, Postgres is a list of references to nothing.
  2. The Postgres database. Albums, faces, tags, user accounts, shared links, smart-search index. Without it, you have a pile of files and no organization.

Do not just back up the Postgres data directory while the database is running. Use a logical dump.

Daily Postgres dump

Add to the host’s crontab:

0 3 * * * docker exec -t immich_postgres pg_dumpall --clean --if-exists -U postgres | gzip > /srv/immich/backup/immich-$(date +\%F).sql.gz && find /srv/immich/backup -name 'immich-*.sql.gz' -mtime +14 -delete

That keeps 14 days of dumps. Adjust to taste. The container name above (immich_postgres) matches the upstream compose; if you renamed it, fix the command.

Photos backup

Use whatever you already trust: restic, borg, rsync to a second box, Synology Hyper Backup, a USB disk you swap weekly, an offsite copy via something like rsync.net. The 3-2-1 rule applies: three copies, two media, one offsite.

A minimal restic example to a local USB disk:

restic -r /mnt/backup-usb/immich init   # one-time
restic -r /mnt/backup-usb/immich backup /srv/immich/library /srv/immich/backup
restic -r /mnt/backup-usb/immich forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune

Test the restore

Once. To a scratch directory. Before you need it. An untested backup is a hope, not a backup.

Updating Immich

Immich releases often, and minor versions occasionally introduce breaking changes. The official upgrade guide is the source of truth: https://docs.immich.app/install/upgrading.

The short loop:

  1. Read every release note between your current version and target. Look for “Breaking Changes” or warning icons.
  2. Take a Postgres dump (the cron job above counts, but take a fresh one before you pull).
  3. Diff your local docker-compose.yml and .env against the upstream templates for the target version. Apply any new required env vars or service blocks.
  4. docker compose pull && docker compose up -d.
  5. Watch docker compose logs -f immich-server until migrations finish and the server is listening.
  6. Sanity-check the UI: log in, timeline loads, search works, jobs pane is clean.
  7. docker image prune -f once you are confident.

Skipping a major version because you missed three months of releases is the most common way people break their library.

  • curl -s http://localhost:2283/api/server/version returns a sensible version.
  • docker compose ps shows all containers healthy.
  • Uploaded test photo from the web appears in the timeline within seconds.
  • Mobile-taken photo via foreground backup shows up on the web UI.
  • Smart Search returns results for terms like 'beach' or 'dog' once jobs finish.
  • A photo's info panel shows location, camera, and date populated.
  • ML container OOM-killed on small hosts

    If docker compose ps shows immich-machine-learning restarting, you are out of RAM. Either give the host more memory, switch to a smaller ML model in Administration > Settings > Machine Learning, or disable ML entirely. The first scan is the worst; steady state is much lighter.

  • iOS auto-backup pausing

    Almost always one of: Background App Refresh disabled, Photos permission set to "Limited" instead of "All Photos", Low Power Mode on, or the user has not opened the app in a week. Foreground backup (open the app, watch the queue drain) is the reliable fallback.

  • Library scan loops

    Usually a permission problem. The Immich server runs as UID 1000 by default; if the bind-mounted folder is owned by a different UID and the container cannot read or write some files, it will retry forever. Fix the ownership, or set PUID/PGID in .env to match the host owner.

  • Postgres on NFS or SMB

    Will appear to work, then corrupt the database the first time the network blips. Move it to local disk before you finish the install. The requirements page is explicit about this.

  • Forgot the admin password

    No email reset. You will be running immich-cli or psql commands. Save the password in your password manager the moment you set it.

  • Mobile app cannot reach the server

    Check that the URL the app uses is reachable from the phone's network. Phones on cellular cannot reach 192.168.x.x without a VPN. If the household needs remote access, set up Tailscale or a reverse proxy with a real domain before you onboard them.

If you decide Immich is not for you, or the household revolts:

  1. Keep the old workflow running for at least a month after switching. Do not delete the Google Photos / iCloud / Proton Drive / NAS folder while you are still validating.
  2. To pause Immich without uninstalling: cd /opt/immich && docker compose down. The data stays on disk.
  3. To roll back a bad upgrade: pin every Immich image tag in docker-compose.yml to the last known-good version, then docker compose up -d. If the database migration ran and broke things, restore the most recent dump into Postgres before bringing the server back up.
  4. To leave entirely: export your library. Photos already live as plain files under UPLOAD_LOCATION, organized per your storage template. The albums and faces stay behind in Postgres, but the photos are portable.

The household will judge this on the first month. Plan for it.


Did this work for you?


Comments (0)

No comments yet. Be the first.

Add a comment