Immich: Self-Hosted Google Photos Alternative
Why Self-Host Your Photos? The Immich Case
Google Photos sunsetting unlimited storage and charging $2/month for 100GB is a wake-up call for anyone with 50k+ family photos. Immich solves this: a self-hosted photo library with ML-powered face recognition, mobile auto-backup, and zero subscription fees. This post walks you through deploying Immich on your homelab infrastructure using Docker Compose, configuring the machine learning pipeline, and setting up reliable mobile backup—exactly how I've run it on my T5810 workstation with 32GB RAM for three years.
You'll need a dedicated machine or spare NAS slot, at least 8GB RAM (more if you want fast ML inference), and patience for the initial face recognition indexing pass. If you've already got a Docker-based homelab running, Immich slots in cleanly. If you're new to Docker, this isn't the starting point—go learn Compose basics first.
Prerequisites: Versions and System Requirements
Software versions tested:
- Immich server: 1.107.1
- Immich mobile app: 1.107.1 (iOS/Android)
- Docker: 26.1.3
- Docker Compose: v2.27.0
- Ubuntu: 24.04.1 LTS
Hardware requirements:
- CPU: 4+ cores (8+ recommended for ML inference without painful delays)
- RAM: 8GB minimum, 16GB if running face recognition continuously
- Storage: 2× your photo library size (one for originals, one for cache/thumbnails)
- Network: 1Gbps+ for mobile uploads, especially if syncing 4K video
This setup assumes you have Docker and Docker Compose already installed. Confirm versions with docker --version and docker compose version.
Docker Compose Configuration: The Foundation
Immich's architecture includes a server, microservices for ML processing, PostgreSQL, and Redis. The official Docker Compose file is solid, but I'll show you my production variant with explicit resource limits and persistent volume binds.
version: '3.8'
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:v1.107.1
command: start.sh immich
volumes:
- /mnt/photos/library:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
environment:
DB_HOSTNAME: immich-db
DB_USERNAME: immich
DB_PASSWORD: ${DB_PASSWORD}
DB_NAME: immich
REDIS_HOSTNAME: immich-redis
IMMICH_MACHINE_LEARNING_ENABLED: 'true'
IMMICH_MACHINE_LEARNING_URL: http://immich-ml:3003
IMMICH_API_FIFO_QUEUE_MIN_JOB_COUNT: 5
ports:
- "2283:3001"
depends_on:
- immich-db
- immich-redis
- immich-ml
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/server/ping"]
interval: 30s
timeout: 10s
retries: 3
immich-microservices:
container_name: immich_microservices
image: ghcr.io/immich-app/immich-server:v1.107.1
command: start.sh microservices
volumes:
- /mnt/photos/library:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
environment:
DB_HOSTNAME: immich-db
DB_USERNAME: immich
DB_PASSWORD: ${DB_PASSWORD}
DB_NAME: immich
REDIS_HOSTNAME: immich-redis
IMMICH_MACHINE_LEARNING_ENABLED: 'true'
IMMICH_MACHINE_LEARNING_URL: http://immich-ml:3003
IMMICH_API_FIFO_QUEUE_MIN_JOB_COUNT: 5
depends_on:
- immich-db
- immich-redis
- immich-ml
restart: unless-stopped
deploy:
resources:
limits:
cpus: '2'
memory: 2G
immich-ml:
container_name: immich_ml
image: ghcr.io/immich-app/immich-machine-learning:v1.107.1
volumes:
- /mnt/photos/ml-cache:/cache
environment:
PYTHONUNBUFFERED: '1'
restart: unless-stopped
deploy:
resources:
limits:
cpus: '4'
memory: 4G
immich-db:
container_name: immich_db
image: postgres:15-alpine
environment:
POSTGRES_DB: immich
POSTGRES_USER: immich
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- /mnt/photos/db:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U immich"]
interval: 10s
timeout: 5s
retries: 5
immich-redis:
container_name: immich_redis
image: redis:7.2-alpine
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
networks:
default:
driver: bridge
Save this as docker-compose.yml in your Immich directory. Create a .env file with a strong database password:
echo "DB_PASSWORD=$(openssl rand -base64 24)" > .env
Gotcha #1: The ML container needs 4GB RAM and 4 CPU cores allocated, or face recognition jobs will time out or queue infinitely. If you're on constrained hardware (like a Raspberry Pi), disable IMMICH_MACHINE_LEARNING_ENABLED and run face recognition on a separate, more powerful machine via a shared PostgreSQL instance.
Start the stack with docker compose up -d. Wait 30 seconds for PostgreSQL to initialize, then check logs: docker compose logs -f immich-server. You should see "Listening on 0.0.0.0:3001" without errors.
Initial Setup and Mobile Backup Configuration
Navigate to http://your-homelab-ip:2283. You'll see the admin setup screen. Create your account and configure authentication. Then, in Settings → Backup, you'll set up automatic backup rules for mobile devices.
For iOS users: Download the Immich app from the App Store, enter your server URL (https://immich.yourdomain.com—I'll cover reverse proxy setup below), log in, and toggle Backup enabled. Choose which albums to back up (I backup everything except screenshots to save space). Set backup frequency to "Daily" unless you're generating hundreds of photos per day.
For Android users: Same process via Google Play. The Android implementation is equally solid; Immich doesn't play favorites between platforms.
Test the backup by taking a photo on your phone. It should appear in the Immich web interface within 2-5 minutes, depending on your network and photo size.
Gotcha #2: If mobile backup stalls, check that your Immich server is reachable on your home network via the URL you entered. Firewall rules, especially on UFW, block 2283 by default. Open it with sudo ufw allow 2283/tcp. Also verify that your mobile device and Immich server are on the same network or that you've set up a reverse proxy (Nginx, Traefik) with proper certificates for external access.
Machine Learning Setup: Face Recognition and Search
Immich's ML pipeline detects faces, generates embeddings, and clusters them into people. On first run with 50k photos, expect 4-8 hours of processing on a mid-range CPU. The ML container runs in the background—no user intervention needed—but you can monitor progress in Settings → Jobs.
If you want to accelerate inference on an Nvidia GPU, add GPU support to the compose file. First, install Nvidia Container Runtime on your host:
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
&& curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - \
&& curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list \
| sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update && sudo apt-get install -y nvidia-container-runtime
sudo systemctl restart docker
Then update the immich-ml service in compose:
immich-ml:
container_name: immich_ml
image: ghcr.io/immich-app/immich-machine-learning:v1.107.1
volumes:
- /mnt/photos/ml-cache:/cache
environment:
PYTHONUNBUFFERED: '1'
runtime: nvidia
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
restart: unless-stopped
Redeploy with docker compose up -d. Face recognition will now run 5-10× faster depending on your GPU. On my T5810 with an RTX 4000, 50k photos index in ~45 minutes.
Once ML completes, open the Explore tab to browse people, map, and timeline views. Immich's smart search bar supports queries like "dog 2023" or "beach face:john" if you've tagged people.
Reverse Proxy and Remote Access
For secure remote access—crucial for mobile backup outside your home network—use a reverse proxy. I'll show Nginx Proxy Manager (NPM), which manages SSL certificates automatically via Let's Encrypt.
Deploy NPM via Compose in the same stack or separately:
nginx-proxy-manager:
container_name: npm
image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
- '80:80'
- '443:443'
- '81:81'
environment:
DB_MYSQL_HOST: npm-db
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: npm
DB_MYSQL_PASSWORD: ${NPM_DB_PASSWORD}
DB_MYSQL_NAME: npm
volumes:
- /mnt/npm/data:/data
- /mnt/npm/letsencrypt:/etc/letsencrypt
depends_on:
- npm-db
npm-db:
container_name: npm-db
image: 'mysql:8.0-oracle'
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${NPM_DB_PASSWORD}
MYSQL_DATABASE: npm
MYSQL_USER: npm
MYSQL_PASSWORD: ${NPM_DB_PASSWORD}
volumes:
- /mnt/npm/mysql:/var/lib/mysql
Access NPM at port 81, create a proxy host pointing to immich-server:3001, and assign your domain. NPM handles SSL renewal automatically. Your mobile app now connects via https://immich.yourdomain.com securely.
Common Issues and Troubleshooting
Mobile app won't connect, "Connection refused" or timeout: Check that your server URL is reachable. Test from your phone with curl https://immich.yourdomain.com/api/server/ping (requires curl, so use a browser and navigate to that URL). If you're inside your home network, use the internal IP (e.g., http://192.168.1.50:2283). Outside your network, ensure your reverse proxy is running and your domain DNS points to your home IP.
Face recognition jobs stuck in "Processing" state: This usually means the ML container crashed. Check logs with docker compose logs immich-ml | tail -20. Out-of-memory is the most common cause—increase the ML container's memory limit to 6GB or higher if you have it. Alternatively, restart the ML container: docker compose restart immich-ml.
Database bloat and slow queries after months of operation: PostgreSQL needs periodic maintenance. Run a VACUUM and ANALYZE on the immich database:
docker compose exec immich-db psql -U immich -d immich -c "VACUUM ANALYZE;"
Schedule this weekly via cron if you have 100k+ photos.
Thumbnails not generating, web UI shows "image error": The microservices container may not have write permissions to the upload volume. Ensure the volume directory is writable by the Docker user (UID 1000 inside containers). Fix with:
sudo chown -R 1000:1000 /mnt/photos/library /mnt/photos/ml-cache
Next Steps: Albums, Sharing, and Maintenance
You now have a fully functional, self-hosted photo library. Create albums for trips or family members. Generate public share links (time-gated or permanent) for sharing with relatives who don't have Immich accounts. Use the mobile app's favorite button to tag important photos, then filter by favorites in the web UI.
For long-term maintenance, set up backups of the PostgreSQL database and the library directory. A simple weekly backup job piped to an offsite NAS keeps you safe:
#!/bin/bash
docker compose exec -T immich-db pg_dump -U immich immich | gzip > /mnt/backup/immich-$(date +%Y%m%d).sql.gz
rsync -av /mnt/photos/library /remote-nas/backups/immich-lib/
Monitor disk space closely; Immich's thumbnail caching can consume 15-20% of your original library size. Set up alerts in your monitoring system when free space drops below 50GB.
For advanced setups, explore Immich's API to build custom automations—trigger backup jobs, generate statistics, or auto-tag photos by date or location using webhooks and external scripts.
Start with this foundation and expand as your library grows. Immich is actively developed, so check GitHub releases monthly for security updates and new features
Disclosure: This post contains affiliate links. If you purchase through these links, we may earn a small commission at no extra cost to you. We only recommend services we've tested and trust.