Self-hosted alternatives to Google Photos exist, and the four we tested differ more in what they ask of your server than in what the screenshots show. We ran each from its official Docker setup on one PrivateByte VPS and measured the cost before a single photo went in.

Using an AI coding agent? There's a ready-made prompt at the end of this guide. Copy that instead of this article.

What this is, and what it is not

This is a measurement of the hosting side: start time, idle memory, disk, and whether the web page answers. Every number is from our own runs, with the method at the end.

It is not a hands-on review. The test rules said no accounts, sign-ins, passwords or uploads, so we never added a photo, scanned a face or restored a Takeout export. Feature statements come from each project's documentation and repository. Where first run forced an admin account, we report that as a finding and measure only the services.

Which four, and why

On 22 October 2026 we checked each repository for activity, licence and paid gating, and kept tools that index a photo library and run in Docker.

Tool Stars Last push Latest release Licence
Immich about 116,900 22 Oct 2026 v3.3.5, 21 Oct 2026 AGPL-3.0
PhotoPrism about 40,300 22 Oct 2026 261007, 7 Oct 2026 AGPL-3.0 source; Docker image under its Plus License
Ente about 29,500 22 Oct 2026 photos v1.3.66, 21 Oct 2026 AGPL-3.0
LibrePhotos about 8,100 22 Oct 2026 1.4.0, 17 Oct 2026 MIT

We left out Lychee (MIT, v7.11.1 on 13 October, with a paid Supporter Edition) and Piwigo. They are gallery-first, and we did not check phone backup or machine learning there. All four tools above are actively maintained.

What each one needs, from the official docs

Immich runs four containers: the server, a machine-learning (ML) container, Valkey (a Redis-compatible store) and PostgreSQL with vector extensions. The docs state 6 GB of RAM as the minimum and 8 GB recommended, 2 CPU cores minimum and 4 recommended, and say a 4 GB machine can run it with machine learning turned off. Thumbnails and transcoded video add 10 to 20 percent to the library's size, and the database is typically 1 to 3 GB. Since version 3 the ML container needs a CPU with the x86-64-v2 instruction level. ML unloads its models after 300 seconds of inactivity by default.

PhotoPrism needs the app container and a database, MariaDB in the official Compose file (SQLite also works). The docs ask for 2 cores, 3 GB of RAM and a 64-bit OS, advise 4 GB of swap, and warn against a hard memory limit because indexing can spike. Face recognition and labelling are built in, with no separate ML container. The source is AGPL-3.0, but the images on Docker Hub, including latest, are under the project's Plus License. The AGPL build is the ce tag. Paid memberships (Essentials from 2 euro a month, Plus from 6) add features such as extra user roles and more map styles. The free edition's account roles are Super Admin, Admin and Guest.

Ente runs museum (its API server), a web app, PostgreSQL, an S3-compatible object store and a small helper, five containers in the official quickstart. The docs say at least 1 GB of RAM and one core. Ente is end-to-end encrypted, so face grouping and search run on your devices, not on the server. That is why the server list has no ML container. The quickstart is described as a sample setup, and the docs point serious users to external S3 storage and a separate database.

LibrePhotos has an unusual shape. Face detection, scene tags, semantic search and captions run inside the backend container, with per-feature switches in the Compose environment. The docs list 4 GB of RAM as the minimum and 8 GB or more as recommended, 2 cores, and 10 GB of disk plus your library. It ships two setups: a single container (SQLite or your own PostgreSQL), which the project recommends for most people, and a four-container Compose stack with a separate PostgreSQL. It is MIT-licensed.

What you can do on day one, per the docs

From documentation only, not from use:

Immich PhotoPrism Ente LibrePhotos
Phone app iOS, Android; App Store, Google Play, APK, F-Droid (FUTO), Obtainium No native app; installable web app (PWA) iOS, Android, F-Droid, desktop, web Not released; run from source
Backup from phone In-app, on open and periodically in the background, chosen albums Third-party apps over WebDAV, e.g. PhotoSync (paid after trial), Syncthing Fork (free) In-app, point it at your server Planned in the unreleased app; web upload today
Face recognition and search Yes, in a separate ML container Faces built in Yes, on the device Yes, inside the backend
Google Takeout Docs recommend the community tool immich-go Extract into the originals or import folder; albums are not imported Import in the Ente desktop app Unzip into the library folder and scan (since 1.4.0)

