What if your photo folders are already organized exactly the way you want?
Many photo platforms answer that question by importing everything into their own library. PiGallery2 takes the filesystem seriously instead. Point it at an existing directory tree and those folders become the gallery. Your originals can stay on a read-only mount, independently usable by backup tools, file shares, and future applications.
That makes PiGallery2 interesting for a NAS archive, a family collection arranged by year and event, or a Raspberry Pi-class home server.
It is not trying to reproduce every part of Google Photos.
It is a focused, fast browser for media you already manage.
What is PiGallery2?
PiGallery2 is an MIT-licensed photo and video gallery written primarily in TypeScript. Its Angular frontend talks to an Express backend, while Sharp and FFmpeg handle image and video derivatives. The project supports maps, metadata, search, shares, saved-search albums, a photo-frame mode, users, OpenID Connect, and administrative indexing jobs.
Its defining property is directory-first organization:
- Existing folders become browsable albums without moving the originals.
- EXIF, IPTC, and XMP information improves presentation and search.
- Geotagged images can appear on a map, with GPX track support.
- Videos can be played and converted into web-friendly derivatives.
- The photo directory can remain read-only.
There is one important nuance: directory-first does not mean database-free.
PiGallery2 uses SQLite by default for indexed metadata, users, and application state, with MySQL/MariaDB available as an alternative.
The original files remain the source of truth, but config and database volumes still matter and should be backed up.
When is it a good fit?
PiGallery2 is at its best when you already have a stable hierarchy such as:
photos/
├── 2024/
│ ├── cycling-trip/
│ └── family-christmas/
├── 2025/
│ └── warsaw-weekend/
└── scanned-archive/
Mount that tree at /app/data/images:ro, let PiGallery2 index it, and browse. You retain a clean separation between irreplaceable media and replaceable application state.
It is less suitable if automatic mobile backup is the main requirement.
For that, Immich offers native mobile applications, a timeline, and a much broader ML pipeline.
PhotoPrism is attractive for AI-assisted discovery and cataloguing, while Piwigo leans toward managed albums and community publishing.
PiGallery2 wins when folders should remain in charge.
Explore geotagged photos on a world map
PiGallery2 includes a world map that places photos according to the GPS coordinates stored in their EXIF metadata. This makes it easy to browse a collection geographically and revisit photographs from a particular country, city, or trip.
The map does not infer where an image was taken. A photo must contain latitude and longitude metadata before it can appear. Images without GPS coordinates remain available in the ordinary gallery but are not plotted automatically. PiGallery2 also supports GPX tracks, allowing a recorded journey to provide additional geographical context alongside the photographs.
Self-hosting PiGallery2 with Docker
The project recommends Docker; a native Node.js deployment is not its supported installation route.
This guide pins stable release 3.5.2 rather than following latest or the development branch.
Get Docker 🐋
Install Docker on your system before proceeding:
- Linux: Official Docker Engine install guide
- Windows / Mac: Docker Desktop
Verify installation: docker --version && docker compose version
The complete Compose configuration lives in the public Home-Lab PiGallery2 folder :
Create .env alongside it:
# Use an absolute path. The Compose file mounts this directory read-only.
PIGALLERY2_IMAGES=/srv/photos
# Bound to localhost by default.
PIGALLERY2_PORT=8088
# Set these when a reverse proxy is configured.
PIGALLERY2_PUBLIC_URL=https://photos.example.com
PIGALLERY2_TRUST_PROXY=false
Why these choices?
bpatrik/pigallery2:3.5.2makes upgrades deliberate and repeatable.- The photos use a read-only bind mount.
- Configuration, SQLite data, and generated temporary files use separate persistent volumes.
- Port 80 inside the container is published only on
127.0.0.1. - A
/heartbeathealth check runs with Node already present in the image. - A 3 GiB memory limit follows the project’s conservative sizing while preventing unbounded use.
no-new-privilegesremoves one unnecessary privilege-escalation path.
Validate the expanded configuration before starting it:
docker compose config
docker compose up -d
docker compose ps
docker compose logs -f pigallery2
Then open:
http://127.0.0.1:8088
Change the default administrator password immediately. Do that before making the service reachable from another machine or putting it behind a public hostname.
What I tested
I tested the official bpatrik/pigallery2:3.5.2 image against the project’s demo photo directory. The application started on an alternate local port, /heartbeat returned HTTP 200 with OK, and the gallery root returned HTTP 200. A request to /pgapi/user/me without a session returned HTTP 401, as it should.
Docker confirmed that /app/data/images was read-only. A write attempt failed, while the configuration, database, and temporary mounts remained writable. The application created a persisted config.json and SQLite database, reinforcing why those volumes belong in the backup plan. With only demo content it idled at roughly 112 MiB of RAM.
The test host had exhausted Docker’s available bridge subnets because it already contained many networks. I used host networking with an explicit port for the live check; that is a host-level Docker constraint, not a PiGallery2 error. The published Compose file itself resolves correctly and retains ordinary bridge networking for portability. After testing, I stopped the container and retained its image and volumes.
First scan and resource planning
A small demo does not predict the cost of indexing decades of photos. During the first scan, PiGallery2 reads metadata and produces thumbnails; videos may introduce FFmpeg work. CPU, memory, and temporary disk activity will all rise.
For a larger collection:
- Confirm that the temporary volume has enough free space.
- Start with a representative subdirectory.
- Watch
docker statsand application logs during indexing. - Measure thumbnail and video-conversion time on your actual hardware.
- Add the remaining collection once the estimates look reasonable.
Original media should also have an independent backup. A gallery interface is not a backup strategy, even when it is reading from a NAS.
Reverse proxy and authentication
The localhost binding is intentional. Keep it that way until an HTTPS reverse proxy is ready. Set PIGALLERY2_PUBLIC_URL to the real external URL so generated links and OIDC redirects are correct.
Be conservative with Server-trustProxy. Leaving it false is appropriate for direct local access. Behind a proxy, configure trust to match the actual proxy topology instead of casually trusting every upstream address. The proxy can also provide TLS, request-size controls, timeouts, security headers, and rate limiting.
PiGallery2 supports local accounts and OpenID Connect. OIDC is useful when a home lab already has a central identity provider, but the callback, issuer, client ID, client secret, and public URL must agree precisely. Keep ordinary authentication required unless the gallery is deliberately public.
Sharing links should be treated like credentials. Anyone holding one may be able to reach its content, so expire or revoke links that are no longer needed.
Can PiGallery2 be automated with code?
Mostly—but with an important support boundary.
Configuration is code-friendly: the application accepts a JSON configuration, environment variables, and command-line values. Docker Compose captures the runtime, mounts, resource limits, and network exposure. Indexing and maintenance jobs are also represented in the application’s server routes.
The Angular client communicates with an HTTP API under /pgapi. Source inspection shows endpoints for galleries, search, users, albums, people, shares, uploads, jobs, statistics, settings, extensions, and OIDC. A Python, JavaScript, Rust, or shell client can technically authenticate and issue the same JSON-over-HTTP requests.
However, PiGallery2 does not advertise /pgapi as a stable, versioned public SDK contract. Browser automation or a custom client can work, but it couples your code to a specific release. A robust integration should:
- Pin the PiGallery2 version.
- Use a dedicated least-privilege account if the available roles allow it.
- Preserve cookies and CSRF-related behavior exactly as the web client expects.
- Derive request and response structures from that tag’s source.
- Run contract tests before every upgrade.
- Prefer filesystem and documented configuration automation when it meets the need.
For ingest, a controlled filesystem pipeline followed by an index job is often cleaner than driving the UI. Upload endpoints exist, but enabling writes changes the safest read-only design and increases the amount of API behavior your automation must own.
Backups and upgrades
At minimum, back up the photo library, pigallery2-config, and pigallery2-db. The temporary volume contains reproducible derivatives, although preserving it can save a lengthy rebuild. Stop the service or use a SQLite-aware procedure when copying a live SQLite database.
An upgrade routine can stay simple:
docker compose stop
# Back up config and database volumes here.
docker compose pull
docker compose up -d
docker compose logs --tail=100 pigallery2
Do not change the image tag until you have read the PiGallery2 release notes . Release 3.5.2 included breaking changes involving search configuration, saved searches, sharing, random-photo queries, and extension button data. Those areas deserve a staging test if you use extensions or custom API clients.
The practical verdict
PiGallery2 has a refreshingly legible contract: you organize the files; it makes them pleasant to browse. It preserves the portability of a normal directory tree while adding enough indexing, search, mapping, sharing, and administration to make a private archive useful from a browser.
It is not a drop-in substitute for a mobile-first backup service, and its internal API should not be mistaken for a guaranteed public SDK. But for an existing library—especially one that should remain read-only—it is one of the cleaner self-hosted designs available.
Project links
FAQ
Does PiGallery2 copy or reorganize my photos?
Not for its normal directory-first workflow. It reads the mounted hierarchy and can operate with that mount set to read-only. Uploads are optional and require a deliberate writable path.
Does it work without a database?
No. The originals stay in ordinary folders, but PiGallery2 uses SQLite by default for indexed metadata and application state. MySQL/MariaDB is optional.
Is it a complete Google Photos replacement?
It replaces the private web-gallery portion particularly well. If automatic phone uploads, a timeline-first UI, native mobile apps, and ML-powered semantic search are essential, Immich is closer to that goal.
Can it run on a Raspberry Pi?
The official image supports ARM64 and the project targets low-resource systems. Verify that the device runs a 64-bit OS and benchmark the first index on the actual collection. Do not assume current ARM32 support.
Can I call PiGallery2 from Python or Rust?
Technically yes, through the same HTTP/JSON API used by the frontend. There is no advertised stable public SDK, so pin the server release and test the contract during upgrades.
Comments