Skip to content

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:

  1. 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).
  2. 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 server is published on Docker Hub as rudibenkovic/fotohordr-server.

Terminal window
docker pull rudibenkovic/fotohordr-server:latest

latest 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.

The shortest thing that works — one command, one data volume, your photos mounted read-only:

Terminal window
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:latest

Replace 192.168.1.10 with this machine’s own LAN address. Then:

  1. Get the pairing link. The server prints a foto://pair?… link on startup — docker logs fotohordr. It expires after 10 minutes; docker restart fotohordr mints a fresh one.
  2. Pair a client. Paste the link into a desktop or Android client’s connect screen — see Connecting a Client.
  3. 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.

  • -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.
  • /datapersist 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.
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.

Terminal window
docker pull rudibenkovic/fotohordr-server:latest
docker stop -t 20 fotohordr && docker rm fotohordr
# re-run the same `docker run` command, or: docker compose up -d

Keep the /data volume and paired devices reconnect on their own — no re-pairing, no rebuild.

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_KEY seeds 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.

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>) to FOTOHORDR_P2P_EXTERNAL if you also port-forward at the router.
  • B. Host networking (Linux, simplest): run with --network host and no -p flags — iroh auto-discovers the LAN address, so FOTOHORDR_P2P_EXTERNAL usually 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.

All settings are environment variables (each has an equivalent CLI flag):

Env varDefaultPurpose
FOTOHORDR_DB/data/fotohordr.dbThe database file
FOTOHORDR_P2P_PORT8787Fixed UDP port iroh binds
FOTOHORDR_P2P_EXTERNAL(unset)LAN/public host:port to advertise as directly reachable (comma-separated for several)
FOTOHORDR_HTTP_PORT8080TCP port for the HTTPS Web UI + WebSocket
FOTOHORDR_LICENSE_KEY(unset)License key; seeds the database on first run only
FOTOHORDR_SHUTDOWN_GRACE_SECS8How long shutdown waits for in-flight work before exiting anyway — keep at or below your docker stop -t <seconds>

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.

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.