Three notes on that table. PhotoPrism's Takeout route reads Google's JSON files for dates and places, but its docs say albums are not imported automatically and point to a community script. LibrePhotos's docs say the Takeout reader does not read albums, people or favourites either. And Ente's quickstart points its object store at the server's own address; the docs link a separate guide for allowing uploads from the mobile app, and we did not try it.

What we measured

One PrivateByte Comet VPS (4 vCPU, 8 GB RAM, 100 GB disk, Ubuntu 24.04, Docker 29.9.2, Compose 5.6.1). Each setup ran alone, three cold starts each, with empty data before every start. "Ready" is the time from docker compose up -d until both the web page and the health URL answered HTTP 200, with images already pulled.

Setup Containers Ready (median, range) Idle RAM, containers Idle RAM, whole server above baseline Images Disk used after first start One-off pull
Ente 5 5.0 s (4.4 to 5.2) 167 MiB (166 to 175) +174 MiB (75 to 176) 1.2 GB 1,277 MiB 10.5 s
PhotoPrism 2 13.5 s (8.3 to 14.1) 292 MiB (290 to 292) +240 MiB (155 to 298) 4.0 GB 3,944 MiB 36.2 s
LibrePhotos, Compose 4 39.0 s (38.5 to 40.2) 625 MiB (625 to 625) +648 MiB (506 to 674) 3.3 GB 3,219 MiB 29.3 s
LibrePhotos, single container 1 33.0 s (32.9 to 33.6) 730 MiB (718 to 731) +708 MiB (631 to 717) 2.3 GB 2,196 MiB 24.8 s
Immich, without ML container 3 8.1 s (8.1 to 9.5) 1,070 MiB (1,049 to 1,086) +1,082 MiB (879 to 1,082) 3.6 GB 3,767 MiB 31.3 s
Immich, with ML container 4 8.8 s (8.8 to 9.3) 1,348 MiB (1,331 to 1,383) +1,327 MiB (1,179 to 1,371) 4.6 GB 4,704 MiB 39.0 s
nginx control 1 0.9 s (0.9 to 1.0) 4.8 MiB about 0 94.5 MB 91 MiB 2.4 s

"Images" is Docker's total for the stack, shared layers counted once. "Disk used" is the rise in the server's used space over a freshly cleaned box, images included. "Idle" is 120 seconds after the first 200, nobody logged in. The container figure sums docker stats; the whole-server figure is the rise in free -m used memory. The nginx control moved the whole-server number by -38, +1 and -10 MiB across three runs, so treat that column as accurate to about 40 MiB.

Reading the table:

What the idle ML number hides

Immich's ML container idled at about 200 MiB because it loads models only when asked. So we ran one extra check: with the stack up, we called the ML container directly with one synthetic image, no face in it. No account, no upload to Immich. Its memory, sampled every three seconds, peaked at 796 MiB and settled near 485 MiB after the face model ran. The first CLIP call took 12.5 seconds and the face call 9.6 seconds, mostly model download. A repeat call took 0.09 seconds. The cache then held 1.6 GB of models.

That is one run on one image, so read it as an order of magnitude, not a benchmark. It is the reason Immich's documented 6 GB minimum is not a typo: idle is cheap, importing a library is not. We did not exercise PhotoPrism's or LibrePhotos's models, so we have no equivalent figure for them.

What happened at first run

Control runs

A closed port returned 000, so a 000 to 200 switch is a real start. A plain nginx container, started three times as a baseline, answered in 0.9 to 1.0 seconds and used 4.8 MiB. The harness polls twice a second, so small times are good to about half a second.

Who each one is for, and what to pick

On sizing: all four idled well inside the 8 GB Comet. Ente and PhotoPrism look like they would fit smaller plans from these numbers, but we did not test Flare or Orbit, and a loaded library uses more than an empty one. Treat that as inference, not a result. For a full walkthrough of the heaviest option, see our guide to self-hosting Immich on a VPS.

Which need outside services

None needs a database or storage service elsewhere; the Compose files bundle them. Two caveats:

How we measured

On a fresh Ubuntu 24.04 server with Docker installed from get.docker.com:

docker system prune -af --volumes   # clean slate between setups
docker compose pull                 # timed once
docker compose down -v              # empty data before each run
docker compose up -d                # start the clock
until [ "$(curl -s -o /dev/null -L -m 3 -w '%{http_code}' http://127.0.0.1:PORT/)" = 200 ]; do sleep 0.5; done
sleep 120                           # idle
docker stats --no-stream            # sum the MemUsage column
free -m; df -m /; docker system df  # server RAM, disk, image size

