Skip to content

Phase 8 — Immich Photo Server

Goal: Set up Immich as a self-hosted Google Photos replacement — giving you and Ahana a beautiful, searchable photo library with face recognition, object search, map view, and sharing, all running on your own infrastructure.

Time estimate: 2–3 hours
What you need: Mini PC running Windows with Docker Desktop, UNAS2 accessible at 192.168.1.2, Phase 5 complete (existing Docker stack running)
Prerequisites: Phase 5 complete, Photos_Bipin and Photos_Ahana populated on UNAS2


Why Immich Instead of Jellyfin for Photos

Jellyfin is a media server built for movies and music — it handles photos but the experience is basic. Immich is built exclusively for photos and videos. The difference in day-to-day use is significant:

Feature Jellyfin Immich
Face recognition ❌ No ✅ Yes — automatic
Object/scene search ❌ No ✅ Yes — "show me beach photos"
Map view ❌ No ✅ Yes — photos on a world map
Mobile app (iOS + Android) ✅ Basic ✅ Excellent — Google Photos feel
Sharing albums ❌ Limited ✅ Full share links, expiry, password
Memory view (On This Day) ❌ No ✅ Yes
Duplicate detection ❌ No ✅ Yes
Folder-based albums ❌ No ✅ Via folder album creator

Architecture For Your Setup

UNAS2 (storage)
├── Photos_Bipin/     ← mounted read-only into Immich
│   ├── Camera/       ← FolderSync daily backup (unchanged)
│   ├── Photos from 2021/
│   └── ...
└── Photos_Ahana/     ← mounted read-only into Immich
    └── Camera/       ← PhotoSync backup (unchanged)

Mini PC (compute)
└── Immich Docker stack
    ├── immich-server (port 2283)
    ├── immich-machine-learning
    ├── PostgreSQL (local SSD only — not on NAS)
    ├── Redis / Valkey
    └── immich-folder-album-creator

Phone backup stays exactly as configured in Phase 3: FolderSync (Android) and PhotoSync (iPhone) continue syncing to the NAS as before. Immich scans those folders nightly as External Libraries and picks up new photos automatically. No change to your existing backup flow — Immich is a viewer and organiser on top of what you already have.

Why keep FolderSync/PhotoSync instead of the Immich mobile app? Immich's mobile app uploads to an internal folder that cannot be redirected to your existing NAS folder structure. Keeping FolderSync/PhotoSync means new photos land in Photos_Bipin/Camera and Photos_Ahana/Camera — the same place your historical photos are — keeping everything in one consistent structure that Immich then indexes.

PostgreSQL cannot run on a network share. The Immich database must live on the mini PC's local SSD. Only photos and uploads live on the NAS.


Step 1 — Create Immich Folder Structure on Mini PC

mkdir C:\docker\immich
mkdir C:\docker\immich\library
mkdir C:\docker\immich\postgres
mkdir C:\docker\immich\model-cache

Step 2 — Create the Environment File

Create C:\docker\immich\.env with the following content:

# Upload location — where Immich stores new photos uploaded
# directly via web or mobile app
UPLOAD_LOCATION=C:\docker\immich\library

# Database location — MUST be on local SSD, not network share
DB_DATA_LOCATION=C:\docker\immich\postgres

# Immich version — pin to v2 for stability
IMMICH_VERSION=v2

# Database credentials — change DB_PASSWORD to something secure
DB_PASSWORD=changethis_to_secure_password
DB_USERNAME=immich
DB_DATABASE_NAME=immich

# Timezone
TZ=Asia/Kolkata

Change DB_PASSWORD to a strong random password using only letters and numbers (A-Za-z0-9) — avoid special characters as Docker may misparse them. Save it in your password manager.


Step 3 — Create the Docker Compose File

Create C:\docker\immich\docker-compose.yml:

name: immich

services:

  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    restart: always
    ports:
      - "2283:2283"
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
      - "P:/:/mnt/photos-bipin:ro"
      - "Q:/:/mnt/photos-ahana:ro"
    env_file:
      - .env
    depends_on:
      - redis
      - database
    healthcheck:
      disable: false

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

  redis:
    container_name: immich_redis
    image: docker.io/valkey/valkey:8-bookworm
    restart: always
    healthcheck:
      test: redis-cli ping || exit 1

  database:
    container_name: immich_postgres
    image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0
    restart: always
    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

  immich-folder-album-creator:
    container_name: immich_folder_album_creator
    image: salvoxia/immich-folder-album-creator:latest
    restart: unless-stopped
    environment:
      API_URL: http://immich_server:2283/api
      API_KEY: REPLACE_WITH_API_KEY_AFTER_SETUP
      ROOT_PATH: /mnt/photos-bipin,/mnt/photos-ahana
      CRON_EXPRESSION: "0 2 * * *"
      RUN_IMMEDIATELY: "yes"
      ALBUM_LEVELS: 1
      SYNC_MODE: "2"
      TZ: Asia/Kolkata

