Self Hosting 11 min read

Immich Tutorial: Self-Hosted Google Photos Option

Suresh S Suresh S
Immich Tutorial: Self-Hosted Google Photos Option

We generate and store more personal data than ever before. Cloud-hosted photo platforms like Google Photos and Apple iCloud provide convenient photo backups, but they carry long-term risks. Monthly subscription fees add up over time, and automated scanning algorithms analyze your private photos to train AI models or run automated account checks.

For self-hosting enthusiasts, Immich represents the ultimate solution. It is a high-performance, open-source photo and video management system designed to be hosted locally. Immich matches the user interface, speed, and features of commercial cloud storage platforms—providing native mobile apps, automatic backups, and local machine learning capabilities.

In this comprehensive tutorial, we will explore the architecture of Immich, walk through a step-by-step Docker Compose deployment, configure database setups, implement secure remote access using Caddy reverse proxies, configure hardware transcoding acceleration, and import existing photo libraries using the immich-cli.


1. Deconstructing the Immich Microservices Architecture

Immich is built using a modern microservices architecture. Instead of running as a single large application, it splits workloads across specialized containers to ensure fast photo loading, video transcoding, and machine learning performance.

Immich Core System Architecture:
                      [ Client Applications (Web UI / Mobile App) ]

                                            ▼ (Port 2283)
                                  [ Immich Server ]
                                  (API / Node.js)
                                     │   │   │
                  ┌──────────────────┘   │   └──────────────────┐
                  ▼                      ▼                      ▼
         [ PostgreSQL + pgvector ]  [ Redis Cache ]  [ Immich Machine Learning ]
            (Metadata & Vectors)    (Job Queuing)        (CLIP / Facial AI)

1. Immich Server (NestJS Engine)

The primary backend container runs a Node.js web application built on the NestJS framework. It acts as the centralized coordinator for the entire system:

  • API Gateway: Routes incoming HTTP requests from the Web UI and mobile clients.
  • Upload Pipeline: Handles multi-part file stream uploads. When you upload a photo or video, the server writes it to a temporary directory, extracts file system metadata, and pipes the raw bytes to your permanent storage path.
  • Static Asset Serving: Handles authorized delivery of media assets, converting raw HEIC/RAW photos into lightweight webp thumbnails on-the-fly when requested by web clients.

2. Immich Machine Learning (Python AI Service)

A Python service that runs modern deep learning models locally, offloading complex inference tasks from the main Node.js server:

  • Facial Recognition (InsightFace): Utilizes a ResNet-based Convolutional Neural Network (CNN) trained on the buffalo_l dataset. When a photo is uploaded, it crops located human faces, extracts a 512-dimensional floating-point vector (embedding) representing facial geometry, and passes it to the database for matching.
  • Semantic Search (CLIP): Stands for Contrastive Language-Image Pre-Training. Developed by OpenAI, it maps text descriptions and image pixels to a single shared vector space. When you query “cats in the snow,” CLIP converts your text into a text embedding vector, and Immich compares it to your photo embedding vectors, showing matching photos even if they lack textual metadata.

3. PostgreSQL & pgvector Vector Database

Stores all metadata (filenames, user profiles, albums, geographic coordinates). The standard database is extended with the pgvector plugin, transforming Postgres into a vector database:

  • Vector Indexing: Stores the 512-dimensional embeddings of faces and images.
  • Mathematical Matching: Runs cosine similarity searches (using the <=> operator) or L2 distance calculations directly inside SQL queries to find faces similar to a target image or matches for a text search. It uses Hierarchical Navigable Small World (HNSW) indexes to keep search speeds fast as your library grows.

4. Redis Cache & BullMQ Job Queue

Acts as the central message queue and broker:

  • Job Queues: Background tasks (generating thumbnails via the high-speed sharp library, extracting EXIF coordinates via exiftool, and transcoding videos via ffmpeg) are packaged as jobs and pushed to Redis.
  • Worker Coordination: Redis distributes these jobs across available CPU threads, preventing the NestJS server from blocking and keeping the user interface responsive during heavy bulk uploads.

2. System Prerequisites & Hardware Planning

Because Immich runs machine learning operations locally, it requires more resources than a simple file server:

  • CPU: Multi-core x86_64 or ARM64 processor.
  • RAM: Minimum 4 GB (8 GB is highly recommended if you have a large library, as face recognition models require significant memory).
  • Storage: Fast NVMe/SATA SSDs for the Immich database configuration and metadata cache, combined with high-capacity HDD storage for your actual media files.
  • Software: A clean installation of Docker and Docker Compose running on a stable Linux environment (like Ubuntu Server LTS).