Ports were published on 127.0.0.1 only, so no setup page was reachable from the internet. Compose files came from each project's docs or repository (PhotoPrism's with its optional extras off), with random per-run secrets. Immich was pinned to v3.3.5; the others used latest as of 22 October.

Not measured: any sign-in, upload, import, face scan, search, mobile app or Takeout restore; a populated library; smaller plans; HTTPS; and PhotoPrism's ce tag. Pull time was timed once per setup, so it has no spread. The ML check is a single run.

Do it with an AI agent

Paste this into Claude Code or Cursor to try one of the tools on your own server and check the same things we did.

Prompt for an AI agent
Deploy ONE self-hosted Google Photos alternative to a fresh Ubuntu 24.04 VPS using the project's official Docker Compose instructions, and report what it costs. I will tell you which tool: Immich, PhotoPrism, Ente or LibrePhotos. If I have not said, ask me first.

ASSUME you run on my own machine and SSH to the server. The server address is SERVER_IP. Key-based SSH as root works.

STEPS:
1. Check the server is empty: "docker ps -a" and "ls". If anything is running or a deploy directory exists, STOP and ask me.
2. Install Docker with the official convenience script from get.docker.com. Show "docker --version" and "docker compose version".
3. Fetch the Compose file from the project's own documentation or repository (not from a blog). Generate any passwords with "openssl rand -hex 24" on the server and keep them in a chmod 600 file. Never print them.
4. Publish the web port on 127.0.0.1 ONLY (for example "127.0.0.1:2283:2283"). Do not bind to 0.0.0.0, because first-run pages let the first visitor create the admin account.
5. "docker compose pull", then "docker compose up -d". Record seconds until the web UI AND the project's health URL both return HTTP 200 with: curl -s -o /dev/null -L -m 3 -w '%{http_code}' URL from the server.
6. Wait 120 seconds. Record "docker stats --no-stream", "free -m", "df -m /" and "docker system df".

RULES:
- Never create an account, sign in, upload a photo or enter a password in the app. I do that in the browser.
- For PhotoPrism, set its admin password in the Compose environment from a random value you generate, and tell me where it is stored. Use the "ce" image tag if I ask for the AGPL build.
- For Ente, use the project's quickstart script and tell me that its docs describe extra setup for phone uploads. Do not change that without asking.
- For LibrePhotos, tell me the mobile app is not released yet.
- If the official Compose file fails to start, show me the container logs. Do not improvise a fix without telling me what you changed.
- Nothing destructive. Do not run "docker system prune" if anything else is on the server.

VERIFY, AND SHOW ME THE OUTPUT OF EACH:
- "docker compose ps" -> all services running
- the HTTP 200 checks, and the time they took
- NEGATIVE CONTROL, from MY machine: curl -m 5 http://SERVER_IP:PORT -> this MUST fail or time out. If it answers, the port is exposed. Stop and tell me.
- the RAM, disk and image-size numbers from step 6

Do not tell me a step succeeded without the command output that proves it. If a check fails, stop and report the actual error.

The agent never creates the admin account, so no password passes through a chat log.

Other Docker self-hosts we have written up: Immich, Jellyfin and Vaultwarden.

Deploy your VPS

Keep your photo library on a server you control.

The Comet plan is the server we measured on, with room for any of the four. Smaller plans may be enough for the lighter tools, but we have not tested them.

Comet plan
$15.99/mo
4 vCPU · 8 GB RAM · 100 GB SSD
Deploy a Comet VPS
  • Unmetered bandwidth, no overage
  • Free DDoS protection
  • Daily automated backups
  • Browser console access
Ready in under 60 seconds. No contract, cancel any time.

Common questions

What is the lightest self-hosted Google Photos alternative? In our runs, Ente: five containers, 167 MiB idle, ready in about five seconds. Its face grouping and search run on your devices, not the server.

Which one is closest to Google Photos? Going by the docs, Immich: native iOS and Android apps with background backup, plus face recognition and search. It also asks for the most RAM, with 6 GB documented as the minimum.

Can I import my Google Photos Takeout? All four have a documented route: Immich via the community tool immich-go, PhotoPrism and LibrePhotos by unzipping into a folder and scanning, Ente through its desktop app. We did not run any of them.

Is PhotoPrism really open source? The source is AGPL-3.0, but the standard Docker images are under the project's Plus License. The AGPL build is the ce tag, and paid memberships add features.

Does LibrePhotos have a phone app? Not yet. Its own documentation says the mobile app has not been published to any store and can only be run from source.