This post details the journey of setting up PhotoPrism on an Openmediavault server with older hardware. It explores the decision to use Kubernetes for container management and the subsequent performance challenges encountered with a containerized MariaDB. The author shares a practical solution: deploying MariaDB via docker-compose for stability before integrating it with a modified PhotoPrism Kubernetes recipe. The result is a functional, self-hosted photo management system accessible on the local network.
Introduction
In today's digital age, managing a growing collection of photos can be a daunting task. I recently embarked on a journey to find a self-hosted solution that would allow me to organize, view, and share my photos seamlessly. I am aware that there are over hundred possible solutions to this problem, but my interest was in a fully local setup.
After exploring various options, I settled on PhotoPrism, an open-source photo management application that runs in a Docker container. In this blog post, I'll share my experience setting up and using PhotoPrism on my self-hosted server running Openmediavault. I know that Openmediavault provides a plugin for PhotoPrism, but I wanted to centralilly manage all my containers using the, now, recommended method of using Kubernetes.
Hardware limitations and background decisions
The server I use for hosting Openmediavault is an old machine with core hardware from 2010-2012. It has an Intel Xeon X5675 CPU, 8 GB of DDR3 RAM, an NVidia Quadro K4200 GPU, and various HDDs for storage. Since the motherboard is a capable, but outdated, ASUS P6T with X58 chipset, I added a PCIe SATA-3 controller card to get the most out of the drives connected.
Nevertheless, the hardware is quite limited by today's standards, especially in terms of I/O performance and power efficiency, but it runs in a house powered by solar panels in an area constantly radiated by the sun, so power consumption is not a big concern. Additionally, there are only two users who will be accessing the NAS, so performance is not a big concern either.
As mentioned earlier, I decided to standardize on using Kubernetes for container management because of the simplified way that services would be available via URL, thanks to the pre-installed cert-manager and Traefik (as explained in the official release, here). This setup allows me to easily manage and access my services without relying on individual container configurations.
Preparation
After having configured the Kubernetes cluster, I proceeded to prepare the environment for PhotoPrism which included setting up a MariaDB database. The choice of MariaDB was influenced by its compatibility with PhotoPrism and the fact that I would be also running Paperless-ngx on this server, which also uses MariaDB.
Initial setup of MariaDB
Initially, I spun up a MariaDB container using the community maintained recipe from openmediavault-k8s-recipes repository, hoping that it would be sufficient for my needs. However, I soon realized that because of my limited resources and lack of experience with Kubernetes, I was facing serious performance issues, with the PhotoPrism web interface reporting high rate of time outs:
2025-09-22 01:02:35 ERRO photo: dial tcp 10.43.105.200:3306: i/o timeout (update date fields)
...
2025-09-22 01:02:20 ERRO photo: dial tcp 10.43.105.200:3306: i/o timeout (update date fields)
...
2025-09-22 01:02:05 ERRO photo: dial tcp 10.43.105.200:3306: i/o timeout (update date fields)
...
2025-09-22 01:01:50 ERRO photo: dial tcp 10.43.105.200:3306: connect: connection refused (update date fields)Refined setup of MariaDB
I tried to tune MariaDB by adjusting the configuration parameters in the recipe, but I was not able to mitigate the issue, therefore I decided to switch approach and I decided to deploy MariaDB using the old openmediavault-compose docker plugin. Thefore, I first created a share for the database persistent data, then I created a docker-compose.yml file with the following content:
services:
mariadb:
image: mariadb:12
container_name: mariadb
restart: unless-stopped
environment:
MARIADB_ROOT_PASSWORD: <MY_VERY_STRONG_PASSWORD>
command:
- --innodb-buffer-pool-size=768M
- --transaction-isolation=READ-COMMITTED
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --max-connections=256
- --innodb-rollback-on-timeout=OFF
- --innodb-lock-wait-timeout=90
volumes:
- /srv/dev-disk-by-uuid-6164bcf5-7b4a-438a-904f-95904c60c180/mariadb:/var/lib/mysql
ports:
- "3306:3306"NOTE: All credit to the official openmediavault recipe for PhotoPrism, which includes the recommended MariaDB configuration parameters.
Finally I deployed the container using the docker-compose plugin interface and created the photoprism database and user:
sudo docker exec -it mariadb mariadb -u root -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 4
Server version: 12.0.2-MariaDB-ubu2404 mariadb.org binary distribution
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> CREATE DATABASE photoprism CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
Query OK, 1 row affected (0.000 sec)
MariaDB [(none)]> CREATE USER 'photoprism-app'@'%' IDENTIFIED BY '<ANOTHER_STRONG_PASSWORD>';
Query OK, 0 rows affected (0.021 sec)
MariaDB [(none)]> GRANT ALL PRIVILEGES ON photoprism.* TO 'photoprism-app'@'%;
Query OK, 0 rows affected (0.019 sec)
MariaDB [(none)]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.001 sec)
MariaDB [(none)]> EXIT;Now I was ready to install the mariadb-client package using sudo apt update && sudo apt install mariadb-client so that I could test the connection from the OMV Command Line Interface (CLI):
mysql -h 127.0.0.1 -P 3306 -u photoprism-app -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 6
Server version: 12.0.2-MariaDB-ubu2404 mariadb.org binary distribution
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| photoprism |
+--------------------+
2 rows in set (0.001 sec)
MariaDB [(none)]> exit;The connection was successful, therefore I could now concentrate on the PhotoPrism recipe knowing that anything not working would be my fault in modifying the recipe, and not the database's.
Installation of PhotoPrism
I used the official PhotoPrism recipe from the k8s repository as a starting point for my installation. I made several modifications because of my specific use case which I detail below.
I need to access the PhotoPrism web interface from the local network without using mDNS or pi-hole or similar services. Therefore, I modified the Service definition to use a NodePort type instead of ClusterIP, and I set the port to 32442.
Likewise I modified the ConfigMap section to set the PHOTOPRISM_DATABASE_SERVER to the IP address of the server where MariaDB is running.
The resulting recipe.yaml files has the following new content:
apiVersion: v1
kind: ConfigMap
metadata:
name: photoprism-db
namespace: photoprism-app
labels:
app.kubernetes.io/instance: photoprism
app.kubernetes.io/name: photoprism
data:
PHOTOPRISM_DATABASE_DRIVER: "mysql"
PHOTOPRISM_DATABASE_SERVER: "<MY_IP_ADDRESS>:3306"
PHOTOPRISM_DATABASE_NAME: "photoprism-app"
PHOTOPRISM_DATABASE_USER: "<ANOTHER_STRONG_PASSWORD>"
...
...
apiVersion: v1
kind: Service
metadata:
name: photoprism
namespace: photoprism-app
labels:
app.kubernetes.io/instance: photoprism
app.kubernetes.io/name: photoprism
spec:
type: NodePort
ports:
- port: 2342
protocol: TCP
targetPort: 2342
nodePort: 32442
selector:
app.kubernetes.io/instance: photoprism
app.kubernetes.io/name: photoprismThe recipe.yaml file I modified was saved directly on the OMV server in the location where the openmediavault-k8s-recipes repository is cloned, i.e. /var/lib/openmediavault/recipes/photoprism/recipe.yaml. Finally, I deployed the recipe using the OMV web interface and after about 5 minutes, the PhotoPrism pod was up and running.
Accessing PhotoPrism
I accessed the PhotoPrism web interface by opening a browser and navigating to:
- from local network:
http://<MY_IP_ADDRESS>:32442 - from outside network:
https://photoprism.<MY_FULLY_QUALIFIED_DOMAIN>:8443