3. Step-by-Step Installation & Docker Configuration

We will deploy Immich using Docker Compose. Create a dedicated directory on your server:

mkdir -p ~/immich-server
cd ~/immich-server

The Production Docker Compose File

Rather than downloading a pre-packaged script, creating your own docker-compose.yml file gives you complete control over your service paths, database passwords, and resource limits. Create the file:

nano docker-compose.yml

Paste the following configuration into the editor:

version: "3.8"

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

  immich-machine-learning:
    container_name: immich_machine_learning
    image: ghcr.io/immich-app/immich-machine-learning:release
    volumes:
      - model-cache:/cache
    env_file:
      - .env
    restart: always

  redis:
    container_name: immich_redis
    image: docker.io/redis:6.2-alpine
    restart: always

  database:
    container_name: immich_postgres
    image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
    environment:
      POSTGRES_DB: immich
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: always

volumes:
  pgdata:
  model-cache:

Configure the Environment Variables (.env)

The environment configuration file stores your credentials and path definitions. Create the .env file:

nano .env

Add the following environment variables to configure your Immich instance:

# Core Storage Path
UPLOAD_LOCATION=/home/suresh/immich-library

# Database Credentials
DB_PASSWORD=MySecurePassword99!
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
DB_HOSTNAME=database

# Caching Services
REDIS_HOSTNAME=redis

# Machine Learning Orchestration
IMMICH_MACHINE_LEARNING_URL=http://immich-machine-learning:3003
MACHINE_LEARNING_WORKERS=1

# Transcoding Performance Constraints
FFMPEG_THREADS=2

# Environment Target
NODE_ENV=production

Explaining the Environmental Parameters

  • UPLOAD_LOCATION: The host directory where all uploaded photos, transcoded videos, and cached thumbnails will be stored.
  • DB_PASSWORD / DB_USERNAME / DB_DATABASE_NAME: Cryptographic credentials used by the NestJS container to authenticate with the PostgreSQL database container.
  • DB_HOSTNAME & REDIS_HOSTNAME: The network alias names of the database and redis containers. These correspond to the service definitions in the docker-compose.yml file.
  • IMMICH_MACHINE_LEARNING_URL: The endpoint where the main NestJS API server sends images to the python container for facial embeddings and semantic search calculations.
  • MACHINE_LEARNING_WORKERS: Limits the number of CPU worker threads in the machine learning container. Setting this to 1 is highly recommended for lower-resource servers (like Raspberry Pi 4/5 or dual-core VPS nodes) to prevent out-of-memory (OOM) lockups.
  • FFMPEG_THREADS: Controls the maximum CPU threads allocated to ffmpeg video transcoding tasks. Limiting this prevents video processing tasks from saturating your server’s CPU during bulk uploads.

Make sure the directory you specified in UPLOAD_LOCATION exists and has the correct write permissions for the user (UID 1000 in our container config):

mkdir -p /home/suresh/immich-library
sudo chown -R 1000:1000 /home/suresh/immich-library

4. Configuring Hardware Transcoding Acceleration

When you upload high-definition video files (e.g., Apple HEVC files or 4K videos), the server needs to transcode them into web-friendly formats to ensure smooth playback in browsers and apps. You can configure hardware acceleration to offload this process from your CPU to a dedicated GPU.

Enabling Intel QuickSync (QSV)

If your server uses an Intel processor with integrated graphics (Intel HD/UHD Graphics), you can pass the GPU device directly to the main server container:

  1. Open docker-compose.yml.
  2. Add a devices block to the immich-server service configuration:
    immich-server:
      ...
      devices:
        - /dev/dri:/dev/dri

Enabling NVIDIA CUDA GPU Acceleration

If you have a dedicated NVIDIA graphics card installed:

  1. Verify the NVIDIA Container Toolkit is installed on your host OS.
  2. Configure the immich-machine-learning service in your docker-compose.yml to request GPU access:
    immich-machine-learning:
      ...
      deploy:
        resources:
          reservations:
            devices:
              - driver: nvidia
                count: 1
                capabilities: [gpu]

5. Launching the Server

Start the stack in detached background mode:

sudo docker compose up -d

Monitor the startup process and download logs to verify all containers initialize correctly:

sudo docker compose logs -f

6. Accessing the Dashboard & Admin Configuration

Once the logs show that the database migrations have completed, access the web dashboard:

  1. Open your web browser and navigate to: http://<YOUR_SERVER_IP>:2283
  2. Click Get Started.
  3. Register your Administrator Account by entering your name, email address, and a secure password.
  4. Log into your dashboard.
