Andrew Mercer
on this page

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-path StorageClass 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:

  1. Durable photo storage
  2. PostgreSQL backups
  3. Photo-library backups
  4. 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] immich PostgreSQL user created
  • [x] immich PostgreSQL database created
  • [x] vector extension created
  • [x] vchord extension created
  • [x] earthdistance extension created
  • [x] Kubernetes pod CIDR identified as 10.42.0.0/16
  • [x] PostgreSQL pg_hba.conf updated
  • [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.