Immich is a Google Photos replacement: automatic phone backup, a timeline, albums, shared libraries, map view, and on-device object and face recognition. It is the single most popular self-hosted media app going, and the most likely to eat more disk than you planned for.
What it is
Your photos land on your disk, get indexed, and become searchable. It has real mobile apps that back up in the background, plus a web UI for the desktop library. A self-hosted ML container does recognition locally, so no photo ever leaves the house.
Before you start
- Budget disk properly. Expect the original files plus thumbnails, a video-encoded proxy, and encoded stills. A 100 GB phone library can land anywhere between 150 and 300 GB.
- The mobile app needs a reachable URL. If your server is only on a LAN, put it behind Tailscale, Cloudflare Tunnel, or a real domain.
- Build the library root and the database on different physical disks. Putting both in the same
./on one cheap SSD is the classic way to lose a decade of photos to one failure.
1 — Create the directories and the .env
mkdir -p ~/services/immich/{db,upload,data,cache}
cd ~/services/immich
cat > .env <<'EOF'
DB_USERNAME=immich
DB_PASSWORD=CHANGE_ME
DB_DATABASE_NAME=immich
DB_DATA_LOCATION=./db
UPLOAD_LOCATION=./upload
IMMICH_MEDIA_LOCATION=./data
ML_CACHE_LOCATION=./cache
EOF
2 — Write the compose file
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-latest}
restart: always
ports:
- "2283:2283"
environment:
DB_HOSTNAME: immich_postgres
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
DB_DATABASE_NAME: ${DB_DATABASE_NAME}
REDIS_HOSTNAME: immich_redis
IMMICH_MEDIA_LOCATION: ${IMMICH_MEDIA_LOCATION:-./data}
volumes:
- ${DB_DATA_LOCATION:-./db}:/var/lib/postgresql/data
- ${UPLOAD_LOCATION:-./upload}:/usr/src/app/upload
- ${IMMICH_MEDIA_LOCATION:-./data}:/usr/src/app/exthost
depends_on:
- immich_postgres
- immich_redis
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:2283/api/server/ping"]
interval: 1m
timeout: 5s
retries: 3
start_period: 5m
immich-machine-learning:
container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-latest}
restart: always
volumes:
- ${ML_CACHE_LOCATION:-./cache}:/cache
environment:
OMP_NUM_THREADS: 1
immich_postgres:
container_name: immich_postgres
image: ghcr.io/immich-app/postgres:${IMMICH_VERSION:-latest}
restart: always
volumes:
- ${DB_DATA_LOCATION:-./db}:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_DATABASE_NAME}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE_NAME}"]
interval: 10s
timeout: 5s
retries: 5
immich_redis:
container_name: immich_redis
image: valkey/valkey:8-bookworm
restart: always
command: redis-server --save "" --appendonly no
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
3 — Start it
docker compose up -d
docker compose logs -f immich-server | grep -i "listening\|error"
4 — First-run setup
- Open
http://yourhost:2283, register the first account. The first registered account becomes the admin — there is no separate admin flag, so register that account first. - In Administration → Settings → Storage, set the library location and enable "External libraries" if you want to point at a NAS or an existing photo folder.
- Install the mobile app, then set Settings → Backup → Background backup to "All photos, including videos" and choose Wi-Fi-only if you're bandwidth-constrained.
- Run the first full job queue from Administration → Jobs and watch the throughput. It'll take a while on a cold library.
5 — Back it up properly
docker exec -u postgres immich_postgres pg_dump -U immich immich > immich.sql
tar -czf immich-upload.tgz ./upload
docker exec immich_postgres pg_dumpall -U postgres > globals.sql