First Setup Checklist:
[ Create Admin Account ] ──► [ Define User Profiles ] ──► [ Enable ML Models ]

7. Configuring a Secure Reverse Proxy (Caddy)

To access your photos securely from the internet, you should configure a reverse proxy to route traffic through HTTPS. Using Caddy simplifies this by handling Let’s Encrypt SSL certificates automatically.

Secure Connection Routing:
[ Mobile Phone App ] ──► HTTPS (Port 443) ──► [ Caddy Server ] ──► HTTP (Port 2283) ──► [ Immich Server ]
  1. Install Caddy on your server.
  2. Point your domain name (e.g., photos.yourdomain.com) to your server’s public IP address.
  3. Edit your Caddyfile configuration:
    sudo nano /etc/caddy/Caddyfile
  4. Add the following reverse proxy block:
    photos.yourdomain.com {
        reverse_proxy localhost:2283
    }
  5. Restart Caddy to apply the changes and enable HTTPS:
    sudo systemctl restart caddy

8. Importing Existing Libraries via CLI

If you have a large library of existing photos stored on an external drive or NAS, you can import them into Immich without copying them manually. Use the official immich-cli tool to import your media:

1. Generate an API Key

  • Log into your Immich web dashboard.
  • Go to User Settings > API Keys.
  • Click Create API Key, copy the generated token, and keep it secure.

2. Run the Import CLI Command

You can execute the import tool globally by installing it via NPM, or run it directly on-the-fly using npx.

Option A: Run directly using npx

This is the recommended method as it ensures you are always using the CLI version that matches your server’s active release:

npx @immich/cli upload \
  --key "YOUR_API_KEY_HERE" \
  --server "http://192.168.1.100:2283/api" \
  --recursive \
  /mnt/data/my-photo-archive

Option B: Install the CLI globally

npm install -g @immich/cli

Verify the installation:

immich --help

Advanced CLI Import Options

The immich-cli tool provides several flags to customize how your files are ingested:

  • Dry-Run Validation (--dry-run): Performs a validation scan of the directory, counting matching photos and reporting potential errors without uploading any assets to the server:
    immich upload --key "YOUR_KEY" --server "http://IP:2283/api" --dry-run --recursive /path/to/photos
  • Upload Directly to a Named Album (--album): Automatically creates a new album (or uploads to an existing one) containing all the synchronized photos:
    immich upload --key "YOUR_KEY" --server "http://IP:2283/api" --album "Family Reunion 2025" --recursive /path/to/photos
  • Import Existing Directory Structure (--import): Attempts to preserve your local folder hierarchies as album arrangements inside Immich.
  • Skip Confirmation Prompts (--yes): Bypasses all prompt checks during execution, which is helpful when integrating the import command into automated shell scripts.

9. Backing Up Your Immich Infrastructure

A home server deployment is only as reliable as its backup strategy. If a disk drive fails, you risk losing both your photo media files and the database relationships (like face groupings and album tags). Set up a regular backup schedule:

1. Database Hot Backups

Because Immich relies on PostgreSQL, copying the raw data folders while the container is running can result in file corruption. Instead, run a hot backup using pg_dumpall:

# Dump the entire database to a compressed SQL file
docker exec -t immich_postgres pg_dumpall -c -U postgres | gzip > ~/immich-server/backups/database_backup_$(date +%F).sql.gz

2. Media Directory Backups

Backup the primary upload folder (UPLOAD_LOCATION) using rsync or an incremental backup utility (like BorgBackup or Restic) to a remote storage server or external backup drive:

# Run incremental sync backup
rsync -avz --delete /home/suresh/immich-library/ /mnt/backup-drive/immich-library/

3. Database Vector Index Optimization (Vacuuming & Reindexing)

As you upload, delete, and organize photos, the pgvector HNSW indexes inside your PostgreSQL database can become fragmented over time, degrading face-matching and semantic search performance. Periodically run a vacuum and analyze check to clean up dead rows:

# Run VACUUM and ANALYZE inside the container
docker exec -t immich_postgres psql -U postgres -d immich -c "VACUUM ANALYZE;"

For large media archives, force PostgreSQL to rebuild the vector indexes to reclaim storage space and optimize query execution paths:

# Rebuild the pgvector HNSW index
docker exec -t immich_postgres psql -U postgres -d immich -c "REINDEX DATABASE immich;"

4. Complete Backup Automation Script

Create a shell script named backup_immich.sh to automate this workflow:

