# The photofield single binary needs three build tags and four system packages

> SmilyOrg/photofield is a self-hosted Go photo gallery that keeps the file system as the source of truth and everything else in a SQLite cache. Its shipped compose file is a development stack with no data volume, and its dependency list carries three WebP bindings and a beta migration tool.

**SmilyOrg/photofield** —  A self-hosted non-invasive single-binary photo gallery with a focus on speed and simplicity.

- Repository: https://github.com/SmilyOrg/photofield
- Website: https://photofield.dev/
- Stars: 608 · Forks: 16
- Language: Go
- License: MIT
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/smilyorg-photofield

## One binary means three build tags and four system packages

The single-file claim is real but conditional. The container build compiles with three tags, one each for the embedded interface, the embedded documentation and the embedded geolocation database, and the repository root carries a paired stub file for each of those three, so a build without the tags produces a binary with none of that content in it. The runtime is a separate matter: the image installs exiftool for metadata, ffmpeg for video thumbnails and HEIC support, the fast JPEG decoder utilities, and libwebp, then hand-patches the shared library with a symbolic link from one soname to the one the code looks for. That link is the fragile part. If the base image's libwebp moves to a new soname, the link points at a file that no longer exists and nothing in the build tells you.

## The compose file in the repository root is a development stack

The compose file at the top level is not the deployment example the documentation gives. It builds the image from source rather than pulling a published one, publishes port 8080, mounts only the documentation assets read-only, sets its restart policy to no, and carries a Traefik label for the service port. Around the gallery it starts four more services: Prometheus, a continuous profiling server, Grafana on a non-standard host port mapped to its own, and a local container registry that exists for a Taskfile target that pushes multi-architecture images. Most importantly it mounts no data directory at all, which means the SQLite cache and the configuration file the application writes live in the container's writable layer and disappear when the container is replaced. `restart: "no"` also means the gallery does not come back on its own after a reboot.

## The compose example in the documentation has the same missing volume

The example folded into the documentation binds the usual Synology photo directories and expects a certain layout:
```
image: ghcr.io/smilyorg/photofield:latest
ports:
  - 8080:8080
volumes:
  - /volume1/photo/:/photo:ro
```
Two things are worth noticing. It declares a schema version that Compose has not needed for years, and more to the point it mounts only the photo directories, with no data volume, so the cache database has nowhere to persist. Given the project's own rule that the file system is the source of truth and everything else is a more or less stale cache, an example that loses the cache on every recreate is teaching the wrong first lesson. A related detail sits in the image build: it creates the data directory and touches a configuration file inside it, so the container ships a configuration stub that the documentation never mentions and never asks you to look at.

## No accounts, no authorization, and a cache that is allowed to be stale

The limitations section is the most useful part of this documentation, and it is unusually blunt. The server keeps state that would normally live on the client, so more than a few simultaneous users will run into processor or memory trouble. There are no user accounts and no authentication or authorization support at all; the suggested workaround is to define separate collections per person using the directory structure, which is access control by folder layout rather than by identity. There are no permalinks in the strict sense either: deep links to images work until you delete the database or move the files, at which point they break. Underneath all of it is one design decision stated plainly: the original files are never touched, you are encouraged to mount them read-only, and the file system is the source of truth while everything else is a cache that may be out of date.

## Three WebP bindings, two metadata readers, and a beta migration tool

The module's requirement list carries three separate Go bindings for WebP, three libraries that can decode or encode the same format, plus one module explicitly named for native WebP support. Metadata extraction has two readers as well, one of which shells out to exiftool, which is why exiftool is installed in the runtime image. Meanwhile the build sets CGO disabled, so anything in that list needing a C library has to reach it through an external process or a manually linked soname, which is exactly what the libwebp symlink is doing. Two entries stand out for a different reason. The database migration tool is required at a beta version, in the primary requirement block rather than behind a flag, and the vector rendering library is pinned to a pseudo-version from 2020 while everything around it is current. The module itself is declared under a bare name with no repository path.

## The speed goal has one demo behind it and real numbers behind the indexing

