How to switch postgres from bitnami image to official image without helm?

In a A12 full stack deployment I need to switch the postgres stateful set from the bitnami image to the official docker image. The deployment is not managed using helm.

When trying to do this I basically need to change the bitnami based stateful set (based on A12 helm chart, stripped down here)

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: a12-infrastructure-postgresql
  namespace: abrieg
spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain
    whenScaled: Retain
  template:
    metadata:
      name: a12-infrastructure-postgresql
    spec:
      containers:
        - env:
            - name: POSTGRESQL_PORT_NUMBER
              value: '5432'
            - name: POSTGRESQL_VOLUME_DIR
              value: /bitnami/postgresql
            - name: PGDATA
              value: /bitnami/postgresql/data
            - name: POSTGRES_USER
              value: postgres-custom-user
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  key: postgresql-custom-user-password
                  name: a12-secrets-postgresql
            - name: POSTGRES_POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  key: postgresql-password
                  name: a12-secrets-postgresql
            - name: POSTGRESQL_SHARED_PRELOAD_LIBRARIES
              value: pgaudit
          image: bitnamilegacy/postgresql:16.3.0-debian-12-r8
          imagePullPolicy: IfNotPresent
          name: postgresql
          ports:
            - containerPort: 5432
              name: tcp-postgresql
              protocol: TCP
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
            privileged: false
            readOnlyRootFilesystem: true
            runAsGroup: 1001
            runAsNonRoot: true
            runAsUser: 1001
          volumeMounts:
            - mountPath: /tmp
              name: empty-dir
              subPath: tmp-dir
            - mountPath: /opt/bitnami/postgresql/conf
              name: empty-dir
              subPath: app-conf-dir
            - mountPath: /opt/bitnami/postgresql/tmp
              name: empty-dir
              subPath: app-tmp-dir
            - mountPath: /docker-entrypoint-initdb.d/
              name: custom-init-scripts
            - mountPath: /dev/shm
              name: dshm
            - mountPath: /bitnami/postgresql
              name: data
      restartPolicy: Always
      securityContext:
        fsGroup: 1001
        fsGroupChangePolicy: Always
      volumes:
        - emptyDir: {}
          name: empty-dir
        - configMap:
            defaultMode: 420
            name: a12-infrastructure-postgresql-init
          name: custom-init-scripts
        - emptyDir:
            medium: Memory
          name: dshm
  volumeClaimTemplates:
    - apiVersion: v1
      kind: PersistentVolumeClaim
      metadata:
        name: data
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 10Gi
        storageClassName: nfs-client-abrieg
        volumeMode: Filesystem

to the official postgres image based stateful set (also based on A12 helm chart, stripped down)

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: a12-infrastructure-postgresql
  namespace: abrieg
spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain
    whenScaled: Retain
  template:
    metadata:
      name: a12-infrastructure-postgresql
    spec:
      automountServiceAccountToken: false
      containers:
        - env:
            - name: POSTGRESQL_PORT_NUMBER
              value: '5432'
            - name: POSTGRESQL_VOLUME_DIR
              value: /var/lib/postgresql
            - name: PGDATA
              value: /var/lib/postgresql/data
            - name: POSTGRES_USER
              value: postgres-custom-user
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  key: postgresql-custom-user-password
                  name: a12-secrets-postgresql
            - name: POSTGRES_POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  key: postgresql-password
                  name: a12-secrets-postgresql
            - name: POSTGRESQL_SHARED_PRELOAD_LIBRARIES
              value: pgaudit
          image: <INTERNAL_LINK>
          imagePullPolicy: IfNotPresent
          name: postgresql
          ports:
            - containerPort: 5432
              name: tcp-postgresql
              protocol: TCP
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
            privileged: false
            readOnlyRootFilesystem: true
            runAsGroup: 999
            runAsNonRoot: true
            runAsUser: 999
          volumeMounts:
            - mountPath: /tmp
              name: empty-dir
              subPath: tmp-dir
            - mountPath: /var/run/postgresql/
              name: empty-dir
              subPath: run-dir
            - mountPath: /opt/bitnami/postgresql/conf
              name: empty-dir
              subPath: app-conf-dir
            - mountPath: /opt/bitnami/postgresql/tmp
              name: empty-dir
              subPath: app-tmp-dir
            - mountPath: /docker-entrypoint-initdb.d/
              name: custom-init-scripts
            - mountPath: /dev/shm
              name: dshm
            - mountPath: /var/lib/postgresql
              name: data
      restartPolicy: Always
      securityContext:
        fsGroup: 999
        fsGroupChangePolicy: Always
      serviceAccount: a12-infrastructure-postgresql
      serviceAccountName: a12-infrastructure-postgresql
      volumes:
        - emptyDir: {}
          name: empty-dir
        - configMap:
            defaultMode: 420
            name: a12-infrastructure-postgresql-init
          name: custom-init-scripts
        - emptyDir:
            medium: Memory
          name: dshm
  volumeClaimTemplates:
    - apiVersion: v1
      kind: PersistentVolumeClaim
      metadata:
        creationTimestamp: null
        name: data
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 10Gi
        storageClassName: nfs-client-abrieg
        volumeMode: Filesystem

However after having the stateful set changed and a new pod is running the database doesn’t contain any data anymore. Why is that? What do I need to do to fix that?

Note that I also changed the postgres init script according to the A12 Helm chart based on the official postgres image.

Hello @andreas-fresh-mesa

We had similar issue, worked it out by

a12-infrastructure.postgres.postgresqlDataDir: /var/lib/postgresql/pgdata

Please check

Hi @andreas-fresh-mesa ,

When using the official Docker image from Postgres, it will always create the folder /var/lib/postgres/data, regardless of whether the folder exists or contains data.

Therefore, using the suggestion path from @loi-risen-dale and assigning that value to PGDATA environment variable should help you solve this issue.

If you do it in this way, please keep in mind that because the folder name is different, you will need to perform a copy from /data to /pgdata once before the pod run.

This explains why I don’t see any of the old database files anymore after making the change in the stateful set. However when using a directory other than /var/lib/posgres/data like /var/lib/postgres/pgdata then postgres will complain about a missing /var/lib/postgres/pgdata/postgres.conf file. In the bitnami stateful set that conf file is stored in a different location. So one has to move that file around when the old stateful set is still active. And if that is solved one will get a

2026-01-07 13:27:48.918 GMT [1] FATAL:  could not access file "pgaudit": No such file or directory

error that I haven’t investigated further.

The solution to all of this is to set up a second stateful set that is based on the official postgres image, stop connections to the first one, export databases from the first and import to the second and then switch over to the application to the new stateful set.