#!/bin/bash
BACKUP_DIR="/home/suresh/immich-server/backups"
TIMESTAMP=$(date +%F)
mkdir -p "$BACKUP_DIR"

# 1. Backup the DB
docker exec -t immich_postgres pg_dumpall -c -U postgres | gzip > "$BACKUP_DIR/db_$TIMESTAMP.sql.gz"

# 2. Backup the Configs
cp /home/suresh/immich-server/.env "$BACKUP_DIR/config_$TIMESTAMP.env"
cp /home/suresh/immich-server/docker-compose.yml "$BACKUP_DIR/compose_$TIMESTAMP.yml"

# 3. Sync media files
rsync -a --delete /home/suresh/immich-library/ /mnt/backup-drive/immich-library/

# 4. Retain only the last 7 days of DB backups
find "$BACKUP_DIR" -type f -name "db_*.sql.gz" -mtime +7 -delete

Make the script executable and schedule it to run daily using cron:

chmod +x backup_immich.sh
# Add to crontab: 0 2 * * * /home/suresh/immich-server/backup_immich.sh

10. Troubleshooting & Maintenance

Use this matrix to identify and resolve common issues:

Issue / SymptomPrimary CauseTroubleshooting / Diagnostic Action
Containers killed with OOM errorLow memory during machine learning tasksEdit your .env file and limit worker threads: MACHINE_LEARNING_WORKERS=1. Ensure swap memory is configured.
Videos fail to play in browserTranscoding failed or is disabledCheck the ffmpeg container logs. Go to Administration > Job Settings and run the Video Transcoding queue.
Mobile backup halts in the backgroundOS battery optimization terminated processGo to Android battery settings for Immich, change optimization policy to “Unrestricted”, and enable background data.
Facial recognition is not grouping peopleML container still processing libraryCheck the status of face detection jobs in Administration > Jobs. Wait for the queue to clear.
Web interface shows “Server Offline”PostgreSQL database failed to startCheck database logs: docker logs immich_postgres. Ensure the disk volume has correct permissions.

11. Photo Management Alternatives

The table below compares Immich with other popular self-hosted photo galleries:

Feature / MetricImmichPhotoprismLychee
Language / StackNode.js / NestJSGo / TensorflowPHP / Laravel
Native Mobile AppYes (iOS & Android)No (Progressive Web App)No
Facial RecognitionYes (InsightFace)Yes (Tensorflow)No
Semantic SearchYes (CLIP)YesNo
Database BackendPostgreSQL + pgvectorMariaDB / SQLiteMySQL / PostgreSQL
Multi-User SharingYes (Full timeline sharing)LimitedBasic

12. Conclusion

Deploying Immich gives you a private, secure, and self-hosted photo management platform. By storing your photos locally and securing access via a Caddy reverse proxy, you maintain complete ownership of your media library and data privacy.

Ready to secure the rest of your self-hosted infrastructure? Read our step-by-step guides on configuring a secure Vaultwarden Password Manager or managing your private cloud storage with Nextcloud today!

Frequently Asked Questions (FAQ)

Q1: What is Immich? A: Immich is a high-performance, open-source photo and video management system designed to be self-hosted locally. It serves as a privacy-friendly alternative to commercial cloud platforms like Google Photos and Apple iCloud.

Q2: What are the hardware requirements to self-host Immich? A: Immich runs machine learning operations locally, so it requires a multi-core x86_64 or ARM64 processor, at least 4 GB of RAM (8 GB recommended for facial recognition), and fast SSD storage for its database alongside high-capacity storage for media.

Q3: Can Immich perform facial recognition and search by text? A: Yes, Immich features local machine learning capabilities. It uses models like InsightFace for accurate facial recognition and CLIP for semantic search, allowing you to search for images using natural language text descriptions.

Q4: How do I access Immich securely over the internet? A: To access your photos securely from outside your home network, it is recommended to set up a reverse proxy like Caddy. Caddy handles routing traffic through HTTPS and automatically manages Let’s Encrypt SSL certificates.

Q5: How can I bulk import an existing photo library into Immich? A: You can easily import massive photo archives using the official immich-cli tool. By generating an API key from the dashboard, you can run the CLI over your local directories to upload photos seamlessly, preserving structure and albums if desired.

Suresh S

Written by Suresh S

Systems Engineer & Tech Educator with 8+ years of experience in Linux Administration, Cloud Computing, and Cybersecurity. Founder of FreeTechLearner, dedicated to creating practical tutorials that help students and professionals build real-world skills.

Share this post:

Discussion

Loading comments...