Note on immich-folder-album-creator: The API_KEY field must be filled in after Immich is set up — you generate the key in Step 7. Leave it as the placeholder for now and update it after setup. The container will fail to start until a valid key is provided — this is expected.

Note on network drive paths: The volumes "P:/:/mnt/photos-bipin:ro" and "Q:/:/mnt/photos-ahana:ro" mount your mapped network drives (Photos_Bipin and Photos_Ahana) as read-only into the Immich container. Confirm P:\ and Q:\ are mapped before starting.


Step 4 — Start Immich

cd C:\docker\immich
docker compose up -d

This pulls four images — takes 5–10 minutes on the first run. Verify all containers are running:

docker compose ps

You should see these all as Up (the folder album creator may show as restarting until you add the API key — that's fine): - immich_server - immich_machine_learning - immich_redis - immich_postgres


Step 5 — Initial Immich Setup

  1. Open http://localhost:2283 in your browser
  2. Click Getting Started
  3. Create the admin account:
  4. Name: Bipin
  5. Email: your email
  6. Password: strong password — save in password manager
  7. Complete the setup wizard
  8. Skip the mobile app step for now

Step 6 — Create Ahana's Account

  1. Go to Administration → Users → Create User
  2. Fill in:
  3. Name: Ahana
  4. Email: Ahana's email
  5. Password: set a password — she can change it after first login
  6. Save

Step 7 — Generate API Key for Folder Album Creator

  1. Click your avatar (top right) → Account Settings
  2. Go to API Keys → New API Key
  3. Name: folder-album-creator
  4. Click Create
  5. Copy the generated key

Now update the docker-compose.yml — replace REPLACE_WITH_API_KEY_AFTER_SETUP with the actual key:

API_KEY: your_actual_api_key_here

Restart the folder album creator container:

cd C:\docker\immich
docker compose up -d immich-folder-album-creator

Step 8 — Configure External Libraries

This is the step that makes your existing NAS photos appear in Immich without moving or duplicating them.

Allow external library paths (admin setting): 1. Go to Administration → Settings → Library 2. Under External Path, add both allowed paths: - /mnt/photos-bipin - /mnt/photos-ahana 3. Save

Create external library for Bipin: 1. Go to Administration → External Libraries 2. Click Create Library → select owner: Bipin 3. Click Add Folder → enter path: /mnt/photos-bipin 4. Click Add 5. Click the ... menu → Scan New Library Files 6. Rename the library to Bipin's Photos

Create external library for Ahana: 1. Click Create Library → select owner: Ahana 2. Click Add Folder → enter path: /mnt/photos-ahana 3. Click Add 4. Click ... menu → Scan New Library Files 5. Rename the library to Ahana's Photos

The initial scan will take time depending on how many photos you have. You can monitor progress under Administration → Jobs → Library.

Important: External library assets belong to the owner user. Bipin sees his photos, Ahana sees hers. Neither can browse the other's library by default — this matches your existing privacy setup.


Step 9 — Set Up Automatic Nightly Library Scan

Configure Immich to automatically pick up new photos from FolderSync and PhotoSync each night:

  1. Go to Administration → External Libraries
  2. Click ... on Bipin's Photos → Edit
  3. Under Scan Schedule, enter: 0 3 * * * (3am nightly)
  4. Save
  5. Repeat for Ahana's Photos

The folder album creator also runs at 2am (Step 3 config) — this sequence means: photos arrive from FolderSync/PhotoSync during the day → Immich scans at 3am → folder album creator runs next night at 2am to create/update albums → photos appear in albums within 24 hours of being taken.


Step 10 — Configure Machine Learning

Immich's machine learning features (face recognition, smart search) run automatically in the background. Enable and tune them:

  1. Go to Administration → Settings → Machine Learning
  2. Confirm Smart Search is enabled
  3. Confirm Facial Recognition is enabled
  4. Go to Administration → Jobs
  5. Run Smart Search job — this indexes all photos for semantic search (takes hours for a large library — let it run overnight)
  6. Run Face Detection job — detects and groups faces (also runs in background)

Once complete you can search using natural language: - "beach photos" - "photos with children" - "outdoor photos 2023"


Step 11 — Add Cloudflare Tunnel Hostname

Add Immich to your Cloudflare Tunnel so you can access it remotely:

  1. Go to Cloudflare → Networking → Tunnels → ahanabipin-home
  2. Click EditPublic HostnamesAdd a hostname
  3. Fill in:
  4. Subdomain: photos
  5. Domain: ahanabipin.in
  6. Service Type: HTTP
  7. URL: localhost:2283
  8. Save

Immich is now accessible at:

https://photos.ahanabipin.in

Update the folder album creator API_URL in docker-compose.yml to use the public URL if you ever need external API access — but for internal Docker communication, keep it as http://immich_server:2283/api.


Step 12 — Install Immich Mobile App (Optional)

The Immich mobile app gives you a Google Photos-like experience for browsing. Since you're keeping FolderSync/PhotoSync for backup, use the app in browse-only mode — disable auto-backup in the app settings to avoid duplicate uploads.

Android (Bipin): 1. Install Immich from Play Store 2. Open → Server URL: https://photos.ahanabipin.in 3. Log in with your Immich credentials 4. Go to app Settings → Backup → disable auto backup

iPhone (Ahana): 1. Install Immich from App Store 2. Open → Server URL: https://photos.ahanabipin.in 3. Log in with Ahana's credentials 4. Go to app Settings → Backup → disable auto backup


Step 13 — Update Phase 7 Backup Script

Immich's PostgreSQL database lives on the mini PC at C:\docker\immich\postgres. This is not in the existing robocopy path (C:\docker\mediastack\config). Update the backup script to include it.

Open C:\rclone\backup-nas.bat and add this line after the existing robocopy line:

echo [Immich] Copying Immich database and config to NAS... >> C:\rclone\backup.log
robocopy "C:\docker\immich" "\\192.168.1.2\Backups\immich-config" /MIR /XD "library" /R:2 /W:5 /LOG+:C:\rclone\backup.log

The /XD "library" flag excludes the upload library folder from the backup — those photos are already on the NAS and backed up to B2 via rclone. Only the database and config are copied here.


Service Reference

Service Local URL External URL
Immich http://localhost:2283 https://photos.ahanabipin.in

Verification Checklist

  • [ ] Immich folder structure created on mini PC
  • [ ] .env file created with secure DB_PASSWORD
  • [ ] docker-compose.yml created with correct NAS paths
  • [ ] All 4 core containers running (server, ML, redis, postgres)
  • [ ] Admin account created for Bipin
  • [ ] Ahana's account created
  • [ ] API key generated and added to folder album creator
  • [ ] External library paths allowed in admin settings
  • [ ] Bipin's external library created → /mnt/photos-bipin
  • [ ] Ahana's external library created → /mnt/photos-ahana
  • [ ] Initial library scan completed
  • [ ] Nightly scan schedule set for both libraries (3am)
  • [ ] Machine learning jobs running (smart search, face detection)
  • [ ] photos.ahanabipin.in loading Immich via Cloudflare Tunnel
  • [ ] Both phones can browse photos via Immich app
  • [ ] Phase 7 backup script updated to include Immich database
  • [ ] Immich folder album creator running and creating albums

Troubleshooting

External library scan finds 0 photos: Confirm the network drives P:\ and Q:\ are mapped and accessible before Docker starts. Run docker compose restart immich_server after confirming drives are connected. Check the path in the external library settings exactly matches the container mount path (/mnt/photos-bipin not the Windows path).

Folder album creator failing to start: The API key placeholder must be replaced with a real key. Generate it in Immich → Account Settings → API Keys, then update docker-compose.yml and run docker compose up -d.

Machine learning very slow: This is normal on first run — it processes every photo once. Leave it running overnight. Subsequent scans only process new photos and are much faster. If you have an Intel CPU with QuickSync or an NVIDIA GPU, hardware acceleration can be enabled in the docker-compose.yml — see Immich docs for hardware transcoding configuration.

Photos appear with wrong date: Google Takeout JSON sidecar files contain the original date. Immich can read these — go to Administration → Jobs → Extract Metadata and run it. This corrects dates from the JSON files.

Cannot access photos.ahanabipin.in: Confirm the Cloudflare Tunnel hostname is configured correctly (localhost:2283) and the Tunnel is showing Healthy. Confirm the immich_server container is running on port 2283.

Immich not picking up new photos from FolderSync/PhotoSync: New photos land in the NAS folders overnight, then Immich scans at 3am. Check the scan schedule is set correctly in Administration → External Libraries. You can also trigger a manual scan any time from the same screen.

PostgreSQL container failing to start: Confirm DB_DATA_LOCATION points to a local SSD path, not a network share. Network shares are not supported for the Postgres data directory.


Future Considerations

Sharing photos with family: Immich supports creating shared albums with external links — you can share an album of holiday photos with family members who don't have Immich accounts. Go to any album → ShareCreate Link → set expiry if desired.

Merging faces: After face detection runs, go to Explore → People to see detected faces. Merge duplicate detections and name the people — Immich will then group all photos of that person together.

Vaultwarden addition: When you decide to add Vaultwarden as a password manager, it runs as a single Docker container on a different port (8765) and can be added to this stack or the mediastack compose file with a new Cloudflare Tunnel hostname vault.ahanabipin.in.


Phase 8 complete. Return to the overview for the full system summary.