Immich gives you the Google Photos experience on a server you own. The iOS and Android apps back up photos and videos in the background, and the web and mobile apps offer a scrubbable timeline, albums and shared albums, partner sharing, a world map, "x years ago" memories, RAW and Live Photo support, and multi-user accounts. Machine learning runs on your own hardware: faces are detected and clustered, and CLIP-based search finds "dog on a beach" without any tags.
It is the best-known self-hosted photo app, with about 115,000 stars, an active team that has worked full time on it with backing from FUTO, and frequent releases (3.2.4 at the time of writing). It is AGPL-3.0 and runs as a set of Docker containers: the server, a machine-learning service, PostgreSQL and Valkey. A public demo lets you try it before installing.
- Repository: github.com/immich-app/immich
- Licence: AGPL-3.0 (GNU Affero General Public License v3.0)
- Language: TypeScript. Stars: 115.5K. Forks: 7,117. Last push: Oct 3, 2026.
- Scan: safe, Oct 2, 2026, commit c5e06dc
Who it is for
Families and individuals with a home server or NAS who want to stop paying for cloud photo storage, and anyone uneasy about a company scanning their photo library.
Getting started
1. Create a folder for the install
mkdir ./immich-app && cd ./immich-app2. Download the Compose file and environment file from the latest release
wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml && wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env3. Set UPLOAD_LOCATION and DB_PASSWORD in .env, then start it
docker compose up -d4. Open the web app on port 2283, create the admin account, then install the mobile app
open http://localhost:2283Use Docker's official packages; the README notes older distro builds such as Ubuntu 22.04's docker.io fail with this Compose file. Immich is not a backup by itself: the project asks you to keep a 3-2-1 backup of your library and database.
Safety scan
We cloned immich-app/immich at commit c5e06dc on Oct 2, 2026 and ran the checks described on the GitHub Tools page: credential patterns, decode-and-execute code, install-time scripts, committed binaries, risky CI workflows, every host the code talks to, known vulnerabilities in pinned dependencies, and project hygiene. A person read every hit. This is what we found.
- One secret candidate in 3,463 files and about 474,000 lines: packages/e2e-auth-server/test-keys.ts, the signing key for the mock OAuth server used in end-to-end tests. No pattern hits, no committed binaries and no bare-IP URLs.
- install.sh (108 lines) is the optional quick installer: it downloads the release's docker-compose.yml and example.env from GitHub and starts the containers, without sudo or shell-profile changes. The only lifecycle scripts are a CLI prepack build and SvelteKit's prepare step.
- 138 known advisories across 2,867 packages (3 critical, 66 high). The pnpm workspace of 2,745 packages covers the server, web app, CLI, docs and tests; its criticals are a node-tar denial of service. The machine-learning service's uv.lock (122 packages) carries anyio, starlette, urllib3 and protobuf advisories, which matter most if you expose that container directly instead of through the server.
- 27 workflows. Five use pull_request_target for labelling, backports and docs cleanup, and none checks out the pull request; 115 of 117 third-party actions are pinned to commits. Renovate, CodeQL, licence and contributing guide present; no SECURITY.md in the repository.
What the scanner counted
| Check | Result |
|---|---|
| Secrets | 1 candidate found and read; see the notes above. |
| Suspicious code | None found. |
| Install-time code | 2 npm lifecycle scripts. 2 installer scripts |
| Committed binaries | None. |
| CI workflows | 27 workflows. 5 use pull_request_target, none check out the pull request head. 2 of 117 third-party actions pinned to a tag rather than a commit. |
| Network hosts | 40 distinct hosts referenced from source; most often github.com, docs.immich.app, demo.immich.app, immich.app. No URLs to bare IP addresses. |
| Known vulnerabilities | 138 advisories across 2,867 pinned packages: 3 critical, 66 high, 54 moderate, 15 low. machine-learning/uv.lock: 122 packages, 39 advisories; pnpm-lock.yaml: 2,745 packages, 99 advisories. |
| Project hygiene | Has automated dependency updates, CodeQL, licence file, contributing guide. Missing security policy. |
| OpenSSF Scorecard | Not scored: the project is not in Scorecard's weekly index. |
The raw findings
Every hit the scanner wrote out, with a link to the exact line at the scanned commit. Secrets candidates are redacted.
Secret candidates (1, redacted)
| Where | Rule | Match |
|---|---|---|
| packages/e2e-auth-server/test-keys.ts:1 | private-key | -----B…--- (27 chars) |
npm lifecycle scripts (2)
packages/cli/package.jsonprepack:pnpm run buildweb/package.jsonprepare:svelte-kit sync
Installer scripts (2)
- install.sh, 108 lines; talks to github.com
- server/bin/start.sh, 66 lines
Worst known vulnerabilities (24 of 138)
| Advisory | Severity | Package | Summary |
|---|---|---|---|
| GHSA-82r6-8w77-94w6 | critical | anyio@4.2.0 | AnyIO: TLSStream IDNA 2003 host name encoding enables potential TLS certificate spoofing |
| GHSA-23hp-3jrh-7fpw | critical | tar@6.2.1 | node-tar: Decompression/parse DoS via unlimited input |
| GHSA-23hp-3jrh-7fpw | critical | tar@7.5.16 | node-tar: Decompression/parse DoS via unlimited input |
| GHSA-2qfp-q593-8484 | high | brotli@1.1.0 | Scrapy is vulnerable to a denial of service (DoS) attack due to flaws in brotli decompression implementation |
| GHSA-hxwh-jpp2-84pm | high | flask-cors@4.0.1 | Flask-CORS allows the `Access-Control-Allow-Private-Network` CORS header to be set to true by default |
| GHSA-6v7p-g79w-8964 | high | msgpack@1.1.2 | MessagePack for Python: Out-of-bounds read / crash on Unpacker reuse after a caught error |
| GHSA-7gcm-g887-7qv7 | high | protobuf@6.33.2 | protobuf affected by a JSON recursion depth bypass |
| GHSA-cgwc-pv48-fhj5 | high | python-engineio@4.12.2 | python-engineio has unbound thread allocation that can cause denial of service |
| GHSA-m9gh-vj53-gvh9 | high | python-engineio@4.12.2 | python-engineio has possible denial of service due to maximum payload size sometimes not being enforced |
| GHSA-5w7q-77mv-v69f | high | python-socketio@5.16.0 | python-socketio: Binary attachment accumulation can cause denial of service |
| GHSA-82w8-qh3p-5jfq | high | starlette@0.50.0 | Starlette: request.form() limits silently ignored for application/x-www-form-urlencoded enable DoS |
| GHSA-wqp7-x3pw-xc5r | high | starlette@0.50.0 | Starlette: SSRF and NTLM credential theft via UNC paths in StaticFiles on Windows |
| GHSA-38jv-5279-wg99 | high | urllib3@2.6.2 | Decompression-bomb safeguards bypassed when following HTTP redirects (streaming API) |
| GHSA-8988-9cw3-xx77 | high | urllib3@2.6.2 | urllib3: HTTPS proxy TLS configuration may be ignored or overridden |
| GHSA-mf9v-mfxr-j63j | high | urllib3@2.6.2 | urllib3: Decompression-bomb safeguards bypassed in parts of the streaming API |
| GHSA-qccp-gfcp-xxvc | high | urllib3@2.6.2 | urllib3: Sensitive headers forwarded across origins in proxied low-level redirects |
| GHSA-vxq7-64xx-v4gw | high | urllib3@2.6.2 | urllib3: HTTPResponse.stream()/read_chunked() buffers an unbounded chunk-size line into memory |
| GHSA-8rrh-rw8j-w5fx | high | wheel@0.45.1 | Wheel Affected by Arbitrary File Permission Modification via Path Traversal in wheel unpack |
| GHSA-m9gg-hp2v-232j | high | @grpc/grpc-js@1.14.4 | @grpc/grpc-js: In certain configurations, getAuthContext can return unauthorized certificates as though they were author… |
| GHSA-3jxr-9vmj-r5cp | high | brace-expansion@1.1.15 | brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups |
| GHSA-6j4f-fj2g-mc7p | high | brace-expansion@1.1.15 | brace-expansion: DoS via uncontrolled recursion in parseCommaParts causing stack exhaustion |
| GHSA-mh99-v99m-4gvg | high | brace-expansion@1.1.15 | brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash |
| GHSA-qhr7-859c-m2p7 | high | brace-expansion@1.1.15 | brace-expansion: DoS via uncontrolled recursion on nested brace groups causing stack exhaustion |
| GHSA-rgw5-rvv9-x895 | high | brace-expansion@1.1.15 | brace-expansion: DoS via unbounded intermediate arrays, bypassing the CVE-2026-14257 mitigation |
Workflows worth a look
- .github/workflows/auto-close.yml: pull_request_target
- .github/workflows/backport.yml: pull_request_target
- .github/workflows/docs-destroy.yml: pull_request_target
- .github/workflows/pr-label-validation.yml: pull_request_target
- .github/workflows/pr-labeler.yml: pull_request_target
By the numbers
| Stars | 115.5K |
|---|---|
| Forks | 7,117 |
| Contributors | 1,015 |
| Commits | 11.2K |
| Open issues | 473 |
| Open pull requests | 201 |
| Releases | 315 |
| Latest release | v3.2.4 |
| Licence | AGPL-3.0 |
| Main language | TypeScript |
| Project age | 4 years |
| Last push | Oct 3, 2026 |
| Tracked files | 3,463 |
| Lines of code | 473.9K |
| Checkout size | 111 MB |
Lines by language: JSON 213.3K, TypeScript 172.1K, Svelte 42.2K, Markdown 12.2K, SQL 10.4K, Python 6,216.
Questions
Is Immich free?
Yes. Immich is AGPL-3.0 and every feature is free. The team sells an optional product key (per user or per server) as a way to support development; it unlocks no features. Your costs are the hardware and storage you run it on.
Can Immich replace Google Photos completely?
For most people, yes: background phone backup, face grouping, smart search, sharing, memories and a map are all there. What you take on is running a server, keeping it updated, and doing your own backups and remote access. Google Takeout archives can be imported with community tools such as immich-go.
Does Immich need a GPU?
No. Face detection and smart search run on the CPU by default, which is fine for a home library though the first indexing pass can take hours. Hardware acceleration is supported for machine learning and video transcoding on NVIDIA, Intel and some other GPUs if you have one.
This post is part of GitHub Tools, where every repository is cloned and scanned before it is written up. The scan is a snapshot of one commit on one day; the repository has moved on since, so check it before you install.
