Home Lab Part 2: Immich — Self-Hosted Google Photos

April 2, 2026 — #home-lab #immich #docker #self-hosting #photos

With the foundation in place — Proxmox, Cloudflare Tunnel, Traefik — it’s time to run the first real service. Immich is a self-hosted alternative to Google Photos with ML-powered search, face recognition, and a mobile app that feels surprisingly close to the real thing.

Why Immich

I wanted to stop paying for cloud photo storage and stop depending on Google or Amazon to host my personal photos. Immich checks all the boxes: automatic mobile backup, face recognition, location-based search, and a clean web UI.

Architecture

Immich runs as four containers:

ContainerPurpose
immich_serverMain API + web UI (port 2283)
immich_machine_learningCLIP and face recognition models
immich_postgresPostgreSQL with pgvecto.rs for vector search
immich_redisRedis (via Valkey) for job queuing

Two Docker networks keep things clean:

  • proxy — Traefik-accessible (only the server container needs this)
  • internal — inter-service communication, isolated from outside

Storage

Photos land on the ZFS HDD mirror for bulk storage, while the database sits on the SSD pool for fast queries:

/mnt/immich/              (HDD ZFS mirror, mounted from host)
├── library/              ← Immich-managed photos
│   └── YYYY/MM/file.jpg  ← Storage template: {{y}}/{{MM}}/{{filename}}
├── upload/               ← Raw upload staging
├── thumbs/               ← Regeneratable
├── encoded-video/        ← Regeneratable
└── backups/              ← Daily automated postgres dumps

/opt/services/immich/postgres  (SSD, fast I/O)

The storage template {{y}}/{{MM}}/{{filename}} organizes photos by year and month on disk — useful if you ever need to browse the raw files.

Docker Compose

The key parts of the compose file:

services:
  immich_server:
    image: ghcr.io/immich-app/immich-server:release
    volumes:
      - /mnt/immich:/usr/src/app/upload
    networks:
      - proxy
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.immich.rule=Host(`immich.johannes-kling.de`)"
      - "traefik.http.services.immich.loadbalancer.server.port=2283"

  immich_machine_learning:
    image: ghcr.io/immich-app/immich-machine-learning:release
    volumes:
      - model-cache:/cache
    networks:
      - proxy    # needs internet on first run for model download
      - internal

  immich_postgres:
    image: tensorchord/pgvecto-rs:pg16-v0.2.0
    volumes:
      - ./postgres:/var/lib/postgresql/data
    networks:
      - internal

  immich_redis:
    image: valkey/valkey
    networks:
      - internal

Gotchas

Never touch library/ manually. Immich manages all file placement. Upload only via the mobile app, web UI, or Immich CLI. If you move files around, Immich loses track of them.

ML container needs internet on first run. It downloads CLIP models (~700 MB) from HuggingFace. If it’s only on the internal network, the download fails silently. Put it on proxy (which has internet access via Traefik/Cloudflare) at least for the initial startup.

The .immich marker file. Immich creates a .immich file in the library directory. If it’s missing, Immich refuses to start. Don’t delete it.

Cloudflare blocks uploads >100 MB. The free Cloudflare plan limits upload size. For bulk uploads or large videos, bypass the tunnel and use the LAN address directly: http://192.168.178.10:2283.

Backup

The database gets daily automated dumps into /mnt/immich/backups/. For a manual backup:

docker exec immich_postgres pg_dumpall -U immich \
  | gzip > /mnt/immich/backups/manual-$(date +%F).sql.gz

Since photos and database are both on ZFS, a snapshot gives you an atomic, consistent backup of everything.

Critical files to back up off-machine:

  • /opt/services/immich/.env (contains DB password)
  • Postgres dumps from /mnt/immich/backups/

Result

Immich is live at immich.johannes-kling.de. The mobile app syncs photos automatically, face recognition works well, and search by content (“beach”, “dog”) is surprisingly accurate thanks to CLIP. All running on two old SAS drives from eBay.

Next up: OpenCloud — replacing Google Drive and Nextcloud.