Quick answer

Takeout returns five to twenty zips capped at 50GB each, ready within 24 hours. Unzip them to staging, then run immich-go upload against http://localhost:2283 with –google-photos and –create-albums so the .json sidecars restore dates, locations and albums. Indexing 80,000 photos takes 8-12 hours on CPU, 2-4 with CUDA.

By LK Wood IV · 2026-06-11 · ~14 min read · St. Louis County, MO

Architecture diagram of the Immich self-hosted photo stack: Google Takeout zips imported via immich-go alongside mobile background backups into the Immich Docker Compose stack (server, microservices, machine-learning, redis, and PostgreSQL with pgvecto-rs) on port 2283, with hardware ML acceleration timings for CPU, Intel QSV, and NVIDIA CUDA, plus an Nginx Proxy Manager reverse proxy.

Immich is the only self-hosted photo library that’s actually replaced Google Photos for most users in 2026 — face recognition, CLIP semantic search (“photos of the lake”), the timeline view, and a mobile backup app that runs automatically in the background. This guide sets up Immich from scratch and migrates an existing Google Photos library using the Takeout export path.

For the Immich vs PhotoPrism vs Ente comparison, see the self-hosted photo library showdown.

Hardware requirements

ScenarioRAMCPUGPU
Small library (<20K photos), no active ML4GB2 coresNot needed
Typical library (20–100K photos)8–16GB4 coresHelpful but not required
Large library (100K+ photos), fast initial index16–32GB6–8 coresCUDA GPU recommended for indexing speed
Running alongside other servicesAdd 4–8GB buffer

ML indexing speed matters most during the initial import. On a library of 80,000 photos:

  • CPU-only indexing: ~8–12 hours
  • Intel Quick Sync (VAAPI): ~4–6 hours
  • NVIDIA GPU (CUDA): ~2–4 hours

After initial indexing, ongoing ML processing (new photos) runs in minutes.

Step 1: Deploy Immich with Docker Compose

Create the Immich stack directory:

mkdir -p /opt/stacks/immich && cd /opt/stacks/immich

Download the official Docker Compose files:

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

Edit .env:

nano .env

Set these values:

# Upload path — where your photos will be stored
# Point this at your NAS mount or a large volume
UPLOAD_LOCATION=/mnt/photos/immich

# Database credentials (generate strong passwords)
DB_PASSWORD=your-strong-db-password-here

# Timezone
TZ=America/Chicago

Create the upload directory:

mkdir -p /mnt/photos/immich

Pin the Immich version — the :release tag in the official compose file is a moving target. Pin to a specific version to avoid surprise schema migrations:

# Find the latest stable release
IMMICH_VERSION=$(curl -s https://api.github.com/repos/immich-app/immich/releases/latest | grep '"tag_name"' | cut -d '"' -f 4)
echo "IMMICH_VERSION=$IMMICH_VERSION" >> .env

Start Immich:

docker compose up -d

Immich starts several containers: immich-server, immich-microservices, immich-machine-learning, redis, and database. The initial startup takes 2–3 minutes while the database initializes.

Access the web UI at http://your-host-ip:2283. Create the admin account on first launch.

Step 2: Configure storage and external libraries

In Immich Settings → Storage:

The default storage structure stores originals in UPLOAD_LOCATION/{userID}/{year}/{month}/. If you prefer to preserve the original directory layout from your existing photo archive, configure an External Library instead:

Administration → External Libraries → Create Library:

  • Point it at an existing folder (e.g., /mnt/photos/archive) that Immich mounts read-only
  • Immich indexes and searches photos in this location without copying or modifying them
  • Use this path for existing photo archives you want to browse in Immich but manage outside of it

For Google Takeout imports and new phone backups, let Immich manage the storage via the Upload path.

Step 3: Mobile backup setup

Immich mobile app is available on iOS and Android (search “Immich” in the App Store / Play Store).

After installing:

  1. Open the app → enter your server URL: http://192.168.1.10:2283 (or your NPM domain if you’ve set that up)
  2. Log in with your account
  3. Tap the profile icon → Backup → configure backup on WiFi only or on any network
  4. Enable “Background Backup” in the app settings

For background backup to work reliably on iOS:

  • Go to iOS Settings → Immich → allow background app refresh
  • On iOS, the Immich app must be opened periodically to trigger background sync (iOS background task limitation)

For full automatic background backup on Android, enable the “Ignore Battery Optimization” option in the app.

Step 4: Enable hardware ML acceleration

Hardware acceleration dramatically reduces the time to index your library. Configure it based on your hardware.

NVIDIA GPU (CUDA):

In docker-compose.yml, under immich-machine-learning:

  immich-machine-learning:
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}-cuda
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

