Google photos but local? Self hosting Photoprism

Published by Oliver on

In the age of digital photography we all amass a lot of pictures. The question is how to store them? Photoprism is a great self-hosted alternative to cloud tools like Google photos. I recently added it to my own server. Here is how to do that.

Managing photo libraries

Today, and for the last couple of years, pretty much everyone owned a smartphone capable of taking pictures – often fully replacing a professional camera. For me – as for many other people – this has lead to a huge amount of photos taken and stored, until now without much structure.

My own home server has a network share that contains a lot of photos from family and friends taken over the years and only roughly sorted by year and sometimes occasion in different folders. Especially in recent years the amount of such pictures has ever increased though, leading to hundreds of pictures per trip. Moving through them is a slow process where you need to connect to the VPN, wait for the images to load and then check each and every one just to find the handful of really good ones.

Even though the process is tedious I never liked using online tools like the admittedly great Google Photos just because I don’t want to share all of this very personal data. Monthly payments and vendor lock-in is not great either. So I was looking for a local alternative and found Photoprism.

What can Photoprism do?

Photoprism is a self hostable software that can manage all your photos and provide an experience very similar to powerful cloud alternatives. Not only can you upload many different types of pictures and videos and view them everywhere quickly in a web client: you can also add tags or group them in albums without any data duplication.

Beyond that it also has a couple of really cool features like displaying your images on a world map (based on the meta data), recognizing faces on the images to show you all other images of the same person and trying to automatically classify by labels (I saw bear, bird, baby, bridge, bus & more in my library).

map view in photoprism showing where images were taken
The map view shows you where you pictures were taken (some demo data from me)

Very important for me: it does all these things locally and can work in a read-only mode with any existing folder structure and without changing it. Also maybe best of all: the community edition is free.

Hosting Photoprism with docker-compose

As with all the other software I am hosting this on my home server using Docker and docker-compose for a simple setup. The images are stored on my ZFS pool and made available to the local network as samba shares. The data needed for the container will be in another folder on the same pool.

The whole docker-compose setup is based on the official documentation adapted to fit my whole setup. I kept most of the comments but updates some things. First it does include two networks: one for only the Photoprism and database container and the second one I used to connect to Traefik, the reverse proxy I use to make the service publicly available. You can find the always up-to-date code on my Github repository.

version: '3.5'

networks:
  traefik_proxy:
    external:
      name: traefik_proxy
  photo_network:
    driver: bridge

