← All Guides
beginner

Immich vs Google Photos: Self-Hosted Photo Backup That Actually Works

Set up Immich on Docker to replace Google Photos. Face recognition, mobile backup, timeline view, all on your own hardware. Full Docker Compose walkthrough.

Budget Homelab ·
dockerhow-tobeginnerself-hosting

This post contains affiliate links. If you buy through them, I earn a small commission at no extra cost to you.

Immich is the fastest-growing self-hosted app in the homelab community, and it’s obvious why. It looks like Google Photos. It works like Google Photos. It has face recognition, a map view, timeline browsing, album sharing, and a mobile app that auto-backs up your camera roll — all on a server you control, with no subscription and no one else’s hands on your photos.

I migrated 15 years of family photos to Immich last year. My wife uses it daily without knowing it’s “self-hosted.” That’s the bar it clears.

This guide covers the full Docker Compose setup, migration from Google Photos, and mobile backup configuration.

For prerequisites and Docker setup, see the Getting Started guide and Docker Compose basics. For remote access after setup, see the Tailscale guide.

Prerequisites


Step 1: Create the directory structure

mkdir -p ~/docker/immich/{upload,model-cache}

The upload folder is where Immich stores all photos. The model-cache folder holds the machine learning models for face recognition and CLIP search.


Step 2: Create the environment file

Immich requires a few environment variables. Create ~/docker/immich/.env:

# Immich config
UPLOAD_LOCATION=./upload
DB_DATA_LOCATION=./postgres

# Database credentials (change these)
DB_PASSWORD=change_this_password_now
DB_USERNAME=postgres
DB_DATABASE_NAME=immich

# Immich version (pin this, don't use latest)
IMMICH_VERSION=release

Change DB_PASSWORD to a strong random password. Immich’s database stores all your metadata — protect it.


Step 3: Write the Docker Compose file

Create ~/docker/immich/docker-compose.yml. Immich uses multiple containers — the server, microservices, machine learning, Redis, and PostgreSQL:

name: immich

services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
    env_file:
      - .env
    ports:
      - "2283:2283"
    depends_on:
      - redis
      - database
    restart: unless-stopped
    healthcheck:
      disable: false

  immich-machine-learning:
    container_name: immich_machine_learning
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
    volumes:
      - ./model-cache:/cache
    env_file:
      - .env
    restart: unless-stopped
    healthcheck:
      disable: false

  redis:
    container_name: immich_redis
    image: docker.io/redis:6.2-alpine
    healthcheck:
      test: redis-cli ping
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  database:
    container_name: immich_postgres
    image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_DB: ${DB_DATABASE_NAME}
      POSTGRES_INITDB_ARGS: '--data-checksums'
    volumes:
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    healthcheck:
      test: >-
        pg_isready --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" || exit 1;
        Chksum="$$(psql --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" --tuples-only --no-align
        --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')";
        echo "checksum failure count - $${Chksum}";
        [ "$${Chksum}" = '0' ] || exit 1
      interval: 5m
      start_interval: 30s
      start_period: 5m
    command: >-
      postgres
      -c shared_preload_libraries=vectors.so
      -c 'search_path="$$user", public, vectors'
      -c logging_collector=on
      -c max_wal_size=2GB
      -c shared_buffers=512MB
      -c wal_compression=lz4
    restart: unless-stopped

This is Immich’s official recommended Compose file as of early 2026. Immich updates frequently — check immich.app/docs if something doesn’t work.


Step 4: Start Immich

cd ~/docker/immich
docker compose up -d

The first start takes 2–5 minutes while containers pull and initialize. Watch the logs:

docker compose logs -f immich-server

When you see Immich Server is listening on port 2283, it’s ready.


Step 5: Initial setup

Open http://your-server-ip:2283 in a browser.

  1. Create an admin account with your email and a strong password
  2. Storage template (optional): Configure how Immich organizes files in the upload folder. I use {{y}}/{{MM}}/{{dd}} (year/month/day) — easy to navigate if you ever need to access files directly
  3. Skip external library setup for now — we’ll cover that below if you have an existing photo collection

Step 6: Mobile backup setup

Install the Immich app on your phone (iOS or Android — search “Immich” in the App Store or Play Store).

  1. Open the app and tap the settings icon
  2. Enter your server URL: http://your-server-ip:2283 (or your Tailscale IP for remote access)
  3. Log in with your admin credentials
  4. Go to Settings → Backup and enable automatic backup
  5. Choose which albums to back up (default: all photos)