The NVIDIA Container Toolkit must be installed on the host:

# On Proxmox or Docker host
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
  sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
  tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
apt update && apt install -y nvidia-container-toolkit
systemctl restart docker

Intel Quick Sync (VAAPI) / OpenVINO:

  immich-machine-learning:
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}-openvino
    devices:
      - /dev/dri:/dev/dri

After changing the image and adding device access, redeploy:

docker compose pull
docker compose up -d

In the Immich web UI → Administration → Jobs → Machine Learning: confirm the GPU is being used (processing speed should increase noticeably).

Step 5: Migrate Google Photos via Takeout

Request your Takeout export:

  1. Go to takeout.google.com
  2. Deselect everything, then select “Google Photos” only
  3. Choose “Export once”, maximum file size (50GB per archive), .zip format
  4. Google emails you when the export is ready (30 min to 24 hours)

Download all the zip files to your server. With a large library, expect 5–20 zips. At up to 50 GB per archive that is a serious one-time download, so if your ISP enforces a monthly cap, check it against your normal usage with the data usage calculator before you kick it off.

Install immich-go, the tool that handles Takeout import correctly (preserving metadata from .json sidecars):

# Get the latest release from GitHub
IMMICH_GO_VERSION=$(curl -s https://api.github.com/repos/simulot/immich-go/releases/latest | grep '"tag_name"' | cut -d '"' -f 4)
# Pick the build for your architecture. Most homelab hosts are x86_64;
# use immich-go_Linux_arm64.tar.gz on a Pi or other ARM box.
ARCH_FILE=immich-go_Linux_x86_64.tar.gz
wget "https://github.com/simulot/immich-go/releases/download/${IMMICH_GO_VERSION}/${ARCH_FILE}"
tar xzf "${ARCH_FILE}"
mv immich-go /usr/local/bin/

Get your Immich API key: Immich web UI → Profile → API Keys → New API Key.

Run the import:

# Extract all Takeout zips to a staging directory
mkdir -p /mnt/staging/takeout
for f in /mnt/staging/takeout-zips/*.zip; do
  unzip -q "$f" -d /mnt/staging/takeout
done

# Import with immich-go
immich-go upload \
  --server http://localhost:2283 \
  --api-key YOUR-API-KEY \
  --google-photos \
  --create-albums \
  /mnt/staging/takeout/Takeout/Google\ Photos/

The --google-photos flag tells immich-go to look for .json sidecar files and apply their metadata. The --create-albums flag recreates your Google Photos albums in Immich.

Expect 30–90 seconds of processing per 1,000 photos for a CPU-based ML setup. Watch progress with immich-go output — it reports files processed, skipped (duplicates), and any errors.

Handle duplicates: If you’ve already imported some photos via mobile backup before running Takeout, immich-go detects duplicates by file hash and skips them. You won’t get duplicates in your library.

After import completes, let Immich run ML indexing on the new photos. In the web UI → Administration → Jobs → Face Detection: queue this job to run. It can take hours on a large library.

Step 6: Reverse proxy with Nginx Proxy Manager

Get your Immich accessible at a clean URL instead of 192.168.1.10:2283:

In Nginx Proxy Manager → Add Proxy Host:

  • Domain: photos.yourdomain.com
  • Scheme: http
  • Forward to: immich-server container name (or LAN IP if separate host)
  • Port: 2283
  • SSL: enable with your wildcard cert
  • Client max body size: Set in the Advanced tab: client_max_body_size 0; — required for large file uploads from mobile

Update the mobile app: replace the server URL with https://photos.yourdomain.com.

Step 7: Backup strategy

Immich’s official backup documentation is thorough. The short version:

What to back up:

  1. UPLOAD_LOCATION — all your original photos and videos
  2. PostgreSQL database — ML embeddings, face clusters, album structure, metadata

PostgreSQL backup:

docker exec -t immich_postgres pg_dumpall -c -U postgres > \
  /mnt/backups/immich/db-$(date +%Y%m%d).sql

Automate this with a cron job:

echo "0 3 * * * root docker exec -t immich_postgres pg_dumpall -c -U postgres > /mnt/backups/immich/db-\$(date +\%Y\%m\%d).sql" >> /etc/crontab

Photo backup via restic:

If you’re running restic for off-site backups, add UPLOAD_LOCATION and your database dump directory to the backup paths. Photos are the highest-priority backup target — originals are only in one place.

A common mistake: backing up only the Docker volumes (which contain thumbnails and cache) instead of UPLOAD_LOCATION. Back up the actual photo directory.

Common issues

ML indexing not starting: Check that immich-machine-learning container is running: docker compose ps. If it’s restart-looping, check logs: docker compose logs immich-machine-learning. CUDA issues usually show as “CUDA not available” — verify the NVIDIA Container Toolkit is installed and the container has GPU access.

Google Takeout import skipping files: immich-go skips files it has already imported (by hash). Run with --dry-run first to see what would be imported without committing.

Mobile app can’t connect: Verify the server URL includes the port if you’re using the direct IP (http://192.168.1.10:2283). If using NPM, verify the client_max_body_size 0 setting is in the advanced config and that the SSL cert is valid.

Thumbnails not generating: Jobs in Immich run asynchronously. After a large import, the thumbnail and ML jobs queue behind each other. Check Administration → Jobs — each job type shows queued/active/failed counts. Failed jobs usually indicate a storage permission issue on UPLOAD_LOCATION.


Comparing Immich to PhotoPrism and Ente before committing? The self-hosted photo library showdown covers all three. For access from anywhere, Tailscale or WireGuard give your mobile app a direct path to Immich without exposing the server publicly.

Sources

Frequently asked questions

How long does Google Takeout take to process?
Requesting Google Takeout for a large Photos library (100GB+) typically takes 1–24 hours for Google to prepare. You’ll receive an email when it’s ready to download. Plan this in advance — it’s the longest step in the migration.
Does Immich import all Google Photos metadata (location, date, albums)?
Yes, with the right process. Google Takeout exports photos alongside .json sidecar files that contain location, creation date, description, and shared album information. Immich’s Google Takeout importer reads these sidecar files and imports the metadata correctly. If you import raw photos without the sidecar files, you lose the metadata.
How much RAM does Immich need?
The minimum for a working install is 4GB — the ML model for face recognition and CLIP embeddings consumes 2–3GB during indexing. The database (PostgreSQL with pgvecto-rs) needs another 1GB at idle. In practice, 8–16GB is comfortable for a library under 100K photos. For initial indexing of a large library, Immich will use all available RAM until the index is complete — plan accordingly.
Can I keep my original photos without Immich modifying them?
Immich stores photos at the path you configure in UPLOAD_LOCATION. The originals are never modified or moved once stored. Immich generates thumbnails and ML embeddings alongside the originals in a separate directory structure. This means you can rsync UPLOAD_LOCATION to any backup target and recover the originals without Immich.
What happens to the Google Photos metadata that doesn't match a file?
Google Takeout sometimes creates more .json files than photos (for shared albums, duplicates it filtered, etc.). The immich-go tool reports these orphaned sidecar files during import so you can review them. Most are benign — duplicates Google filtered, shared album copies you already have.

Evidence ledger

Last updated
Methodology
This tutorial was written and edited by Lowell K. Wood IV in St. Louis County, MO. Specs, prices, commands, and version numbers are drawn from the official vendor, reseller, and project documentation current on the date above, and were verified before publishing. First-person hardware claims appear only where the article shows a verifiable artifact — a photo, receipt, or measurement — or links to the TechFuelHQ Open Bench Datasets. Every fact is human-verified against its cited source before publishing; AI assists with first-draft structure and source-gathering, not with the verdict. Full editorial standard: methodology.
Update log
  • 2026-07-25 — Last reviewed and updated.
Corrections
Spotted an error or stale price? Email hello@techfuelhq.com. Confirmed corrections are added to the update log above.

About the author

Written by Lowell K. Wood IV. Lowell builds and runs TechFuelHQ from St. Louis, Missouri, pairing thirteen-plus years of hands-on homelab, PC, server, and networking experience with cited third-party testing and first-party benchmarks on the gear he still runs. He also works ground EMS as a Nationally Registered Paramedic (NREMT).