feat(k8s/vikunja): deploy Vikunja with Authelia OIDC and restic backups

This commit is contained in:
2026-09-07 15:01:46 +03:00
parent 72fd37d82d
commit 93b9f6495f
15 changed files with 570 additions and 13 deletions
+4
View File
@@ -90,6 +90,10 @@ data:
icon: grocy.png
href: https://${GROCY_HOST}/
description: Chores and inventory tracker
- Vikunja:
icon: vikunja.png
href: https://${VIKUNJA_HOST}/
description: Task management
widgets.yaml: |
---
+144
View File
@@ -0,0 +1,144 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: vikunja-db-backup
namespace: vikunja
labels:
app: vikunja-backup
spec:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
metadata:
labels:
app: vikunja-backup
spec:
restartPolicy: OnFailure
initContainers:
- name: pg-dump
image: postgres:18
imagePullPolicy: IfNotPresent
env:
- name: PGHOST
value: vikunja-db
- name: PGUSER
valueFrom:
secretKeyRef:
name: vikunja-credentials
key: DB_USERNAME
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: vikunja-credentials
key: DB_PASSWORD
- name: PGDATABASE
valueFrom:
secretKeyRef:
name: vikunja-credentials
key: DB_DATABASE_NAME
command:
- sh
- -c
- pg_dump --clean --if-exists > /backup/dump.sql
volumeMounts:
- name: backup-tmp
mountPath: /backup
containers:
- name: resticprofile
image: creativeprojects/resticprofile:0.32.0
imagePullPolicy: IfNotPresent
command:
- sh
- -c
- |
resticprofile -c /secrets/profiles.yaml -n vikunja-db backup
resticprofile -c /secrets/profiles.yaml -n vikunja-db copy
env:
- name: B2_ACCOUNT_ID
valueFrom:
secretKeyRef:
name: vikunja-backup-config
key: B2_ACCOUNT_ID
- name: B2_ACCOUNT_KEY
valueFrom:
secretKeyRef:
name: vikunja-backup-config
key: B2_ACCOUNT_KEY
volumeMounts:
- name: secrets
mountPath: /secrets
readOnly: true
- name: backup-tmp
mountPath: /backup
volumes:
- name: secrets
secret:
secretName: vikunja-backup-config
- name: backup-tmp
emptyDir: {}
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: vikunja-files-backup
namespace: vikunja
labels:
app: vikunja-backup
spec:
schedule: "0 3 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
metadata:
labels:
app: vikunja-backup
spec:
restartPolicy: OnFailure
containers:
- name: resticprofile
image: creativeprojects/resticprofile:0.32.0
imagePullPolicy: IfNotPresent
command:
- sh
- -c
- |
resticprofile -c /secrets/profiles.yaml -n vikunja-files backup
resticprofile -c /secrets/profiles.yaml -n vikunja-files copy
env:
- name: B2_ACCOUNT_ID
valueFrom:
secretKeyRef:
name: vikunja-backup-config
key: B2_ACCOUNT_ID
- name: B2_ACCOUNT_KEY
valueFrom:
secretKeyRef:
name: vikunja-backup-config
key: B2_ACCOUNT_KEY
volumeMounts:
- name: secrets
mountPath: /secrets
readOnly: true
- name: files
mountPath: /files
readOnly: true
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 1Gi
volumes:
- name: secrets
secret:
secretName: vikunja-backup-config
- name: files
persistentVolumeClaim:
claimName: vikunja-files
+114
View File
@@ -0,0 +1,114 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: vikunja
namespace: vikunja
labels:
app: vikunja
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: vikunja
template:
metadata:
labels:
app: vikunja
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
# No fsGroup — the NFS subdir provisioner creates directories 0777, and an
# fsGroup chown over NFS is slow and can fail against Synology's root squash.
seccompProfile:
type: RuntimeDefault
containers:
- name: vikunja
image: vikunja/vikunja:2.6.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3456
name: http
protocol: TCP
env:
- name: VIKUNJA_SERVICE_PUBLICURL
value: https://${VIKUNJA_HOST}/
- name: VIKUNJA_SERVICE_TIMEZONE
value: Europe/Kyiv
- name: VIKUNJA_SERVICE_ENABLEREGISTRATION
value: "false"
- name: VIKUNJA_SERVICE_SECRET
valueFrom:
secretKeyRef:
name: vikunja-credentials
key: SERVICE_SECRET
- name: VIKUNJA_DATABASE_TYPE
value: postgres
- name: VIKUNJA_DATABASE_HOST
value: vikunja-db
- name: VIKUNJA_DATABASE_SSLMODE
value: disable
- name: VIKUNJA_DATABASE_USER
valueFrom:
secretKeyRef:
name: vikunja-credentials
key: DB_USERNAME
- name: VIKUNJA_DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: vikunja-credentials
key: DB_PASSWORD
- name: VIKUNJA_DATABASE_DATABASE
valueFrom:
secretKeyRef:
name: vikunja-credentials
key: DB_DATABASE_NAME
- name: VIKUNJA_FILES_BASEPATH
value: /app/vikunja/files
# Authentication is delegated to Authelia over OIDC — local accounts are off,
# so the login page redirects straight to Authelia (single provider).
- name: VIKUNJA_AUTH_LOCAL_ENABLED
value: "false"
- name: VIKUNJA_AUTH_OPENID_ENABLED
value: "true"
- name: VIKUNJA_AUTH_OPENID_PROVIDERS_AUTHELIA_NAME
value: Authelia
- name: VIKUNJA_AUTH_OPENID_PROVIDERS_AUTHELIA_AUTHURL
value: https://auth.${AUTHELIA_DOMAIN}
- name: VIKUNJA_AUTH_OPENID_PROVIDERS_AUTHELIA_CLIENTID
value: vikunja
- name: VIKUNJA_AUTH_OPENID_PROVIDERS_AUTHELIA_SCOPE
value: openid profile email
- name: VIKUNJA_AUTH_OPENID_PROVIDERS_AUTHELIA_CLIENTSECRET
valueFrom:
secretKeyRef:
name: vikunja-credentials
key: OIDC_CLIENT_SECRET
volumeMounts:
- name: files
mountPath: /app/vikunja/files
livenessProbe:
httpGet:
port: 3456
path: /health
initialDelaySeconds: 30
periodSeconds: 30
readinessProbe:
httpGet:
port: 3456
path: /health
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 512Mi
volumes:
- name: files
persistentVolumeClaim:
claimName: vikunja-files
+25
View File
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vikunja
namespace: vikunja
annotations:
cert-manager.io/cluster-issuer: letsencrypt
# No Authelia forward-auth middleware — Vikunja authenticates against Authelia
# natively over OIDC. A middleware here would break the OIDC callback and the API.
spec:
tls:
- hosts:
- ${VIKUNJA_HOST}
secretName: vikunja-tls
rules:
- host: ${VIKUNJA_HOST}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vikunja
port:
number: 3456
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: vikunja
+95
View File
@@ -0,0 +1,95 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: vikunja
spec:
podSelector: {}
policyTypes:
- Ingress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-controller
namespace: vikunja
spec:
podSelector:
matchLabels:
app: vikunja
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: traefik
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-app-to-db
namespace: vikunja
spec:
podSelector:
matchLabels:
app: vikunja-db
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: vikunja
ports:
- port: 5432
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backup-to-db
namespace: vikunja
spec:
podSelector:
matchLabels:
app: vikunja-db
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: vikunja-backup
ports:
- port: 5432
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backup-egress
namespace: vikunja
spec:
podSelector:
matchLabels:
app: vikunja-backup
policyTypes:
- Egress
egress:
- ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
- ports:
- port: 8888
protocol: TCP
- ports:
- port: 443
protocol: TCP
- ports:
- port: 5432
protocol: TCP
to:
- podSelector:
matchLabels:
app: vikunja-db
+12
View File
@@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: vikunja-files
namespace: vikunja
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-synology-ssd
resources:
requests:
storage: 10Gi
@@ -0,0 +1,26 @@
apiVersion: v1
kind: Secret
metadata:
name: vikunja-backup-config
namespace: vikunja
stringData:
profiles.yaml: ENC[AES256_GCM,data:F1LINzN/Mg4Qk5J1nHRNSeOptMDMNrlFmJ1hLqYuuUpwfC8wDjMcz6ckpAkBFwqmN9i6Yl+w8q/rmFl1/hOU6cE1j5naFd3ElAoxYoftxho3bj41qBs74OwgrisPFKkB1/uUzzl6GTh4fg0jWpDoZSNDWgRzpb/g+mw9Duo+EePP396sF7t1g282NPd3LHqBNF4esE18xjtfLvyR/m7qHJGEjLd1+UyNECcofcoUoEx2Pf7BIGyTdHBoJjqou5djp/z8n9MKjNrw/9vGbr/jew398xqfDuJw+Xa8M5iudbFddnYfDoBv5HdRCRwDpUmNJuLEYFBp3TO72vaTGLG0j7TWxx4OjtxLx7arekk4s8NIybe92mRsXvEJuEfbyShJ5gDrvJZnwItQRTi/uI4Gncp6DHVU9YF7KN8qjUmMFh7YwutuakqLNg/hb5zk+KgDcvSyms9dBr1b7DPhZNjxLMTdila+s8tcA4l3VScEu/NAE4oMmAIbZU28S4Y2Y/IF0XbPZxU9KIVRRux0bysaeOPHlGYEJyx0008bKaTFWOzRHMgEyLZIqMmDr9h8avzciuuUr5aV3IrImwVHs2YJi/L0SESyuNutFwqalPCX3EUg/GYMdHFLgUuUmCeoFKgpmYHheQEofPt8X/uTXjsys3CrJpqvNUHnAFhoxl2cuI3MULP7bFOSbRenJ/0neftm/M2vfGSSOt9yVMOPlpK277yxg2OUZdHX9z0AyBsDZ4HVJRDXa6/n9N4sls18TUTo0Vj29SrOBRYHpYQ1Cw3V32N3krKjdqxjqeScOx4yJcyG6yu6UTJ3Q6UXnNpoZdCUj7qsqf72y8SIA7QhuJtSyXdw/aUHg4L5Aovi15e277WRm8UFz8dVYj0P9NIU6yp3+yCvEyIgnsyk8IIfIL0LLJIG8dnpJ+w/WdYNiggOfmCbszEkywmYrbuxt/KfavmWsWjYOI+s8pFjgOag8AxCHG/HNQun7qxMQMSwToye7IIjlsna8Wd1H1YbVH2EtnsoVxBU07DVCeyIS5n1qVv7D8DN91escW5aMCFMXkMkEM+CPjBwFZTpMeCggxRW3NHu3vi7pN7MwAGSKNnf6nkNZxYB1r6YUx67GAkkwcjPoMdZ5EW6CIYHSKcc8xJ2/yLR0Yffjjf0S/veIaj5RZW79qIE77WCgizjfA0qw0XlQjBpBkLd88rXe6O5lkfHlUxNcXOwh+GYDud2Hl4i5Lq3pmgWlC8+yGow/5SxRx3XEw==,iv:fnSZAS8b6UaHxfrXDK5U+DB2aeVP6bay8uMeAQcz0q8=,tag:XfJpr5JlwEV5vJsUBFBM1w==,type:str]
restic-password-nas: ENC[AES256_GCM,data:QBpRS2bHav6zYn509C5T0T3mlHPYW0H7KkxubzbPwDJcOE3spZYCzW0qbSOSc4yh1aeTcd71VyuUQfG3UjRAHw==,iv:tveOXU2g04wGLtxBuBzmYFywXmG3dUE+6jlrIFox62w=,tag:2ldefgyqtDV/8KR3ebzRHQ==,type:str]
restic-password-b2: ENC[AES256_GCM,data:MdaPu+NM/Xn9KK6gd3P7gc/vDi9cOwh4aJ24M5TZU/DAoUGeGkB1ztUzcpBd/rLJgATBSDkGQHjpMHvnwtThOO4=,iv:OgzNbdsjQoniBWa5+0bC3NoxyX8sCK/KuhIDqaPuhhw=,tag:GZYjJCpwIHIRBVlhxvpH0Q==,type:str]
B2_ACCOUNT_ID: ENC[AES256_GCM,data:It7pzn642LA3YlaUCsimXJQSEt76JhDNaA==,iv:ESTTD9OeE3KBcqz5N4M1dxGPKk/8RDu4EdQfjAbOWys=,tag:fml43DJboUNVvsE9t+af4A==,type:str]
B2_ACCOUNT_KEY: ENC[AES256_GCM,data:3avk03/e6oX6SakG0f2DNEjUC5qU5axOHjGLuxa/cg==,iv:+uTPW3qFocGtUvkf7V3/gaI0dif6AxuUfyGWlN+1zII=,tag:QtlBxGquvZnV6zejdiPDmA==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrdG84VTMyZUhaVkpEQnJS
anU0VEs0U0l2WFF6K2hQKzVWQy91dkdOMTFNClI0VjQ3WkhjN2FsTUxwbEVYRjUr
QkxIT0JOWWZaUTl5eWM1TFpBM2NRODQKLS0tIGZRNXVlcU1pU0h0N1p6UWdMRDhV
NHFaS3pkTzlQQllzR0ZzMFU0N2hQV28KvFlkIKTi6p2EnGtq3NDYr2OKiSuLV8pM
FpQ54FBj7Qe6AEn6vWAQegRyMWAnOBFLR3q4mf+I4N7uaNh7FyZwQw==
-----END AGE ENCRYPTED FILE-----
recipient: age1zffnskvuezntkk703a0pyxsd5m8vx2hm33dr47wdfy8mn4fdw4sqgw0jgc
encrypted_regex: ^(data|stringData|email)$
lastmodified: "2026-09-07T12:01:37Z"
mac: ENC[AES256_GCM,data:G57xdrKJAFULcdRxXYuDdlztZRRLJfByYsEn9QZ/QEklFu3HcqoR8jgBcDF4d33LuyUFjayAg9w/vheTHK5TChaUMMsDj9AVDt1cIK4ZAnuLgWhF2kloxQqGY2F0xSJvIXoDyC+lwV+m+fGyeLDdGbboeheKEZJdCkHU/Pu/bLo=,iv:MPCYY2lUIZqjhFYfcHHau7mMWvYh1d1CLyBkha96oyM=,tag:xoqE5txjs5aoWtqJKcFNWw==,type:str]
version: 3.13.3
+26
View File
@@ -0,0 +1,26 @@
apiVersion: v1
kind: Secret
metadata:
name: vikunja-credentials
namespace: vikunja
stringData:
DB_USERNAME: ENC[AES256_GCM,data:v5ZLlBBCYw==,iv:FEAQixJxjQESAm44z9AubpzSv6VPKSQxpGkoYIxfKbk=,tag:e97BlPYRhZg8UweFyeaf3w==,type:str]
DB_PASSWORD: ENC[AES256_GCM,data:J90Xco62vI8a1KFPBjJNJV1ybisNX+D4LfPebTiXO6c=,iv:j/XY/1K87lfKRCNUSBF0wUH8jx1qlYIHuVjDjh6ia00=,tag:g7yoGK6W3ACbMo/VqYWSuQ==,type:str]
DB_DATABASE_NAME: ENC[AES256_GCM,data:ySn5r6E0Lw==,iv:lkZUN87lavIFzn/4VMpxzQY813gEmqp5zppoiHWQIE0=,tag:roKDQsDlt8qdQ6LUcNH6oQ==,type:str]
SERVICE_SECRET: ENC[AES256_GCM,data:NMliN4ctgGzSUQZ1lxdlis+7oEuI9Wo7AgR/Arad+Rwo3IYnz1dHcfxfozwz4nwO/8NMxfx91KHHKgpKL732Rg==,iv:8RRsKOjWJBDPIbMaiqjSDt78z4uY7RmDW9M9s7vEGzk=,tag:8gEbiZI1SxSpliNf5IYnKg==,type:str]
OIDC_CLIENT_SECRET: ENC[AES256_GCM,data:qg950MtKcwfCbVBGimxgQBSv11jK0SwFsTQAJEbmeINsIDUGGaaSdNNAcB//ccSBSw5SVgJQ/JTq67ZVz/6KjQ==,iv:HriNEfVMiGTA/y6ItBORtQ1hs/G7bYteqtqtx3HWYR8=,tag:kOoLNgD+ZC+nlbOhIqvzsg==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWTGtJSmo4eVBOU3pvdGZT
U1JwYjhaUm41NzBERzF6ek1TWStGSEZHK2pnCjU1RkdWVEVaeXhXc2tKVDlrRmgv
Kzd1N2dXTHRwdTlBNmdpMjdiTFRLSGsKLS0tIGp3cFh4a1YrRVBYamRNbGZxdXNP
c1ZMclQvcHpJR2N0MHNyVDc5VU13NjAKHLYo/UVPtAUydGDjk0cVyrN+m2rMg8AR
alZw1JdIch/Fgb9MPHmDDJwRU1aX8FEedz+pWE9p/UJjh0xIpgy2HQ==
-----END AGE ENCRYPTED FILE-----
recipient: age1zffnskvuezntkk703a0pyxsd5m8vx2hm33dr47wdfy8mn4fdw4sqgw0jgc
encrypted_regex: ^(data|stringData|email)$
lastmodified: "2026-09-07T11:46:05Z"
mac: ENC[AES256_GCM,data:VSSzkGzEAfo3G6GcG28aQaxG/ZFB0SYILd9JRh8MRCGShWxFaQK/6lSV341xhFFQpOHr87lGC4pIFUVMBZ81Iu2C+QAVSQN9HFEN7R6o2cizNrIycaGaoHB7YenNpJ63EBeCd4E32hXXEXOs6nvRwVlAkaNitMhDXp+myjHU/Tc=,iv:cG2JXmcHsY7HA56FTHzrJj+5XpZW75ynSlNXtV4zHjg=,tag:XYqsGpBBFiInSk5CXEubuQ==,type:str]
version: 3.13.3
+12
View File
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: vikunja-db
namespace: vikunja
spec:
clusterIP: None
selector:
app: vikunja-db
ports:
- port: 5432
targetPort: 5432
+12
View File
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: vikunja
namespace: vikunja
spec:
selector:
app: vikunja
ports:
- port: 3456
targetPort: 3456
name: http
@@ -0,0 +1,80 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: vikunja-db
namespace: vikunja
labels:
app: vikunja-db
spec:
serviceName: vikunja-db
replicas: 1
selector:
matchLabels:
app: vikunja-db
template:
metadata:
labels:
app: vikunja-db
spec:
securityContext:
runAsUser: 999
runAsGroup: 999
fsGroup: 999
containers:
- name: postgres
image: postgres:18
imagePullPolicy: IfNotPresent
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: vikunja-credentials
key: DB_USERNAME
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: vikunja-credentials
key: DB_PASSWORD
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
name: vikunja-credentials
key: DB_DATABASE_NAME
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
startupProbe:
tcpSocket:
port: 5432
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 30
livenessProbe:
tcpSocket:
port: 5432
periodSeconds: 30
failureThreshold: 5
readinessProbe:
tcpSocket:
port: 5432
periodSeconds: 10
failureThreshold: 3
resources:
requests:
cpu: 50m
memory: 256Mi
limits:
memory: 1Gi
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: nfs-synology-ssd
resources:
requests:
storage: 5Gi