services:
  photoprism:
    image: photoprism/photoprism:latest
    restart: unless-stopped
    stop_grace_period: 10s
    depends_on:
      - mariadb
    security_opt:
      - seccomp:unconfined
      - apparmor:unconfined
    ports:
      - "2342:2342"
    environment:
      PHOTOPRISM_ADMIN_USER: "admin"                 # admin login username
      PHOTOPRISM_ADMIN_PASSWORD: "insecure"          # initial admin password (8-72 characters)
      PHOTOPRISM_AUTH_MODE: "password"               # authentication mode (public, password)
      #PHOTOPRISM_SITE_URL: "http://images.${DOMAINNAME}/"  # server URL in the format "http(s)://domain.name(:port)/(path)"
      PHOTOPRISM_ORIGINALS_LIMIT: 5000               # file size limit for originals in MB (increase for high-res video)
      PHOTOPRISM_HTTP_COMPRESSION: "gzip"            # improves transfer speed and bandwidth utilization (none or gzip)
      PHOTOPRISM_LOG_LEVEL: "info"                   # log level: trace, debug, info, warning, error, fatal, or panic
      PHOTOPRISM_READONLY: "true"                   # do not modify originals directory (reduced functionality)
      PHOTOPRISM_EXPERIMENTAL: "false"               # enables experimental features
      PHOTOPRISM_DISABLE_CHOWN: "false"              # disables updating storage permissions via chmod and chown on startup
      PHOTOPRISM_DISABLE_WEBDAV: "false"             # disables built-in WebDAV server
      PHOTOPRISM_DISABLE_SETTINGS: "false"           # disables settings UI and API
      PHOTOPRISM_DISABLE_TENSORFLOW: "false"         # disables all features depending on TensorFlow
      PHOTOPRISM_DISABLE_FACES: "false"              # disables face detection and recognition (requires TensorFlow)
      PHOTOPRISM_DISABLE_CLASSIFICATION: "false"     # disables image classification (requires TensorFlow)
      PHOTOPRISM_DISABLE_VECTORS: "false"            # disables vector graphics support
      PHOTOPRISM_DISABLE_RAW: "false"                # disables indexing and conversion of RAW images
      PHOTOPRISM_RAW_PRESETS: "false"                # enables applying user presets when converting RAW images (reduces performance)
      PHOTOPRISM_JPEG_QUALITY: 85                    # a higher value increases the quality and file size of JPEG images and thumbnails (25-100)
      PHOTOPRISM_DETECT_NSFW: "false"                # automatically flags photos as private that MAY be offensive (requires TensorFlow)
      PHOTOPRISM_UPLOAD_NSFW: "true"                 # allows uploads that MAY be offensive (no effect without TensorFlow)
      # PHOTOPRISM_DATABASE_DRIVER: "sqlite"         # SQLite is an embedded database that doesn't require a server
      PHOTOPRISM_DATABASE_DRIVER: "mysql"            # use MariaDB 10.5+ or MySQL 8+ instead of SQLite for improved performance
      PHOTOPRISM_DATABASE_SERVER: "mariadb:3306"     # MariaDB or MySQL database server (hostname:port)
      PHOTOPRISM_DATABASE_NAME: ${PHOTOPRISM_DATABASE_DB}        # MariaDB or MySQL database schema name
      PHOTOPRISM_DATABASE_USER: ${PHOTOPRISM_DATABASE_USER}         # MariaDB or MySQL database user name
      PHOTOPRISM_DATABASE_PASSWORD: ${PHOTOPRISM_DATABASE_PASSWORD}       # MariaDB or MySQL database user password
      PHOTOPRISM_SITE_CAPTION: "My Photos"
      PHOTOPRISM_SITE_DESCRIPTION: "Images & Memories"                # meta site description
      PHOTOPRISM_SITE_AUTHOR: "Oliver"                     # meta site author
      ## Run/install on first startup (options: update https gpu tensorflow davfs clitools clean):
      # PHOTOPRISM_INIT: "https gpu tensorflow"
      ## Hardware Video Transcoding:
      # PHOTOPRISM_FFMPEG_ENCODER: "software"        # FFmpeg encoder ("software", "intel", "nvidia", "apple", "raspberry")
      # PHOTOPRISM_FFMPEG_BITRATE: "32"              # FFmpeg encoding bitrate limit in Mbit/s (default: 50)
      # PHOTOPRISM_UID: ${PUID}
      # PHOTOPRISM_GID: ${PGID}
    working_dir: "/photoprism" # do not change or remove
    volumes:
      - "${DATADIR}/photoprism/storage:/photoprism/storage" # *Writable* storage folder for cache, database, and sidecar files
      - "/dataPool/pathToYour/pictures:/photoprism/originals:ro" # read only access to original images
    labels:
        - "traefik.enable=false"
        - "traefik.backend=photoprism"
        - "traefik.frontend.rule=Host:images.${DOMAINNAME}"
        - "traefik.port=2342"
        - "traefik.docker.network=traefik_proxy"
    networks:
        - default
        - traefik_proxy
        - photo_network

  mariadb:
    image: mariadb:10.11
    restart: unless-stopped
    stop_grace_period: 5s
    security_opt: # see https://github.com/MariaDB/mariadb-docker/issues/434#issuecomment-1136151239
      - seccomp:unconfined
      - apparmor:unconfined
    command: mariadbd --innodb-buffer-pool-size=512M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
    volumes:
      - "${DATADIR}/photoprism/db:/var/lib/mysql"
    environment:
      MARIADB_AUTO_UPGRADE: "1"
      MARIADB_INITDB_SKIP_TZINFO: "1"
      MARIADB_DATABASE: ${PHOTOPRISM_DATABASE_DB}
      MARIADB_USER: ${PHOTOPRISM_DATABASE_USER}
      MARIADB_PASSWORD: ${PHOTOPRISM_DATABASE_PASSWORD}
      MARIADB_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    networks:
      - photo_network

I decided to also make it available on the local network via port forwarding on port 2342 and left most of the other basic settings the same. It is important to note that you need a couple of additional lines in your .env file to store the database user and password like:

# photoprism
PHOTOPRISM_DATABASE_DB=photoprism
PHOTOPRISM_DATABASE_USER=photoprism
PHOTOPRISM_DATABASE_PASSWORD=someGeneratedPassword

The next crucial part is the volumes setup. You need to have the storage path point to some free storage on your setup so that Photoprism can store things like configurations, thumbnails and more. You also have to map the internal /photoprism/originals to some place you store your existing images if you want to important them. I made sure here to add a :ro to mount this folder in read only mode to make I am not deleting any of these files by accident.

