Immich Setup and Google Photos Migration
By LK Wood IV · 2026-06-13 · ~14 min read · St. Louis County, MO
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
| Scenario | RAM | CPU | GPU |
|---|---|---|---|
| Small library (<20K photos), no active ML | 4GB | 2 cores | Not needed |
| Typical library (20–100K photos) | 8–16GB | 4 cores | Helpful but not required |
| Large library (100K+ photos), fast initial index | 16–32GB | 6–8 cores | CUDA GPU recommended for indexing speed |
| Running alongside other services | Add 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:
- Open the app → enter your server URL:
http://192.168.1.10:2283(or your NPM domain if you’ve set that up) - Log in with your account
- Tap the profile icon → Backup → configure backup on WiFi only or on any network
- 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:
- Go to takeout.google.com
- Deselect everything, then select “Google Photos” only
- Choose “Export once”, maximum file size (50GB per archive),
.zipformat - 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.
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)
wget "https://github.com/simulot/immich-go/releases/download/${IMMICH_GO_VERSION}/immich-go_Linux_arm64.tar.gz"
# For x86_64:
wget "https://github.com/simulot/immich-go/releases/download/${IMMICH_GO_VERSION}/immich-go_Linux_x86_64.tar.gz"
tar xzf immich-go_*.tar.gz
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-servercontainer 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:
UPLOAD_LOCATION— all your original photos and videos- 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.