The stated ambition is to be as fast as or faster than a commercial photo service on commodity hardware while showing more photos at once. What backs that up is a single demonstration: zooming into a logo inside a sample of forty-three thousand images, on a named six-core processor with an NVMe drive. There is no measurement of the comparison anywhere in the documentation. The numbers that are given are narrower and more useful. File indexing is claimed at roughly a thousand to ten thousand files per second on a fast drive with a hot cache, and the follow-up passes that extract metadata and prominent colour are claimed at up to about two hundred and about a thousand files per second. Video is supported across multiple resolutions only when they were generated in advance by other software, because on-the-fly transcoding is not supported.

## Tags and faces write into the cache database

Three optional features build on the same substrate, and each is at a different maturity. Semantic search of photo contents is off by default and needs a separate component, with example queries like a beach at sunset or a cat's eyes. Tagging is labelled alpha, stores its tags in the cache database, and uses them to filter the gallery. Face detection is also alpha, produces a dedicated layout of detected face crops, and supports searching by face identifier. Reverse geolocation is the exception: it is local and embedded, covering about fifty thousand places from a compact package, and it is served from the timeline and flex layouts with what the documentation calls negligible overhead. The consequence of the first three is the same as the permalink limitation: derived data lives in the cache, so it is the first thing lost when the cache is.

## The newest release is an MCP server, and the root is full of internal documents

The most recent tag is titled around an MCP server and stability work, and the evidence is in the tree: a server configuration file at the root and the official Go SDK for that protocol in the requirement list. Around it sits a repository that keeps its working papers in version control. There is a split plan document, a release checklist, an OpenAPI specification, a changelog tool with its own directory of fragments, an environment file for development, and a configuration file for a web extension linter. Four test files sit directly beside the main entry point rather than inside a package directory, and there are separate embed and embed-stub pairs for the interface, the documentation and the geolocation data, which is the clearest statement of what the build tags are for. Fonts are committed too, since they are part of what gets embedded.

## Conclusion

Suitable as a read-only viewer over an existing photo library, since the originals are never touched and you are encouraged to mount them that way, and unsuitable as anything multi-user, because there are no accounts and no authorization. Before deploying, copy a compose file of your own rather than using the one in the repository, which builds from source, mounts no data volume and does not restart. If you rely on tags, faces or search results, remember they live in the cache database, which the project itself describes as possibly stale.

## FAQ

### Does photofield support multiple users or accounts?

No. There is no authentication or authorization support and no user accounts, and the documentation suggests defining separate collections per user using the directory structure instead. It also warns that server-side state means more than a few simultaneous users will hit processor or memory limits.

### Does photofield transcode videos on the fly?

No. Videos are supported along with multiple resolutions when those were generated in advance by other software, such as Synology Moments, but on-the-fly transcoding is not supported. The container image installs ffmpeg for video thumbnails and for HEIC, HEIF, MOV and GIF handling.

### How do I run photofield with Docker?

Create an empty data directory and put photos in a photos directory, then run the image with port 8080 published, the data directory mounted writable and the photos directory mounted read-only. The compose file in the repository root is a development stack instead: it builds from source, mounts no data volume and sets its restart policy to no.

### Does photofield leave my original photos untouched?

Yes, by design. The documentation says the original files are not touched, encourages mounting them read-only, and states that the file system is the source of truth while everything else is a more or less stale cache. Tags and detected faces are stored in that cache database.

### Can photofield search what is inside my photos?

Optionally, through a separate component, with example queries such as a beach at sunset or a cat's eyes. Tagging and face detection are marked alpha, keep their data in the cache database, and the faces layout shows detected crops with search by face identifier.

### What limitations does photofield document about itself?

That it is not optimized for many simultaneous clients, has no user accounts or authorization support, can load slowly the first time a page is opened at a new window size, and offers no permalink guarantee, since deep links can break if the database is removed or the files are moved.

## Sources

- [License: MIT](https://github.com/SmilyOrg/photofield/blob/main/LICENSE)
- [Project website](https://photofield.dev/)
- [README](https://github.com/SmilyOrg/photofield/blob/main/README.md)
- [Releases](https://github.com/SmilyOrg/photofield/releases)
- [SmilyOrg/photofield on GitHub](https://github.com/SmilyOrg/photofield)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/smilyorg-photofield
