This guide describes how to run a PhotoPrism® Portal and its tenant instances on a Kubernetes cluster. The Portal terminates the shared domain, authenticates users as the cluster’s OpenID Provider, and provisions a database for each tenant.

A public Helm chart for the Portal is not available yet. The examples below use plain Kubernetes manifests for the Portal and the public photoprism-pro chart for tenants. If you need an orchestrated Portal chart for your platform, contact us.

Overview

A cluster has one Portal node and one or more instance (tenant) nodes:

  • The Portal runs the photoprism/portal image and is exposed over HTTPS at the shared domain.
  • Tenant instances run the photoprism/pro image and register with the Portal on first boot.
  • The Portal and tenants share one database server; the Portal provisions a database and user per tenant.

See Config Options for every variable referenced below, and Architecture for the cluster model.

1. Namespace & Cluster Secret

Create a namespace and a secret with the shared join token and database credentials. Keeping these in a Secret avoids hard-coding them into the Deployment.

kubectl create namespace photoprism

kubectl -n photoprism create secret generic portal-secrets \
  --from-literal=PHOTOPRISM_JOIN_TOKEN='<a-strong-shared-secret>' \
  --from-literal=PHOTOPRISM_ADMIN_PASSWORD='<portal-admin-password>' \
  --from-literal=PHOTOPRISM_DATABASE_PASSWORD='<db-password>' \
  --from-literal=PHOTOPRISM_DATABASE_PROVISION_DSN='root:<root-password>@tcp(mariadb:3306)/'

The join token must be at least 24 characters long. The provisioning DSN is the privileged account the Portal uses to create per-tenant databases; it is kept on the Portal only.

2. Deploy the Portal

The Portal needs a persistent volume for its application storage, a Service, and ingress that terminates TLS at the shared domain:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: portal-storage
  namespace: photoprism
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 15Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: portal
  namespace: photoprism
spec:
  replicas: 1
  selector:
    matchLabels: { app: portal }
  template:
    metadata:
      labels: { app: portal }
    spec:
      securityContext:
        runAsUser: 1000
        runAsGroup: 1000
        fsGroup: 1000
      containers:
        - name: portal
          image: photoprism/portal:latest
          ports:
            - containerPort: 2342
          env:
            - { name: PHOTOPRISM_NODE_ROLE,        value: "portal" }
            - { name: PHOTOPRISM_CLUSTER_DOMAIN,   value: "portal.example.com" }
            - { name: PHOTOPRISM_SITE_URL,         value: "https://portal.example.com/" }
            - { name: PHOTOPRISM_PORTAL_PROXY,     value: "true" }
            - { name: PHOTOPRISM_PORTAL_PROXY_URI, value: "/i/" }
            - { name: PHOTOPRISM_DATABASE_DRIVER,  value: "mysql" }
            - { name: PHOTOPRISM_DATABASE_SERVER,  value: "mariadb:3306" }
            - { name: PHOTOPRISM_DATABASE_NAME,    value: "photoprism_portal" }
            - { name: PHOTOPRISM_DATABASE_USER,    value: "portal" }
          envFrom:
            - secretRef: { name: portal-secrets }
          volumeMounts:
            - { name: storage, mountPath: /photoprism/storage }
      volumes:
        - name: storage
          persistentVolumeClaim: { claimName: portal-storage }
---
apiVersion: v1
kind: Service
metadata:
  name: portal
  namespace: photoprism
spec:
  selector: { app: portal }
  ports:
    - port: 2342
      targetPort: 2342

The Portal’s own database (PHOTOPRISM_DATABASE_NAME, here photoprism_portal) must already exist before the Portal starts. On first start, the Portal creates a superadmin account and is ready to register instances.

3. Shared-Domain Ingress

Expose the Portal over HTTPS at the shared domain. Because the Portal proxies tenants under /i/<name>/, a single hostname and certificate cover the whole cluster:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: portal
  namespace: photoprism
spec:
  rules:
    - host: portal.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: portal
                port: { number: 2342 }
  tls:
    - hosts: ["portal.example.com"]
      secretName: portal-tls

PhotoPrism keeps internal TLS disabled because TLS terminates at the ingress. For separate-hostname layouts (one host and certificate per instance) instead of shared-domain routing, see Portal & Clusters.

4. Join Tenant Instances

Tenants run a standard PhotoPrism instance configured to join the cluster. The public photoprism-pro chart registers an instance with a Portal when cluster integration is enabled:

helm repo add photoprism https://charts.photoprism.app/photoprism
helm repo update photoprism

helm upgrade --install media photoprism/photoprism-pro \
  --namespace photoprism \
  --set cluster.integration.enabled=true \
  --set cluster.integration.domain=portal.example.com \
  --set cluster.integration.portalURL=http://portal.photoprism.svc.cluster.local:2342/ \
  --set cluster.integration.joinToken=<the-same-shared-secret> \
  --set config.PHOTOPRISM_NODE_NAME=media \
  --set config.PHOTOPRISM_SITE_URL=https://portal.example.com/i/media/

On first start the instance registers with the Portal, receives its database credentials, and becomes reachable at https://portal.example.com/i/media/. Registration is idempotent and safe to repeat on every restart. Leave the tenant PHOTOPRISM_OIDC_URI, _CLIENT, and _SECRET empty — cluster OIDC derives them from the node credentials (see Config Options).

5. Storage

  • The Portal stores its application state on a writable PersistentVolumeClaim mounted at /photoprism/storage.
  • Each tenant needs its own storage PVC and, if it indexes a shared library, an originals volume (a PVC or an NFS-backed claim).
  • Use your cluster’s default StorageClass unless a workload requires a specific one. Size the database StatefulSet PVC for the combined tenant schemas.

Firewall & Networking

For the cluster-internal ports, outgoing-connection allowlist, and kernel-module requirements that apply to any PhotoPrism Kubernetes deployment, see Getting Started with Rancher and Kubernetes. For OpenShift specifics (Routes, SCC, Form View), see OpenShift.

PhotoPrism® Documentation

For more information on specific features, services and related resources, please refer to the other documentation available in our Knowledge Base and User Guide: