From 9ec09e21044497c73ee8af0653a5ff838a67f5c8 Mon Sep 17 00:00:00 2001 From: Pim Zandbergen Date: Fri, 29 May 2026 23:46:50 +0200 Subject: [PATCH] Persist /etc/frepple in the Kubernetes manifest so runtime config survives pod restarts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The manifest persisted only the log directories, so /etc/frepple reset to the image defaults on every pod restart — regenerating the secret key (logging users out) and dropping anything the app writes to djangosettings.py at runtime (installed apps, scenario count, display settings). Mount /etc/frepple on a PVC, seeded from the image on first start by an init container. The init container mounts the volume at /mnt/config (so the image's own copy stays visible) and runs as root, since the freshly provisioned volume root is root-owned and cp -a must preserve the image's ownership; the copy is skipped on later starts, preserving runtime changes. This matches how docker-compose already persists the directory on a named volume. Document it, including the seed-once / reconcile-on-upgrade caveat. Refs #747. Co-Authored-By: Claude Opus 4.8 --- contrib/kubernetes/frepple-deployment.yaml | 37 +++++++++++++++++++ .../advanced/kubernetes.rst | 17 ++++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/contrib/kubernetes/frepple-deployment.yaml b/contrib/kubernetes/frepple-deployment.yaml index f8122a21f6..b5f210a397 100644 --- a/contrib/kubernetes/frepple-deployment.yaml +++ b/contrib/kubernetes/frepple-deployment.yaml @@ -23,6 +23,18 @@ spec: storage: 50Mi status: {} --- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: config-frepple +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 50Mi +status: {} +--- apiVersion: apps/v1 kind: Deployment metadata: @@ -42,6 +54,26 @@ spec: labels: app: frepple spec: + initContainers: + # Seed /etc/frepple from the image on the first start only. The volume is + # mounted here at /mnt/config (not /etc/frepple) so the image's own copy + # stays visible to copy from; on later starts the file already exists and + # the copy is skipped, preserving runtime changes to djangosettings.py. + # Runs as root: the freshly provisioned volume root is root-owned, so a + # non-root user can't populate it, and cp -a can't preserve the image's + # root-owned files unless it runs as root. This replicates the image's + # ownership/modes (/etc/frepple is group-writable by the frepple group). + - name: seed-config + image: ghcr.io/frepple/frepple-community:latest + securityContext: + runAsUser: 0 + command: + - sh + - -c + - test -f /mnt/config/djangosettings.py || cp -a /etc/frepple/. /mnt/config/ + volumeMounts: + - mountPath: /mnt/config + name: config-frepple containers: - env: - name: POSTGRES_HOST @@ -66,12 +98,17 @@ spec: cpu: 250m memory: "4294967296" volumeMounts: + - mountPath: /etc/frepple + name: config-frepple - mountPath: /var/log/apache2 name: log-apache - mountPath: /var/log/frepple name: log-frepple restartPolicy: Always volumes: + - name: config-frepple + persistentVolumeClaim: + claimName: config-frepple - name: log-apache persistentVolumeClaim: claimName: log-apache diff --git a/doc/installation-guide/advanced/kubernetes.rst b/doc/installation-guide/advanced/kubernetes.rst index b9c9f9b7b4..cf3f1d3b7e 100644 --- a/doc/installation-guide/advanced/kubernetes.rst +++ b/doc/installation-guide/advanced/kubernetes.rst @@ -19,7 +19,20 @@ The following resources are then defined in your cluster: - A postgresql service to store the frepple data. -- Persistent volumes to store the web server logs (50MB), the application logs (100MB) - and the postgresql data (1GB). +- Persistent volumes to store the web server logs (50MB), the application logs (100MB), + the frepple configuration in ``/etc/frepple`` (50MB) and the postgresql data (1GB). - A network policy to keep the connection between frepple and its postgres database private. + +frepple stores its runtime configuration in ``/etc/frepple/djangosettings.py``: the +Django secret key, the list of installed apps, the number of scenarios and some display +settings are all written to this file as you use the application. The deployment therefore +mounts ``/etc/frepple`` on a persistent volume, seeded from the image on the first start by +an init container, so that this configuration survives pod restarts - the same way the +docker-compose deployment persists it on a named volume. Without it the directory resets to +the image defaults on every restart: a new secret key is generated (invalidating sessions), +and installed apps and scenario settings are lost. + +The volume is seeded only once. After upgrading to a newer frepple image, any new default +settings shipped in the image are not copied over an existing ``djangosettings.py``; review +and apply such changes manually after an upgrade.