Finally you can set a lot of environment variable to control Photoprisms behaviour. I left the official comments for most of them but changed PHOTOPRISM_READONLY to true and set the PHOTOPRISM_SITE_URL to the URL I am using to expose my service like: https://images.mydomain.de. The same domain along with some other settings is used in the labels to tell Traefik to expose this service (you just need to change traefik.enable to true).

Running Photoprism

Once all of this is done and you created the local ${DATADIR}/photoprism folder that will be used to save the applications (and DBs) data you can start by running docker-compose -f photoprism/photoprism.yml up -d. The first run will take some time as it applies database migrations and installs a couple of things via the PHOTOPRISM_INIT parameter but once it is done you can reach it at port 2342 or via the external images.${DOMAINNAME}.

I suggest not exposing it for the first startup as you will be using the default admin/insecure set via the environment variables. Change these on the first login via the account settings page (settings – account)! Once logged in go to the library tab and click the start button to scan all your existing images. This might take quite a while depending on the size of your library, but images will start showing up soon.

If you run into issues at the scan step then look into the mounting path you used for the originals folder and you might want to change PHOTOPRISM_UID/GID parameters if those belong to a different user.

Converting videos and more with cron jobs

After adding pictures and videos from my latest vacation to my data storage I noticed several things: first they are not added automatically. You need to tell Photoprism to re-index the storage to add new media. This can be done via the library tab in the web UI.

Even after adding them I noticed that many of the videos would actually not play. Photoprism can actually fix this itself by converting them into a format that browsers support but it only starts this process once you try to watch this video. As this can take a long time depending on the video I started looking for a solution to do this automatically.

Again the awesome documentation of Photoprism actually helped though: photoprism index --cleanup will discover new and updated files and photoprism convert will do the conversions. Of course you can manually run this in the docker containers with docker-compose exec but in the example docker-compose file Photoprism suggests a better cron job based alternative.

Using ofelia you can easily define cron type jobs (things that should automatically run regularly) on top of docker containers. To do this all I did is add a container_name: photoprism to my main container to have a reliable name and then add the ofelia container plus a configuration file like:

ofelia: # docker job runner
    restart: unless-stopped
    image: mcuadros/ofelia:latest
    container_name: ofelia
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./jobs.ini:/etc/ofelia/config.ini"
    networks:
      - photo_network

plus a configuration file called jobs.ini

// config file called jobs.ini
[job-exec "photoprism index"]
schedule = 0 0 1 * * *
container = photoprism
command =  photoprism index --cleanup
no-overlap = true

# Uncomment to automatically import every 2 hours:
#
# [job-exec "photoprism import"]
# schedule = @every 2h
# container = photoprism
# command =  photoprism import /photoprism/import
# no-overlap = true

[job-exec "photoprism convert"]
schedule = @daily # runs at midnight
container = photoprism
command =  photoprism convert
no-overlap = true

[job-exec "photoprism backup"]
schedule = @daily # runs at midnight
container = photoprism
command =  photoprism backup -a -i -f /photoprism/storage/index-backup.sql
no-overlap = true

This will reindex all my files at 1 AM and convert files as well as back up the DB every night at 12 AM. Now everything is done automatically and so far has been working great.

What is not so great about Photoprism?

I really can’t complain: the Photoprism software is free, self hostable and comes with a set of awesome features. The web interface is neat and super snappy even via a limited internet connection. The setup process was pretty simple and worked well for me.

I did run into one strange decision though: while nearly all of the cool features are available on free community version for some reason the multi-user support is very limited. You can create additional users apparently, but only via the command line, not the UI. This is not really a problem, a quick

docker compose exec photoprism photoprism users add -r user -p mysecret -n "Bob" bob

should do the trick and I am totally fine with them hiding some quality of life features. What I noticed only after setting everything up is that they also restrict the user types. As a free user you can only create other admin users. As the admin for my family I wanted to set up limited users for them but that’s simply not possible. This seems like a very weird restriction to me.

Update: After publishing this article I was actually contacted by the developer(s) of Photoprism. The explanation for this behavior is simple and actually really reasonable: currently some of the user related features are restricted to paying customers (mostly companies I guess) as it was developed for them. A simpler solution for private users might follow in the future.

I was offered to test the full set of features and it even includes a very neat web UI for managing users, roles and sessions. With this last point clear now I can fully recommend Photoprism.

Photoprism user management UI

Overall this tool is a great alternative to cloud based solutions though and so far I can heavily recommend it if you have the skills to run Docker containers securely.

Categories: Software