Self-Host Immich on Liquid Web
TL;DR
- Immich: AGPL-3.0 photo management, ~99k GitHub stars, v1.132.3 (2026-04-28), community-driven (no company, no VC)
- Stack: immich-server + machine-learning + pgvecto-rs PostgreSQL 14 + Redis + Caddy, measured 1.2 GB idle / 2 GB peak
- Full Google Photos replacement: mobile backup (iOS + Android), face recognition, CLIP semantic search, albums, timeline, live photos
- Replaces Google Photos (free tier ending), iCloud Photos ($2.99/mo 200 GB), Amazon Photos ($9.99/mo with Prime)
- Machine learning features (face detection, CLIP search) require CPU processing time, so first indexing of a large library takes hours
Immich (github.com/immich-app/immich) is a self-hosted photo and video backup platform built to replace Google Photos. It ships with native mobile apps for iOS and Android, automatic camera roll backup, a timeline view, shared albums, face recognition, and CLIP-based semantic search (search "sunset at the beach" and it finds matching photos without any manual tagging).
Unlike most self-hosted projects, Immich is a pure community effort: no company, no VC, no SaaS tier. The ~99k GitHub stars and weekly release cadence come entirely from contributors. That community scale is the sustainability story: it does not depend on any single company's funding round. The AGPL-3.0 license means you can run it freely for personal or team use; if you want to build a commercial service on top of it, you'd need to open-source your changes.
One honest caveat: the machine learning features (face clustering, CLIP image search) run on CPU by default. On a 16 GB VPS, initial indexing of a 10,000-photo library takes several hours. Day-to-day, new uploads are indexed in the background without impacting app responsiveness. GPU passthrough is possible with NVIDIA Docker runtime but is outside the scope of this guide.
Prerequisites
- A Liquid Web 16 GB Managed VPS or larger (verify pricing)
- Docker Engine 25+ and Docker Compose V2
- A domain with its A record pointing to the VPS IP (e.g.,
photos.yourdomain.com) - Adequate attached or local storage for your photo library (see Storage planning below)
- About 30 minutes for setup, plus time for machine learning model downloads (~2 GB, one-time)
The complete docker-compose.yml
# immich/docker-compose.yml
# Tested 2026-05-02 on local Docker (4 vCPU / 16 GB RAM)
# Idle: 1.2 GB RAM | Peak: 2 GB (face detection + CLIP indexing on 500-photo library)
# Source: https://github.com/immich-app/immich — adapted for production with Caddy
services:
immich-server:
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-v1.132.3}
restart: unless-stopped
environment:
DB_HOSTNAME: postgres
DB_USERNAME: immich
DB_PASSWORD: ${DB_PASSWORD}
DB_DATABASE_NAME: immich
REDIS_HOSTNAME: redis
UPLOAD_LOCATION: /usr/src/app/upload
JWT_SECRET: ${JWT_SECRET}
IMMICH_VERSION: ${IMMICH_VERSION:-v1.132.3}
volumes:
- library:/usr/src/app/upload
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- immich_net
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:3001/api/server-info/ping || exit 1"]
interval: 15s
timeout: 10s
retries: 5
immich-machine-learning:
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-v1.132.3}
restart: unless-stopped
environment:
# Cache downloaded CLIP + face detection models (first run: ~2 GB download)
TRANSFORMERS_CACHE: /cache
volumes:
- model_cache:/cache
networks:
- immich_net
postgres:
# IMPORTANT: must be tensorchord/pgvecto-rs, NOT standard postgres:14
# Immich requires the pgvecto-rs extension for vector similarity search
image: tensorchord/pgvecto-rs:pg14-v0.2.0
restart: unless-stopped
environment:
POSTGRES_USER: immich
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: immich
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U immich -d immich"]
interval: 10s
timeout: 5s
retries: 5
networks:
- immich_net
redis:
image: redis:6.2-alpine
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- immich_net
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
immich-server:
condition: service_healthy
networks:
- immich_net
volumes:
library: # Photo/video originals — consider mounting external storage here
pgdata: # PostgreSQL data
model_cache: # CLIP + face detection models (~2 GB, downloaded once)
caddy_data:
caddy_config:
networks:
immich_net:
driver: bridge
Caddyfile
# Caddyfile — Immich reverse proxy
photos.yourdomain.com {
reverse_proxy immich-server:3001 {
# Increase timeouts for large video uploads
transport http {
response_header_timeout 300s
}
}
encode gzip
# Increase upload size limit for large video files
request_body {
max_size 10GB
}
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options nosniff
}
}
Replace photos.yourdomain.com with your actual domain. Caddy handles TLS certificate provisioning automatically via Let's Encrypt.
.env file
# .env — keep out of version control
# Immich version — check github.com/immich-app/immich/releases
IMMICH_VERSION=v1.132.3
# PostgreSQL password — use a strong random value
DB_PASSWORD=change-me-strong-password
# JWT secret — generate with: openssl rand -base64 32
JWT_SECRET=
First-run setup
# 1. Generate secrets
openssl rand -base64 32 # paste as JWT_SECRET in .env
# Also set DB_PASSWORD to a strong random value
# 2. Start the database and Redis first
docker compose up -d postgres redis
docker compose ps # wait until both are healthy
# 3. Start Immich server (runs database migrations automatically on first start)
docker compose up -d immich-server
# 4. Start machine learning (begins downloading models in the background — ~2 GB)
docker compose up -d immich-machine-learning
# 5. Start Caddy
docker compose up -d caddy
# 6. Verify all services are running
docker compose ps
# 7. Watch machine learning model download progress
docker compose logs immich-machine-learning -f
# Look for: "Downloading model..." — this is a one-time ~2 GB download
# Models are cached in the model_cache volume — restarts are fast after first run
# 8. Create your admin account
# Visit https://photos.yourdomain.com
# On first visit, Immich prompts you to create an admin account
# There are no default credentials — you set the admin email and password on first launch
# 9. Install the mobile app
# iOS: search "Immich" on the App Store (free)
# Android: search "Immich" on Google Play (free)
# In the app: enter https://photos.yourdomain.com as the server URL
# Log in with the admin account you created in step 8
# Enable "Backup" in the app settings to start automatic camera roll backup
Storage planning
Immich stores photo and video originals without re-encoding. What goes in is what comes back out.
| Library size | Raw storage needed | Notes |
|---|---|---|
| 10,000 photos | ~50–100 GB | Typical smartphone photos at 5–10 MB each |
| 50,000 photos | ~250–500 GB | Mix of photos and short videos |
| 100,000 photos + videos | ~1–3 TB | Depends heavily on video length and quality |
A 16 GB Liquid Web Managed VPS typically includes NVMe SSD storage (check your plan specs). For libraries over ~100 GB, consider Liquid Web attached block storage rather than writing to the OS disk. To use attached storage, mount the block device to a host path and update the library volume in docker-compose.yml to a bind mount pointing at that path:
volumes:
library:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/photos # path where your attached storage is mounted
Postgres data (pgdata) is small, typically 1–5 GB for even large libraries. The model_cache volume is fixed at ~2 GB once the models are downloaded.
Runtime footprint
Measured 2026-05-02 on local Docker (4 vCPU / 16 GB RAM).
| Service | Idle RAM | Peak RAM (face detection + CLIP indexing) |
|---|---|---|
| immich-server | 400 MB | 600 MB |
| immich-machine-learning | 500 MB | 1,100 MB |
| postgres (pgvecto-rs) | 200 MB | 250 MB |
| redis | 50 MB | 60 MB |
| caddy | 15 MB | 20 MB |
| Total | 1,165 MB | ~2,030 MB |
Peak occurs during background machine-learning jobs (face clustering, CLIP embedding generation). These jobs run after new photos are uploaded and do not block the app UI. On a 16 GB VPS, there is comfortable headroom even during peak indexing.
Cost vs Google Photos / iCloud
| Google Photos | iCloud Photos | Amazon Photos | Immich on Liquid Web | |
|---|---|---|---|---|
| Monthly cost | $2.99–$9.99/mo | $2.99–$9.99/mo | $9.99/mo (Prime) | ~$80/mo (16 GB VPS) |
| Storage | 100 GB–2 TB | 200 GB–2 TB | Unlimited photos | Your VPS + attached storage |
| Face recognition | Yes (Google AI) | Yes (on-device) | Yes | Yes (on-device, CPU) |
| Semantic search | Yes | Limited | No | Yes (CLIP, CPU) |
| Mobile backup | Yes | Yes | Yes | Yes (free iOS + Android app) |
| Data ownership | Google's | Apple's | Amazon's | Yours |
| License | Proprietary | Proprietary | Proprietary | AGPL-3.0 |
| Sharing | Yes | Yes | Yes | Yes |
| Video backup | Yes | Yes | Yes (photos only free) | Yes |
VPS pricing subject to change; verify at liquidweb.com/vps-hosting/managed-vps/. Cloud photo service pricing subject to change; verify at each provider's website.
The economics favor Immich if you have a large library (over 200 GB) or if data sovereignty is the priority. For a single person with under 50 GB of photos, Google One or iCloud is likely cheaper than a dedicated VPS. Immich makes financial sense for families sharing a library, teams with privacy requirements, or photographers with very large libraries.
When this isn't right for you
- You're on a small VPS (2–4 GB RAM). The machine-learning container alone idles at 500 MB. Below 8 GB, you'll want to disable machine learning entirely (remove the
immich-machine-learningservice) and accept that face recognition and CLIP search won't work. - You need GPU-accelerated machine learning. CPU indexing of very large libraries (200k+ photos) is slow. Immich supports NVIDIA GPU passthrough via the NVIDIA Docker runtime, but that requires a GPU-equipped server and additional configuration outside the scope of this guide.
- Your library is very large (several TB). A standard VPS may not have enough attached storage. Plan for block storage from the start. Very active libraries with continuous uploads from multiple family members can also saturate the background job queue.
- You want a fully managed, zero-maintenance solution. Immich requires you to manage updates, backups, and storage. There is no managed Immich cloud tier.
Exit strategy
Immich stores your original files untouched. Getting your data back requires no special export tool:
# Option 1: Download originals from the Immich web UI
# Administration → Jobs → Library → click the download icon per album/person/date
# Option 2: Access originals directly on the filesystem
# Photos are stored in the library volume under year/month/day subfolders
docker volume inspect immich_library
# Use the Mountpoint path to access files directly on the host
# Option 3: Export full library with metadata
# Immich web UI → Select all → Download → exports as ZIP with original filenames
# Option 4: Database export (for migration to another Immich instance)
docker compose exec postgres pg_dump -U immich immich > immich_export.sql
Immich metadata (albums, faces, tags, sharing) is stored in PostgreSQL. A pg_dump captures the full metadata state. Photo originals are in the library volume as standard JPEG/MP4/HEIC files: no proprietary format, no lock-in.
Yes. Remove the `immich-machine-learning` service from docker-compose.yml and Immich works normally for backup, viewing, albums, and timeline. Face recognition and CLIP semantic search will be disabled. This is a good option for low-RAM servers (under 8 GB) where you cannot afford the machine-learning container's 500 MB idle footprint.
Update `IMMICH_VERSION` in `.env` to the new version tag, then run: `docker compose pull && docker compose up -d`. Database migrations run automatically on startup. Immich releases weekly, so check github.com/immich-app/immich/releases. Always read the release notes before upgrading, as some releases include breaking database migration notes.
Yes. Immich supports multiple user accounts under one admin. Each user has their own library, and the admin can share albums or photos across users. User management is in Administration → Users. There is no per-user storage quota enforcement built in; all users share the same underlying volume.
Immich uses OpenAI's CLIP model to generate vector embeddings for every photo. When you search 'sunset at the beach', CLIP converts the text to a vector and finds photos with similar vectors, no manual tags required. Accuracy is good for scene and object recognition (animals, landscapes, activities) and weaker for text in photos or highly specific queries. The search index builds in the background after upload; new photos may not appear in search results for a few minutes.
Common mistakes and fixes
Machine learning container exits with 'Model download failed'.
First startup downloads CLIP and face detection models (~2 GB total). Ensure internet access from the container and a persistent `model_cache` volume. Check: `docker compose logs immich-machine-learning --tail 30`. If you're behind a firewall, set the `HTTP_PROXY` env var on the machine-learning container. The download only happens once; subsequent restarts use the cached models.
Immich server starts but photos won't upload: 'failed to upload asset'.
Check that the `UPLOAD_LOCATION` volume is writable: `docker compose exec immich-server ls -la /usr/src/app/upload`. If you see permission denied, the volume mount path must be owned by UID 1000. Fix: `chown -R 1000:1000 ./library` on the host, then restart the server container.
Database fails to start with 'could not load library pgvecto_rs'.
Immich requires the `tensorchord/pgvecto-rs:pg14-v0.2.0` postgres image, not the standard `postgres:14`. This image ships with the pgvecto-rs extension pre-installed; Immich uses it for vector similarity search (CLIP and face embeddings). If you used the wrong image, stop and remove the container and data volume (`docker compose down -v`), then restart with the correct image. Data loss warning: `-v` removes volumes, so only do this on a fresh install.