Your phone will now automatically back up new photos when connected to WiFi. For background backup over cellular, you can enable it in the backup settings — be aware this will use mobile data.

For remote access without opening ports, add Tailscale to both your server and phone. Once connected, use your server’s Tailscale IP (100.x.x.x:2283) as the server URL.


Step 7: Import your Google Photos library

If you’re migrating from Google Photos:

1. Export your library from Google Takeout

Go to takeout.google.com, select only “Google Photos,” choose your export format (I recommend multiple ZIP files of 10GB each for easier handling), and request the export. Google will email you download links within a few hours to a few days.

2. Extract the archives

mkdir ~/google-photos-export
cd ~/google-photos-export
# unzip all archives
for f in ~/Downloads/takeout-*.zip; do unzip "$f"; done

3. Use the Immich CLI to import

Immich provides a CLI tool designed specifically for Google Takeout imports. It handles the JSON metadata files that Google includes (which contain original timestamps and GPS data):

# Install the CLI
npm install -g @immich/cli

# Log in
immich login http://your-server-ip:2283 [email protected]

# Import the Google Takeout folder
immich upload --recursive ~/google-photos-export/Takeout/Google\ Photos/

The CLI reads the .json metadata files alongside each photo and uses the original capture date rather than the import date. Without this, all your Google Photos would show the import date instead of when you actually took them.

This import process can take hours for large libraries. Let it run overnight.


Step 8: Set up external libraries (existing photo collection)

If you have photos stored elsewhere on your server that you want Immich to index without moving them:

  1. Go to Administration → External Libraries
  2. Click “Create External Library”
  3. Set the path to your photo folder (the Docker volume path, e.g., /photos if you added a volume)
  4. Run a scan

External library photos are indexed and appear in the timeline but are not copied to Immich’s upload folder. You control the files; Immich just reads them.

To add an external library volume, add it to your Compose file:

  immich-server:
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /mnt/photos:/photos:ro
      - /etc/localtime:/etc/localtime:ro

Immich’s machine learning container handles two things:

Face recognition: Immich automatically clusters faces across your library. Go to the Explore → People tab — you’ll see grouped faces. Click one to name the person, and that person becomes searchable across your entire library.

Face recognition runs on your server’s CPU by default. On an N100, this takes several hours for a large library (10,000+ photos) — let it run in the background. You can track progress in the Jobs panel in administration settings.

CLIP search: Natural language photo search. Type “beach sunset” or “birthday cake” in the search bar and Immich returns photos matching the concept, even if those words aren’t in any metadata. This feature impressed me more than I expected.

If your server is tight on RAM (under 4GB usable), you can disable machine learning entirely. Add DISABLE_MACHINE_LEARNING=true to your .env and recreate the containers with docker compose up -d. Face recognition and CLIP search will be unavailable, but everything else works normally.


Sharing photos

Immich has a clean sharing model:


Backup strategy for Immich itself

Your Immich data lives in two places: the upload folder (the actual photo files) and the PostgreSQL database (metadata, albums, face data, sharing settings).

Back up the upload folder with whatever your standard file backup strategy is (Syncthing to another device, Restic to Backblaze B2, etc.).

Back up the database with a daily dump:

docker exec immich_postgres pg_dumpall --clean --if-exists --username=postgres \
  > ~/backups/immich-db-$(date +%Y%m%d).sql

Add this to a cron job. Without a database backup, losing the PostgreSQL container means losing all your metadata, albums, and face recognition work even if the photos themselves survive.


Updating Immich

Immich releases updates frequently (weekly in active development periods). The project recommends keeping up with updates:

cd ~/docker/immich
docker compose pull
docker compose up -d

If you pinned IMMICH_VERSION=release in your .env, this will always pull the latest release tag. Check the Immich GitHub releases for release notes before major version bumps.


Immich is one of those rare self-hosted apps that genuinely competes with the cloud service it replaces. The mobile backup experience is smooth enough to use daily without thinking about it. Once you’ve been running it for a few months, canceling Google One feels like an obvious decision.

For syncing photos from external sources or other devices, see the Syncthing setup guide. For the full list of high-value apps to self-host next, see the best self-hosted apps roundup.