Skip to content

PhotoPrism

PhotoPrism indexes a photo library, classifies what is in the pictures, and recognises faces and places. Two containers — the Go server with its models, and MariaDB.

Last run on a host 8 Nov 2026

Pinned at 261029 (released 29 Oct 2026) · the newest upstream release

In the upstream images: 1 CRITICAL · 26 HIGH with a published fix — per image

To try it: Docker, and the first start still downloads roughly 1 GB of classification models. See Try it locally.

To deploy it behind the proxy: Traefik running, a domain, and disk space for the library and the models.

On your own machine, with no proxy, domain or certificate. From apps/photoprism/:

Terminal window
cp .env.local.example .env.local
# The secrets ship empty; generate the ones this stack needs
{
echo "DB_PASSWORD=$(openssl rand -hex 32)"
echo "DB_ROOT_PASSWORD=$(openssl rand -hex 32)"
echo "ADMIN_PASSWORD=$(openssl rand -base64 24 | tr -d '\n')"
} >> .env.local
docker compose -f docker-compose.local.yml --env-file .env.local up -d

Then open http://localhost:2342. Stop it again with the same command and down.

From apps/photoprism/:

  1. Copy the environment file:

    Terminal window
    cp .env.example .env

    Set APP_TRAEFIK_HOST, TZ, PUID/PGID, SITE_TITLE, and ORIGINALS_PATH — the last one is where your photos live and it may point outside the stack directory.

  2. Generate the database secrets and mirror the password into .env. PhotoPrism has no _FILE form for its database password, so the value exists twice:

    Terminal window
    mkdir -p .secrets
    openssl rand -base64 32 | tr -d '\n' > .secrets/db_pwd.txt
    openssl rand -base64 32 | tr -d '\n' > .secrets/db_root_pwd.txt
    sed -i "s|^DB_PWD_INLINE=.*|DB_PWD_INLINE=$(cat .secrets/db_pwd.txt)|" .env
  3. Set the initial administrator password, which is read once at first start:

    Terminal window
    ADMIN_PWD_VAL=$(openssl rand -base64 24 | tr -d '\n')
    sed -i "s|^ADMIN_PASSWORD=.*|ADMIN_PASSWORD=${ADMIN_PWD_VAL}|" .env
    echo "Initial admin password: ${ADMIN_PWD_VAL}"

    Keep that value somewhere safe. After the first login, change the password in the interface and remove it from .env.

  4. Create the volumes:

    Terminal window
    mkdir -p volumes/database volumes/storage volumes/originals
  5. Start it. The first run downloads the classification models and takes several minutes:

    Terminal window
    docker compose up -d
    docker compose logs photoprism-app --follow

    Wait for server: listening on 0.0.0.0:2342.

  6. Open https://<APP_TRAEFIK_HOST> and log in with ADMIN_USER and the password from step 3.

Terminal window
docker compose ps # two services, healthy
curl -fsSI https://<APP_TRAEFIK_HOST>/api/v1/status # 200

Then put a few photos into the originals path and start an index run from the interface. Pictures appearing with thumbnails and detected content is the check — the server answers long before indexing works.

The indexer can reach several gigabytes while processing large RAW files or video. photoprism-app runs with a 2 GB memory limit and no swap (memswap_limit equals the limit). A ceiling the indexer touches ends as an out-of-memory kill mid-run, so a library of large RAW files or video needs a higher limit: raise memory and memswap_limit together in docker-compose.yml and run docker compose up -d.

PhotoPrism tags releases by date — 260601 is 1 June 2026. There is no semantic version to read, so the release notes are the only way to know what a bump contains.

Upstream works around an io_uring issue by running MariaDB with seccomp and AppArmor unconfined. This stack keeps the defaults; the block is present in docker-compose.yml and commented out, to be enabled only if that crash appears.

On first run and on renewal, PhotoPrism posts to my.photoprism.app/v1/hello with a persistent client serial, the version, the operating system, the architecture and the CPU core count. The serial does not rotate, so the vendor can follow this installation over time.

There is no supported way to switch it off. hub.Disable() exists but is reachable only through the test configuration. Blocking it needs an egress rule on the host or the network.

Database MariaDB, container photoprism-db, database photoprism, user photoprism
Password .secrets/db_pwd.txt
The library ORIGINALS_PATH — the photos, which may sit outside the stack directory
Also needed volumes/storage/sidecar — edits and metadata written back
Rebuildable the rest of volumes/storage — thumbnail cache, regenerated by an index run
Quiescing Not needed for the dump.

For borgmatic:

mariadb_databases:
- name: photoprism
container: photoprism-db
username: photoprism
password: "{credential file /srv/docker/apps/photoprism/.secrets/db_pwd.txt}"

Exclude the thumbnail cache subdirectory rather than all of volumes/storage. Most of that tree is cache, but the sidecar files in it carry edits and metadata that exist nowhere else.

Restore order: database, then originals and sidecars, then the application.

  1. Read the release notes — the tag is a date, so it is the only description of what changed.

  2. Back up the database, the originals and the sidecar directory together.

  3. Raise APP_TAG in .env, then:

    Terminal window
    docker compose pull && docker compose up -d
    docker compose logs photoprism-app --follow
  4. Open the library and run an index. A schema change surfaces during indexing rather than at start.

The indexer stops partway through a large file. Out of memory against the 2 GB limit — docker inspect photoprism-app --format '{{.State.OOMKilled}}' answers true. Raise the limit as under Memory during indexing.

Login fails with the password from .env. ADMIN_PASSWORD is read only at first start. If the stack has run before, the password is whatever is stored in the database.

MariaDB crashes on start. The io_uring case. Uncomment the seccomp and AppArmor block in docker-compose.yml.

Photos are visible but nothing is classified. The model download did not finish. Check docker compose logs photoprism-app from the first start.