Skip to content

Instantly share code, notes, and snippets.

@devsteppe9
Created December 11, 2025 02:22
Show Gist options
  • Select an option

  • Save devsteppe9/17b83f51ca05c5012632d32c5e42cea5 to your computer and use it in GitHub Desktop.

Select an option

Save devsteppe9/17b83f51ca05c5012632d32c5e42cea5 to your computer and use it in GitHub Desktop.
Kubernetes file for configuring self-hosted immich server
apiVersion: v1
kind: Namespace
metadata:
name: immich
---
kind: PersistentVolume
apiVersion: v1
metadata:
name: immich-db-pv
namespace: immich
labels:
app: immich-postgresql
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
hostPath:
path: /mnt/homelab/immich-db-data
persistentVolumeReclaimPolicy: Retain
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: immich-db-pvc
namespace: immich
labels:
app: immich
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi # Match or be less than the PV's capacity
volumeName: immich-db-pv # Bind explicitly to the PV created above
storageClassName: ''
---
kind: PersistentVolume
apiVersion: v1
metadata:
name: immich-library-pv
namespace: immich
labels:
app: immich
spec:
capacity:
storage: 500Gi # Adjust the storage size as needed
accessModes:
- ReadWriteMany
hostPath:
path: /mnt/homelab/immich-library-data
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: immich-library-pvc
namespace: immich
labels:
app: immich
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 500Gi # Match or be less than the PV's capacity
volumeName: immich-library-pv # Bind explicitly to the PV created above
storageClassName: ''
---
kind: ConfigMap
apiVersion: v1
metadata:
name: immich-env
namespace: immich
labels:
app: immich
data:
DB_DATABASE_NAME: "immich"
DB_HOSTNAME: "immich-database"
DB_USERNAME: "immich"
IMMICH_MACHINE_LEARNING_URL: "http://immich-machine-learning:3003"
REDIS_HOSTNAME: "redis-server.redis-server.svc.cluster.local"
REDIS_PORT: "6379"
REDIS_DBINDEX: "0"
REDIS_PASSWORD: ""
DISABLE_REVERSE_GEOCODING: "false"
REVERSE_GEOCODING_PRECISION: "2"
PUBLIC_LOGIN_PAGE_MESSAGE: ""
PUID: "0"
PGID: "0"
DB_PASSWORD: "password"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: immich-database
namespace: immich
spec:
replicas: 1
selector:
matchLabels:
app: immich-database
template:
metadata:
labels:
app: immich-database
spec:
containers:
- name: immich-postgres
image: "docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0"
imagePullPolicy: Always
ports:
- containerPort: 5432
env:
- name: POSTGRES_USER
valueFrom:
configMapKeyRef:
name: immich-env
key: DB_USERNAME
- name: POSTGRES_PASSWORD
valueFrom:
configMapKeyRef:
name: immich-env
key: DB_PASSWORD
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: immich-env
key: DB_DATABASE_NAME
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
subPath: postgres
resources: {}
volumes:
- name: pgdata
persistentVolumeClaim:
claimName: immich-db-pvc
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: immich-server
namespace: immich
labels:
app: immich-server
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: immich-server
template:
metadata:
labels:
app: immich-server
annotations:
k8s.v1.cni.cncf.io/networks: |
[{
"name": "multus-iot",
"namespace": "default",
"mac": "2e:f8:57:99:6e:31",
"ips": ["192.168.1.192/24"]
}]
spec:
securityContext:
fsGroup: 0
serviceAccountName: default
dnsPolicy: ClusterFirst
initContainers:
- name: postgresql-isready
image: "docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0"
imagePullPolicy: Always
env:
- name: POSTGRES_USER
valueFrom:
configMapKeyRef:
name: immich-env
key: DB_USERNAME
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: immich-env
key: DB_DATABASE_NAME
command:
- /bin/sh
- -c
- until pg_isready -U "${POSTGRES_USER}" -d "dbname=${POSTGRES_DB}"
-h immich-database -p 5432 ; do sleep 2 ; done
containers:
- name: immich-server
image: "ghcr.io/immich-app/immich-server:release"
imagePullPolicy: Always
securityContext:
runAsUser: 0
ports:
- containerPort: 3001
env:
- name: DB_PASSWORD
valueFrom:
configMapKeyRef:
name: immich-env
key: DB_PASSWORD
envFrom:
- configMapRef:
name: immich-env
optional: false
livenessProbe:
failureThreshold: 120
httpGet:
path: /server/ping
port: 2283
initialDelaySeconds: 10
periodSeconds: 120
timeoutSeconds: 1
readinessProbe:
failureThreshold: 120
httpGet:
path: /server/ping
port: 2283
initialDelaySeconds: 10
periodSeconds: 120
timeoutSeconds: 1
volumeMounts:
- name: library
mountPath: /usr/src/app/upload
subPath: library
volumes:
- name: library
persistentVolumeClaim:
claimName: immich-library-pvc
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: immich-machine-learning
namespace: immich
labels:
app: immich-machine-learning
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: immich-machine-learning
template:
metadata:
labels:
app: immich-machine-learning
spec:
securityContext:
fsGroup: 0
serviceAccountName: default
automountServiceAccountToken: true
dnsPolicy: ClusterFirst
enableServiceLinks: true
containers:
- name: immich-machine-learning
image: "ghcr.io/immich-app/immich-machine-learning:release"
imagePullPolicy: Always
ports:
- containerPort: 3003
env:
- name: DB_PASSWORD
valueFrom:
configMapKeyRef:
name: immich-env
key: DB_PASSWORD
- name: TRANSFORMERS_CACHE
value: /cache
envFrom:
- configMapRef:
name: immich-env
optional: false
livenessProbe:
failureThreshold: 3
httpGet:
path: /ping
port: 3003
initialDelaySeconds: 0
periodSeconds: 10
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /ping
port: 3003
initialDelaySeconds: 0
periodSeconds: 10
timeoutSeconds: 1
volumeMounts:
- name: cache
mountPath: /cache
resources: {}
volumes:
- name: cache
emptyDir: {}
---
kind: Service
apiVersion: v1
metadata:
name: immich-database
namespace: immich
labels:
app: immich-database
spec:
type: ClusterIP
selector:
app: immich-database
ports:
- name: tcp-postgresql
port: 5432
targetPort: 5432
---
kind: Service
apiVersion: v1
metadata:
name: immich-machine-learning
namespace: immich
labels:
app: immich-machine-learning
spec:
type: ClusterIP
selector:
app: immich-machine-learning
ports:
- port: 3003
targetPort: 3003
protocol: TCP
---
kind: Service
apiVersion: v1
metadata:
name: immich-server
namespace: immich
labels:
app: immich-server
spec:
type: LoadBalancer
selector:
app: immich-server
ports:
- port: 2283
targetPort: 2283
protocol: TCP
---
apiVersion: v1
kind: Namespace
metadata:
name: immich-restic-backup
---
apiVersion: v1
kind: Secret
metadata:
name: aws-creds
namespace: immich-restic-backup
type: Opaque
stringData:
AWS_ACCESS_KEY_ID: "YOUR_SECRET"
AWS_SECRET_ACCESS_KEY: "YOUR_SECRET"
---
apiVersion: v1
kind: ConfigMap
metadata:
name: restic-config
namespace: immich-restic-backup
data:
RESTIC_REPOSITORY: "s3:s3.us-east-1.amazonaws.com/r620-immich-cold-backup"
RESTIC_PASSWORD: "CHANGE_ME"
---
# --- INIT REPOSITORY (run one time only) ---
apiVersion: batch/v1
kind: Job
metadata:
name: restic-init
namespace: immich-restic-backup
annotations:
argocd.argoproj.io/sync-options: Force=true,Replace=true
spec:
template:
spec:
restartPolicy: Never
containers:
- name: restic-init
image: restic/restic:0.18.1
env:
- name: GOMAXPROCS
value: "32"
- name: RESTIC_PACK_SIZE
value: "128" # 128MB
envFrom:
- configMapRef:
name: restic-config
- secretRef:
name: aws-creds
volumeMounts:
- name: immich-data
mountPath: /data
command:
- /bin/sh
- -c
- |
echo "Initializing restic repository..."
restic -r ${RESTIC_REPOSITORY} init || echo "Repository already exists."
restic -r ${RESTIC_REPOSITORY} --verbose backup -o s3.storage-class=GLACIER -o s3.connections=16 /data --compression off --exclude "/data/thumbs" --exclude "/data/encoded-video" --skip-if-unchanged
volumes:
- name: immich-data
hostPath:
path: /mnt/homelab/immich-library-data/library
type: Directory
---
# --- BACKUP CRONJOB ---
apiVersion: batch/v1
kind: CronJob
metadata:
name: restic-backup-cron
namespace: immich-restic-backup
spec:
schedule: "0 3 * * *" # every day at 3am
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: restic-backup
image: restic/restic:0.18.1
envFrom:
- configMapRef:
name: restic-config
- secretRef:
name: aws-creds
volumeMounts:
- name: immich-data
mountPath: /data
command:
- /bin/sh
- -c
- |
echo "Running backup…"
restic -r ${RESTIC_REPOSITORY} --verbose backup -o s3.storage-class=GLACIER -o s3.connections=16 /data --compression off --exclude "/data/thumbs" --exclude "/data/encoded-video" --skip-if-unchanged
restic forget --keep-monthly 1 --prune
volumes:
- name: immich-data
hostPath:
path: /mnt/homelab/immich-library-data/library
type: Directory
---
apiVersion: v1
kind: Namespace
metadata:
name: redis-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-server
namespace: redis-server
labels:
app: redis-server
spec:
selector:
matchLabels:
app: redis-server
replicas: 1
template:
metadata:
labels:
app: redis-server
spec:
containers:
- image: redis:alpine
name: redis-server
ports:
- containerPort: 6379
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: redis-server
namespace: redis-server
labels:
app: redis-server
spec:
ports:
- port: 6379
selector:
app: redis-server
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment