Photoprism


Photoprism Photoprism is a photo library management system that lets you manage all of your photos and had companion apps for your phone to also help you upload those pictures as well

Product: Photoprism
0 Install Type: Manifest Files
Container Image: Docker

Installation Details

While there are no official Kubernetes instrustions for Photoprims, we can adapt the Photoprism Pure Docker Install Instructions to guide us in creating the appropriate manifests.

Now let's create the files we'll need to configure Photoprims in Kubernetes

The following manifest files assume you will want to install this to a namespace named media, an nginx ingress named nginx, and Cert Manager configured to use the ACME provider Let's Encrypt. Please adjust for your particular needs.

00-media-namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: media
  labels:
    name: media

01-config.yaml

This will define the environment variables for your Photoprism installation. You should consult the Photoprism Configuration Docs for configuration options. I configure some general settings and database connections but there are many other options can also be configured.

I am currently using a central MariaDB database for my installation. You will need to adjust or remove these options as needed. Also, you will see that some more sensitive items are stored as a Secret rather than a ConfigMap.

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: photoprism-configs
  namespace: media
data:
  TZ: "America/New_York"
  PHOTOPRISM_AUTH_MODE: "password"               # authentication mode (public, password)
  PHOTOPRISM_SITE_URL: "https://your.photos.url/"  # server URL in the format "http(s)://domain.name(:port)/(path)"
  PHOTOPRISM_DISABLE_TLS: "true"                # disables HTTPS/TLS even if the site URL starts with https:// and a certificate is available
  PHOTOPRISM_DEFAULT_TLS: "false"                 # defaults to a self-signed HTTPS/TLS certificate if no other certificate is available
  PHOTOPRISM_ORIGINALS_LIMIT: "100000"               # 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: "false"                   # do not modify originals directory (reduced functionality)
  PHOTOPRISM_EXPERIMENTAL: "true"               # 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: "your.database.server:3306"     # MariaDB or MySQL database server (hostname:port)
  PHOTOPRISM_DATABASE_NAME: "photoprism"         # MariaDB or MySQL database schema name
  PHOTOPRISM_SITE_CAPTION: "My Photo Album"
  PHOTOPRISM_SITE_DESCRIPTION: ""                # meta site description
  PHOTOPRISM_SITE_AUTHOR: ""                     # meta site author
  ## Video Transcoding (https://docs.photoprism.app/getting-started/advanced/transcoding/):
  # PHOTOPRISM_FFMPEG_ENCODER: "software"        # H.264/AVC encoder (software, intel, nvidia, apple, raspberry, or vaapi)
  # PHOTOPRISM_FFMPEG_SIZE: "1920"               # video size limit in pixels (720-7680) (default: 3840)
  # PHOTOPRISM_FFMPEG_BITRATE: "32"              # video bitrate limit in Mbit/s (default: 50)
  ## Run/install on first startup (options: update https gpu tensorflow davfs clitools clean):
  # PHOTOPRISM_INIT: "https gpu tensorflow"

---
apiVersion: v1
kind: Secret
metadata:
  name: photoprism-secrets
  namespace: media
type: Opaque
stringData:
  PHOTOPRISM_DATABASE_USER: "photoprism"         # MariaDB or MySQL database user name
  PHOTOPRISM_DATABASE_PASSWORD: "YourSuperSecretPassword"       # MariaDB or MySQL database user password
  PHOTOPRISM_ADMIN_PASSWORD: "YourSuperSecretPassword"          # initial admin password (8-72 characters)
  PHOTOPRISM_ADMIN_USER: "admin"                 # admin login username

02-storage.yaml

Even though I'm using an NFS share on a NAS to store my media, Photoprism will need some local storage for it's own use. I defined a 250 MB config volume using Longhorn for this volume.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  namespace: media
  name: photprism-storage
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 10Gi

03-deploy.yaml

The Deployment brings together the configuration and storage with the container image.

In addition I'm using an NFS share named storage. You will probably need to alter this for your needs. Be sure to adjust the appropriate volume and volumeMount definitions from the YAML below.

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: photoprism
  namespace: media
  labels:
    app: photoprism
    app.kubernetes.io/name: photoprism
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: photoprism
  template:
    metadata:
      labels:
        app: photoprism
        app.kubernetes.io/name: photoprism
    spec:
      securityContext:
        runAsUser: 1000
        runAsGroup: 1000
      volumes:
        - name: photoprism-storage
          persistentVolumeClaim:
            claimName: photoprism-storage
        - name: photoprism-pictures
          nfs:
            server: your.nfs.server
            path: /path/to/pictures
        - name: photoprism-import
          nfs:
            server: your.nfs.server
            path: /path/to/importPictures
      containers:
        - name: photoprism
          image: photoprism/photoprism:latest
          imagePullPolicy: Always
          resources:
            requests: 
              cpu: 250m
              memory: 256Mi
            limits:
              cpu: "4"
              memory: 8Gi
          ports:
            - containerPort: 2342
          volumeMounts:
            - name: photoprism-storage
              mountPath: /photoprism/storage
            - name: photoprism-pictures
              mountPath: /photoprism/originals
            - name: photoprism-import
              mountPath: /photoprism/import
          envFrom:
            - configMapRef:
                name: photoprism-configs
            - secretRef:
                name: photoprism-secrets
          livenessProbe:
            httpGet:
              path: /
              port: 2342
            initialDelaySeconds: 20
            periodSeconds: 5

04-service.yaml

The service will help expose the pod for use. I leverage ClusterIP with an Ingress, but you could use a LoadBalancer type (with something like MetalLB to expose Photoprism on an IP outside of your cluster directly.

kind: Service
apiVersion: v1
metadata:
  name: photoprism-service
  namespace: media
spec:
  selector:
    app: photoprism
  ports:
  - protocol: TCP
    port: 2342
    targetPort: 2342
  type: ClusterIP

05-ingress.yaml

An Ingress is one way to expose your services and can allow you to use Cert Manager to create TLS certificates for your site as well. In the annotations: {} section

I set an annotation (nginx.ingress.kubernetes.io/proxy-body-size: "0") to disable Nginx size limitations for uploads

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: photoprism-ingress
  namespace: media
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
spec:
  ingressClassName: nginx
  rules:
    - host: your.photoprism.url
      http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: photoprism-service
              port:
                number: 2342
  tls:
    - hosts:
        - your.photoprism.url
      secretName: photoprism-ext-tls

build-photoprism.sh

Now that we have prepared our manifests we need to deploy them to the cluster with kubectl. I create shell scripts for all my deployments so I can quickly redeploy if I make any adjustments. The below script does assume you have configured kubectl properly already.

#!/bin/bash

kubectl apply -f 00-media-namespace.yaml \
              -f 01-config.yaml \
              -f 02-storage.yaml \
              -f 03-deploy.yaml \
              -f 04-service.yaml \
              -f 05-ingress.yaml

We can deploy the manifests for Photoprism to the Kubernetes cluster by executing the following:

chmod 755 build-photoprism.sh
./build-photoprism.sh

I keep all my manifests, scripts, and helm charts in a private git repository for version control and archival storage While it is certainly not required to deploy Photoprism, it has made my life a little easier.