Deploying Immich on Kubernetes with External PostgreSQL¶
This guide documents the deployment of Immich on a Kubernetes homelab cluster using an external PostgreSQL server.
Architecture¶
+----------------------+
| Nginx |
| Reverse Proxy |
+----------+-----------+
|
v
+----------------------+
| Kubernetes |
| Cluster |
| |
| +----------------+ |
| | Immich Server | |
| +----------------+ |
| | |
| +----------------+ |
| | Microservices | |
| +----------------+ |
| | |
| +----------------+ |
| | Machine | |
| | Learning | |
| +----------------+ |
| | |
| +----------------+ |
| | Valkey | |
| +----------------+ |
+----------+-----------+
|
10.42.0.0/16
|
v
+----------------------+
| PostgreSQL Server |
| 192.168.0.200 |
| |
| PostgreSQL 16 |
| pgvector 0.8.7 |
| VectorChord 0.5.3 |
| earthdistance |
+----------------------+
Environment¶
- Kubernetes running in KVM VMs
- Containerd
- Calico networking
- Kubernetes pod network:
10.42.0.0/16 - PostgreSQL server:
192.168.0.200 - PostgreSQL 16
- Immich namespace:
immich - Helm
local-pathStorageClass for initial testing
PostgreSQL is external to Kubernetes and is already used by other services.
1. Install PostgreSQL vector extensions¶
The PostgreSQL server uses PostgreSQL 16.
Install pgvector from PGDG. The version used for this deployment is:
postgresql-16-pgvector 0.8.7-1.pgdg24.04+1
Verify:
dpkg -l | grep pgvector
The package should provide:
/usr/lib/postgresql/16/lib/vector.so
2. Install VectorChord¶
Download VectorChord for PostgreSQL 16:
cd /tmp
wget https://github.com/tensorchord/VectorChord/releases/download/0.5.3/postgresql-16-vchord_0.5.3-1_amd64.deb
Install it:
sudo apt install /tmp/postgresql-16-vchord_0.5.3-1_amd64.deb
Verify:
dpkg -l | grep vchord
3. Configure VectorChord¶
Edit:
sudo nano /etc/postgresql/16/main/postgresql.conf
Ensure:
shared_preload_libraries = 'vchord.so'
If other libraries are already configured, preserve them.
Restart:
sudo systemctl restart postgresql
Verify:
sudo -u postgres psql -c "SHOW shared_preload_libraries;"
The output should include:
vchord.so
4. Create the Immich PostgreSQL user and database¶
Connect:
sudo -u postgres psql
Create the user:
CREATE USER immich WITH PASSWORD 'YOUR_PASSWORD';
Create the database:
CREATE DATABASE immich OWNER immich;
Connect:
\c immich
Create the extensions:
CREATE EXTENSION vector;
CREATE EXTENSION vchord CASCADE;
CREATE EXTENSION earthdistance CASCADE;
Verify:
SELECT extname, extversion
FROM pg_extension
WHERE extname IN ('vector', 'vchord', 'earthdistance')
ORDER BY extname;
Expected extensions:
earthdistance
vector
vchord
psql troubleshooting¶
If the prompt becomes:
postgres-#
instead of:
postgres=#
the previous SQL statement is incomplete.
Press:
Ctrl+C
Then enter each command separately.
Use \p to inspect the current query buffer:
\p
5. Configure PostgreSQL network access¶
The Kubernetes pod network is:
10.42.0.0/16
Find the PostgreSQL HBA file:
sudo -u postgres psql -c "SHOW hba_file;"
Edit:
sudo nano /etc/postgresql/16/main/pg_hba.conf
Add an Immich-specific rule:
# Immich from Kubernetes pods
host immich immich 10.42.0.0/16 scram-sha-256
Reload:
sudo systemctl reload postgresql
6. Verify PostgreSQL is listening¶
Check:
sudo -u postgres psql -c "SHOW listen_addresses;"
Because PostgreSQL already accepts remote OpenProject connections, avoid changing listen_addresses unless necessary.
If required, use:
listen_addresses = 'localhost,192.168.0.200'
Then restart:
sudo systemctl restart postgresql
7. Test PostgreSQL locally¶
Test the Immich account:
psql -h 127.0.0.1 -U immich -d immich
Then:
SELECT current_user, current_database();
Expected:
current_user | current_database
--------------+-----------------
immich | immich
Exit:
\q
8. Test PostgreSQL from Kubernetes¶
Start a temporary PostgreSQL client:
kubectl run pg-test \
--rm -it \
--restart=Never \
--image=postgres:16 \
-- bash
Inside the container:
export PGHOST=192.168.0.200
export PGPORT=5432
export PGUSER=immich
export PGDATABASE=immich
Connect:
psql -W
Enter the Immich database password.
Verify:
SELECT current_user, current_database(), inet_server_addr();
Expected:
current_user | current_database | inet_server_addr
--------------+------------------+-----------------
immich | immich | 192.168.0.200
Exit:
\q
Then:
exit
The temporary pod is removed automatically because of --rm.
9. Create the Immich namespace¶
kubectl create namespace immich
If it already exists, that is harmless.
Verify:
kubectl get namespace immich
10. Create the PostgreSQL Kubernetes Secret¶
The database password is stored in pass-homelab:
pass-homelab show immich/pg-password
Create the Secret:
kubectl -n immich create secret generic immich-postgres \
--from-literal=DB_PASSWORD="$(pass-homelab show immich/pg-password)"
For repeatable deployments, use the idempotent version:
kubectl -n immich create secret generic immich-postgres \
--from-literal=DB_PASSWORD="$(pass-homelab show immich/pg-password)" \
--dry-run=client -o yaml | kubectl apply -f -
Verify without displaying the password:
kubectl -n immich get secret immich-postgres
11. Create the initial Immich library PVC¶
For initial testing, use the existing local-path StorageClass.
Create immich-pvc.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: immich-library
namespace: immich
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 10Gi
Apply:
kubectl apply -f immich-pvc.yaml
Check:
kubectl -n immich get pvc
The PVC may initially show Pending because the StorageClass uses WaitForFirstConsumer.
Storage warning¶
This 10 GiB volume is for getting Immich running.
It should not be considered the final photo-storage architecture. Before uploading the real photo collection, migrate the library to storage with an appropriate resilience and backup strategy.
12. Configure Immich Helm values¶
Create:
nano immich-values.yaml
Use:
image:
tag: v3.2.0
env:
DB_HOSTNAME: "192.168.0.200"
DB_PORT: "5432"
DB_USERNAME: "immich"
DB_PASSWORD:
valueFrom:
secretKeyRef:
name: immich-postgres
key: DB_PASSWORD
DB_DATABASE_NAME: "immich"
DB_VECTOR_EXTENSION: "vectorchord"
immich:
persistence:
library:
existingClaim: immich-library
valkey:
enabled: true
The password is supplied through the Kubernetes Secret rather than stored directly in the values file.
Valkey is deployed inside Kubernetes.
13. Render the Helm chart¶
Render before installing:
helm template immich \
oci://ghcr.io/immich-app/immich-charts/immich \
--version 0.13.2 \
--namespace immich \
-f immich-values.yaml \
> immich-rendered.yaml
Inspect the database settings:
grep -E 'DB_HOSTNAME|DB_USERNAME|DB_DATABASE_NAME|DB_VECTOR_EXTENSION' \
immich-rendered.yaml
Check the PVC reference:
grep -A5 -B5 'immich-library' immich-rendered.yaml
14. Install Immich¶
helm upgrade --install immich \
oci://ghcr.io/immich-app/immich-charts/immich \
--version 0.13.2 \
--namespace immich \
-f immich-values.yaml
Check the release:
helm -n immich status immich
Check pods:
kubectl -n immich get pods
Check services:
kubectl -n immich get svc
15. Watch the deployment¶
kubectl -n immich get pods -w
Also:
kubectl -n immich get pvc
The library PVC should eventually become:
Bound
16. Check Immich logs¶
Once the server pod exists:
kubectl -n immich logs deployment/immich-server --tail=100
The key thing to verify is that Immich can connect to:
192.168.0.200:5432
and initialize/use the immich database.
Useful diagnostics:
kubectl -n immich get pods -o wide
kubectl -n immich get svc
kubectl -n immich get pvc
kubectl -n immich logs deployment/immich-server --tail=100
17. Deployment architecture¶
Once deployed:
Kubernetes Cluster
10.42.0.0/16
|
+---------------+----------------+
| | |
v v v
Immich Server Microservices Machine Learning
|
+--------------> Valkey
|
| TCP/5432
v
192.168.0.200
PostgreSQL 16
|
+-- immich
+-- vector
+-- vchord
+-- earthdistance
The PostgreSQL database remains independent of Kubernetes.
18. Reverse proxy¶
Configure the reverse proxy only after the Kubernetes deployment is healthy.
The target architecture is:
Internet / LAN
|
v
Nginx
|
v
Immich Kubernetes Service
|
v
Immich Server
Keep database, Kubernetes, Immich, storage, and reverse-proxy troubleshooting separate.
19. Production storage¶
The initial local-path library PVC is not the final storage solution.
Important Immich data includes:
- Original photos
- Videos
- Thumbnails
- Encoded media
- Immich database
- Machine-learning metadata
The final deployment should provide:
- Durable photo storage
- PostgreSQL backups
- Photo-library backups
- A recovery plan independent of the Kubernetes cluster
The PostgreSQL database should be backed up independently from the Kubernetes workloads.
20. Useful troubleshooting commands¶
Pods:
kubectl -n immich get pods -o wide
Services:
kubectl -n immich get svc
PVCs:
kubectl -n immich get pvc
Events:
kubectl -n immich get events --sort-by=.lastTimestamp
Helm:
helm -n immich status immich
Helm values:
helm -n immich get values immich
Server logs:
kubectl -n immich logs deployment/immich-server --tail=100
Follow server logs:
kubectl -n immich logs -f deployment/immich-server
21. Deployment checklist¶
- [x] PostgreSQL 16 installed
- [x] pgvector installed
- [x] VectorChord installed
- [x] VectorChord added to
shared_preload_libraries - [x] PostgreSQL restarted
- [x]
immichPostgreSQL user created - [x]
immichPostgreSQL database created - [x]
vectorextension created - [x]
vchordextension created - [x]
earthdistanceextension created - [x] Kubernetes pod CIDR identified as
10.42.0.0/16 - [x] PostgreSQL
pg_hba.confupdated - [x] PostgreSQL reloaded
- [x] Local PostgreSQL authentication tested
- [x] Kubernetes to PostgreSQL connectivity tested
- [x] Immich namespace created
- [x] PostgreSQL password stored in
pass-homelab - [x] Kubernetes PostgreSQL Secret created
- [x] Initial Immich library PVC created
- [x] Immich Helm values created
- [x] Helm chart rendered
- [ ] Immich Helm release deployed
- [ ] Immich pods healthy
- [ ] Immich UI verified
- [ ] Reverse proxy configured
- [ ] Final photo storage implemented
- [ ] PostgreSQL backups configured
- [ ] Photo-library backups configured
Conclusion¶
The deployment separates the Immich application layer from PostgreSQL.
Kubernetes manages:
- Immich Server
- Immich Microservices
- Machine Learning
- Valkey
- Application workloads
The dedicated PostgreSQL server manages:
- Immich's relational database
- pgvector
- VectorChord
- earthdistance
This keeps the database independent of the Kubernetes cluster while allowing Immich itself to be deployed and upgraded with Helm.
The initial local-path volume is suitable for validating the deployment, but the photo library should be migrated to a more resilient storage system before the deployment becomes the primary home for important photos.