Docker-Hosted Server
For a library shared across several devices — or hosted on a NAS or an always-on machine rather than your everyday computer — run Fotohordr as a headless Docker server. Devices connect to it over encrypted peer-to-peer (P2P), and a browser can also reach a read-only Web UI directly.
There are two ways in:
- P2P (iroh) — the native desktop and Android apps pair with the server over an encrypted, direct connection. This is the same mechanism the standalone app uses to let other devices connect to it (see Connecting a Client).
- Browser Web UI over HTTPS — a WebAssembly build of the same UI is baked into the server image and served over HTTPS + WebSocket. It has no authentication and is read-only — anyone who can reach the port can browse, search, and view the live library, but not modify it. Keep the port on a trusted network, or behind a VPN / authenticating reverse proxy.
The image
Section titled “The image”The server is published on Docker Hub as
rudibenkovic/fotohordr-server.
docker pull rudibenkovic/fotohordr-server:latestlatest tracks the newest release; every release is also tagged with its
version number (for example 0.36.1), so pin a tag if you’d rather upgrade
deliberately.
Quick start
Section titled “Quick start”The shortest thing that works — one command, one data volume, your photos mounted read-only:
docker run -d --name fotohordr \ --restart unless-stopped \ -p 8787:8787/udp \ -p 8080:8080 \ -e FOTOHORDR_P2P_EXTERNAL=192.168.1.10:8787 \ -v fotohordr-data:/data \ -v /srv/photos:/mnt/photos:ro \ rudibenkovic/fotohordr-server:latestReplace 192.168.1.10 with this machine’s own LAN address. Then:
- Get the pairing link. The server prints a
foto://pair?…link on startup —docker logs fotohordr. It expires after 10 minutes;docker restart fotohordrmints a fresh one. - Pair a client. Paste the link into a desktop or Android client’s connect screen — see Connecting a Client.
- Add your photos. From that client, go to Settings → Libraries →
Photo folders → Browse Server Filesystem, and add
/mnt/photos. Photo folders live in the database, so there’s no env var for this.
Indexing starts immediately; the library fills in as previews are built.
What the flags do
Section titled “What the flags do”-p 8787:8787/udp— the iroh P2P port. Publish it 1:1 so LAN clients connect directly instead of relaying.-p 8080:8080— the browser Web UI. Omit it if you only use the native apps.FOTOHORDR_P2P_EXTERNAL=<host-lan-ip>:8787— advertises this machine’s LAN address so nearby clients connect directly rather than through the relay. Also becomes the Web UI certificate’s allowed hostnames. The published host UDP port must match the port named here./data— persist this volume. It holds the database, the P2P identity, and the paired-device list. Losing it means every paired device has to re-pair, and the library has to rebuild from your photo files.- Mount photo directories read-only (
:ro) — the server never writes to your originals.
Docker Compose
Section titled “Docker Compose”services: fotohordr: image: rudibenkovic/fotohordr-server:latest container_name: fotohordr restart: unless-stopped stop_grace_period: 20s ports: - "8787:8787/udp" - "8080:8080" environment: FOTOHORDR_P2P_EXTERNAL: "192.168.1.10:8787" # FOTOHORDR_LICENSE_KEY: "<your-key>" volumes: - fotohordr-data:/data - /srv/photos:/mnt/photos:ro
volumes: fotohordr-data:stop_grace_period matters: the server needs a few seconds on shutdown to
close its database cleanly, and being killed early costs a slow integrity
scan on the next start. Keep it above FOTOHORDR_SHUTDOWN_GRACE_SECS.
Upgrading
Section titled “Upgrading”docker pull rudibenkovic/fotohordr-server:latestdocker stop -t 20 fotohordr && docker rm fotohordr# re-run the same `docker run` command, or: docker compose up -dKeep the /data volume and paired devices reconnect on their own — no
re-pairing, no rebuild.
Licensing and demo mode
Section titled “Licensing and demo mode”Without a license key the server indexes at most 1,000 photos, so you can run it and see your own library before deciding. The standalone desktop app is never capped and needs no key.
- The cut is deterministic: every file the scan finds is sorted by full path and the first 1,000 are kept, so the same library always shows the same subset. Photos over the cap are never opened — no previews, no geocoding, no face detection for them.
- Clients show a persistent banner while the cap is active.
FOTOHORDR_LICENSE_KEYseeds a key on first run only. After that the database is the source of truth — changing the env var later does nothing (the startup log says so).- Settings → License on an admin device shows the status and lets you enter, change or remove the key. Saving triggers a rebuild, so the library grows to what the license allows.
- Backups carry the key: exporting includes it, importing one that has a key installs it.
The startup log states the outcome — either license valid: photo library unlimited, or a warning naming the active limit.
Networking options
Section titled “Networking options”The quick-start above is option A. Two others are available if it doesn’t fit your setup:
- A. Bridge + published UDP + advertised LAN address — what the quick
start uses. Works on the LAN and remotely. Add a second entry
(
<public-ip>:<forwarded-port>) toFOTOHORDR_P2P_EXTERNALif you also port-forward at the router. - B. Host networking (Linux, simplest): run with
--network hostand no-pflags — iroh auto-discovers the LAN address, soFOTOHORDR_P2P_EXTERNALusually isn’t needed. - C. macvlan / ipvlan: give the container its own LAN IP, so it behaves like a physical host.
If the advertised address doesn’t match a reachable host:port, pairing
still works — iroh falls back to its relay. You just lose the direct LAN
path, and with it a lot of speed.
Configuration reference
Section titled “Configuration reference”All settings are environment variables (each has an equivalent CLI flag):
| Env var | Default | Purpose |
|---|---|---|
FOTOHORDR_DB | /data/fotohordr.db | The database file |
FOTOHORDR_P2P_PORT | 8787 | Fixed UDP port iroh binds |
FOTOHORDR_P2P_EXTERNAL | (unset) | LAN/public host:port to advertise as directly reachable (comma-separated for several) |
FOTOHORDR_HTTP_PORT | 8080 | TCP port for the HTTPS Web UI + WebSocket |
FOTOHORDR_LICENSE_KEY | (unset) | License key; seeds the database on first run only |
FOTOHORDR_SHUTDOWN_GRACE_SECS | 8 | How long shutdown waits for in-flight work before exiting anyway — keep at or below your docker stop -t <seconds> |
The self-signed certificate
Section titled “The self-signed certificate”Since the server usually has no public domain name, it generates and
persists its own local certificate authority on first start, and issues a
certificate covering localhost and every host in
FOTOHORDR_P2P_EXTERNAL. Your browser will warn about this on first visit —
either click through it, or download https://<host>:8080/ca.crt and add
it to your device’s trust store once, after which every future visit is a
clean padlock.
Pairing the first device
Section titled “Pairing the first device”On startup, the server logs a foto://pair?... link (and its expiry) to
docker logs. Redeem it from a desktop client
(fotohordr_client --ticket <link>), or paste it into the client’s connect
screen — see Connecting a Client.
Already-paired devices reconnect automatically without a new link; if the
printed link expires before anyone uses it, docker restart mints a fresh
one. Once one admin device is paired, further devices can be paired from
that client’s Settings → Remote access page — see
Remote Access & Pairing — without needing
to read docker logs again.