title: Helm Keycloak Postgres with Existing database...Data is not saved across installs
body: <p>I'm trying to get Helm Keycloak chart to work with Postgres database. I would like my data to persist during the uninstalls.
  I've created a /mnt/disks/vol1/postgresql mount.
  When I run this command:
  helm install keycloak -f keycloak.yaml codecentric/keycloak</p>
  <p>Postgres database is created in the mount directory.
  The problem is, it doesn't save any of the my updated data in Keycloak like adding a new realm, etc. Next time I come back after doing &quot;helm delete keycloak&quot; and installing the chart again, all the data is gone.
  What am I doing wrong?
  Here are my files:</p>
  <p>keycloak.yaml</p>
  <pre><code>init:
    image:
      repository: busybox
      tag: 1.31
      pullPolicy: IfNotPresent
    resources: {}
      # limits:
      #   cpu: &quot;10m&quot;
      #   memory: &quot;32Mi&quot;
      # requests:
      #   cpu: &quot;10m&quot;
    #   memory: &quot;32Mi&quot;
  
  clusterDomain: cluster.local
  
  ## Optionally override the fully qualified name
  # fullnameOverride: keycloak
  
  ## Optionally override the name
  # nameOverride: keycloak
  
  keycloak:
    replicas: 1
    image:
      repository: jboss/keycloak
      tag: 8.0.1
      pullPolicy: IfNotPresent
  
      ## Optionally specify an array of imagePullSecrets.
      ## Secrets must be manually created in the namespace.
      ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
      ##
      pullSecrets: []
      # - myRegistrKeySecretName
  
    hostAliases: []
    #  - ip: &quot;1.2.3.4&quot;
    #    hostnames:
    #      - &quot;my.host.com&quot;
  
    proxyAddressForwarding: true
  
    enableServiceLinks: false
  
    podManagementPolicy: Parallel
  
    restartPolicy: Always
  
    serviceAccount:
      # Specifies whether a service account should be created
      create: false
      # The name of the service account to use.
      # If not set and create is true, a name is generated using the fullname template
      name:
  
    securityContext:
      fsGroup: 1000
  
    containerSecurityContext:
      runAsUser: 1000
      runAsNonRoot: true
  
    ## The path keycloak will be served from. To serve keycloak from the root path, use two quotes (e.g. &quot;&quot;).
    basepath: &quot;auth&quot;
  
  
    ## Additional sidecar containers, e. g. for a database proxy, such as Google's cloudsql-proxy
    extraContainers: |
  
    ## lifecycleHooks defines the container lifecycle hooks
    lifecycleHooks: |
      # postStart:
      #   exec:
      #     command: [&quot;/bin/sh&quot;, &quot;-c&quot;, &quot;ls&quot;]
  
    ## Override the default for the Keycloak container, e.g. for clusters with large cache that requires rebalancing.
    terminationGracePeriodSeconds: 60
  
    ## Additional arguments to start command e.g. -Dkeycloak.import= to load a realm
    extraArgs: &quot;&quot;
  
    ## Username for the initial Keycloak admin user
    username: admin
  
    ## Password for the initial Keycloak admin user. Applicable only if existingSecret is not set.
    ## If not set, a random 10 characters password will be used
    password: &quot;aaaa&quot;
  
    # Specifies an existing secret to be used for the admin password
    existingSecret: &quot;&quot;
  
    # The key in the existing secret that stores the password
    existingSecretKey: password
  
    ## jGroups configuration (only for HA deployment)
    jgroups:
      discoveryProtocol: dns.DNS_PING
      discoveryProperties: &gt;
        &quot;dns_query={{ template &quot;keycloak.fullname&quot; . }}-headless.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}&quot;
  
    javaToolOptions: &gt;-
      -XX:+UseContainerSupport
      -XX:MaxRAMPercentage=50.0
  
    ## Allows the specification of additional environment variables for Keycloak
    extraEnv: |
      - name: KEYCLOAK_LOGLEVEL
        value: DEBUG
      # - name: WILDFLY_LOGLEVEL
      #   value: DEBUG
      # - name: CACHE_OWNERS
      #   value: &quot;2&quot;
      # - name: DB_QUERY_TIMEOUT
      #   value: &quot;60&quot;
      # - name: DB_VALIDATE_ON_MATCH
      #   value: true
      # - name: DB_USE_CAST_FAIL
      #   value: false
  
    affinity: |
      podAntiAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                {{- include &quot;keycloak.selectorLabels&quot; . | nindent 10 }}
              matchExpressions:
                - key: role
                  operator: NotIn
                  values:
                    - test
            topologyKey: kubernetes.io/hostname
        preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchLabels:
                  {{- include &quot;keycloak.selectorLabels&quot; . | nindent 12 }}
                matchExpressions:
                  - key: role
                    operator: NotIn
                    values:
                      - test
              topologyKey: failure-domain.beta.kubernetes.io/zone
  
    nodeSelector: {}
    priorityClassName: &quot;&quot;
    tolerations: []
  
    ## Additional pod labels
    ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
    podLabels: {}
  
    ## Extra Annotations to be added to pod
    podAnnotations: {}
  
    livenessProbe: |
      httpGet:
        path: {{ if ne .Values.keycloak.basepath &quot;&quot; }}/{{ .Values.keycloak.basepath }}{{ end }}/
        port: http
      initialDelaySeconds: 300
      timeoutSeconds: 5
    readinessProbe: |
      httpGet:
        path: {{ if ne .Values.keycloak.basepath &quot;&quot; }}/{{ .Values.keycloak.basepath }}{{ end }}/realms/master
        port: http
      initialDelaySeconds: 30
      timeoutSeconds: 1
  
    resources: {}
      # limits:
      #   cpu: &quot;100m&quot;
      #   memory: &quot;1024Mi&quot;
      # requests:
      #   cpu: &quot;100m&quot;
    #   memory: &quot;1024Mi&quot;
  
    ## WildFly CLI configurations. They all end up in the file 'keycloak.cli' configured in the configmap which is
    ## executed on server startup.
    cli:
      enabled: true
      nodeIdentifier: |
        {{ .Files.Get &quot;scripts/node-identifier.cli&quot; }}
  
      logging: |
        {{ .Files.Get &quot;scripts/logging.cli&quot; }}
  
      ha: |
        {{ .Files.Get &quot;scripts/ha.cli&quot; }}
  
      datasource: |
        {{ .Files.Get &quot;scripts/datasource.cli&quot; }}
  
      # Custom CLI script
      custom: |
  
    ## Custom startup scripts to run before Keycloak starts up
    startupScripts: {}
      # mystartup.sh: |
      #   #!/bin/sh
      #
    #   echo 'Hello from my custom startup script!'
  
    ## Additional init containers, e. g. for providing custom themes
    extraInitContainers: |
  
    ## Add additional volumes and mounts, e. g. for custom themes
    extraVolumeMounts: |
     - name: theme
       mountPath: /opt/jboss/keycloak/themes/
       subPath: kcthemes
    extraVolumes: |
     - name: theme
       persistentVolumeClaim:
        claimName: keycloak-pv-claim
  
    ## Add additional ports, eg. for custom admin console
    extraPorts: |
  
    podDisruptionBudget: {}
      # maxUnavailable: 1
    # minAvailable: 1
  
    service:
      annotations: {}
      # service.beta.kubernetes.io/aws-load-balancer-internal: &quot;0.0.0.0/0&quot;
  
      labels: {}
      # key: value
  
      ## ServiceType
      ## ref: https://kubernetes.io/docs/user-guide/services/#publishing-services---service-types
      type: ClusterIP
  
      ## Optional static port assignment for service type NodePort.
      # nodePort: 30000
  
      httpPort: 8080
      httpNodePort: &quot;&quot;
  
      httpsPort: 8443
      httpsNodePort: &quot;&quot;
  
      # Optional: jGroups port for high availability clustering
      jgroupsPort: 7600
  
    ## Ingress configuration.
    ## ref: https://kubernetes.io/docs/user-guide/ingress/
    ingress:
      enabled: true
      path: /
  
      annotations: {}
        # kubernetes.io/ingress.class: nginx
        # kubernetes.io/tls-acme: &quot;true&quot;
      # ingress.kubernetes.io/affinity: cookie
  
      labels: {}
      # key: value
  
      ## List of hosts for the ingress
      hosts:
        - localhost
  
      ## TLS configuration
      tls:
       - hosts:
           - localhost
         secretName: keycloak-secret
  
    ## OpenShift route configuration.
    ## ref: https://docs.openshift.com/container-platform/3.11/architecture/networking/routes.html
    route:
      enabled: false
      path: /
  
      annotations: {}
        # kubernetes.io/tls-acme: &quot;true&quot;
        # haproxy.router.openshift.io/disable_cookies: &quot;true&quot;
      # haproxy.router.openshift.io/balance: roundrobin
  
      labels: {}
      # key: value
  
      # Host name for the route
      host:
  
      # TLS configuration
      tls:
        enabled: true
        insecureEdgeTerminationPolicy: Redirect
        termination: edge
  
    ## Persistence configuration
    persistence:
      # If true, the Postgres chart is deployed
      deployPostgres: true
  
      # The database vendor. Can be either &quot;postgres&quot;, &quot;mysql&quot;, &quot;mariadb&quot;, or &quot;h2&quot;
      dbVendor: postgres
  
      ## The following values only apply if &quot;deployPostgres&quot; is set to &quot;false&quot;
      dbName: keycloak
      dbHost: keycloak-postgresql
      dbPort: 5432
  
      ## Database Credentials are loaded from a Secret residing in the same Namespace as keycloak.
      ## The Chart can read credentials from an existing Secret OR it can provision its own Secret.
  
      ## Specify existing Secret
      # If set, specifies the Name of an existing Secret to read db credentials from.
      existingSecret: &quot;&quot;
      existingSecretPasswordKey: &quot;&quot;  # read keycloak db password from existingSecret under this Key
      existingSecretUsernameKey: &quot;&quot;  # read keycloak db user from existingSecret under this Key
  
      ## Provision new Secret
      # Only used if existingSecret is not specified. In this case a new secret is created
      # populated by the variables below.
      dbUser: KcSuperUser
      dbPassword: KcSuperPass
  
  postgresql:
    image:
      tag: 12
    ### PostgreSQL User to create.
    ##
    postgresqlUsername: &quot;KcSuperUser&quot;
  
    ## PostgreSQL Password for the new user.
    ## If not set, a random 10 characters password will be used.
    ##
    postgresqlPassword: &quot;KcSuperPassword&quot;
  
    ## PostgreSQL Database to create.
    ##
    postgresqlDatabase: keycloak
  
    ## Persistent Volume Storage configuration.
    ## ref: https://kubernetes.io/docs/user-guide/persistent-volumes
    ##
    persistence:
      ## Enable PostgreSQL persistence using Persistent Volume Claims.
      ##
      enabled: true
      exitingClaim: &quot;pdata-keycloak-postgresql-0&quot;
      mountPath: /var/lib/postgresql/data
  
  classes:
    - name: postgres # Defines name of storage class.
      # Path on the host where local volumes of this storage class are mounted
      # under.
      #hostDir: /mnt/fast-disks
      # Optionally specify mount path of local volumes. By default, we use same
      # path as hostDir in container.
      mountDir: /mnt/disks/vol1/postgresdata
      # The volume mode of created PersistentVolume object. Default to Filesystem
      # if not specified.
      volumeMode: Filesystem
      # Filesystem type to mount.
      # It applies only when the source path is a block device,
      # and desire volume mode is Filesystem.
      # Must be a filesystem type supported by the host operating system.
      fsType: ext4
      blockCleanerCommand:
        #  Do a quick reset of the block device during its cleanup.
        #  - &quot;/scripts/quick_reset.sh&quot;
        #  or use dd to zero out block dev in two iterations by uncommenting these lines
        #  - &quot;/scripts/dd_zero.sh&quot;
        #  - &quot;2&quot;
        # or run shred utility for 2 iteration.s
        - &quot;/scripts/shred.sh&quot;
        - &quot;2&quot;
      # or blkdiscard utility by uncommenting the line below.
      #  - &quot;/scripts/blkdiscard.sh&quot;
      # Uncomment to create storage class object with default configuration.
      # storageClass: true
      # Uncomment to create storage class object and configure it.
      # storageClass:
  
  test:
    enabled: true
    image:
      repository: unguiculus/docker-python3-phantomjs-selenium
      tag: v1
      pullPolicy: IfNotPresent
    securityContext:
      fsGroup: 1000
    containerSecurityContext:
      runAsUser: 1000
      runAsNonRoot: true
  
  prometheus:
    operator:
      ## Are you using Prometheus Operator?
      enabled: false
  
      serviceMonitor:
        ## Optionally set a target namespace in which to deploy serviceMonitor
        namespace: &quot;&quot;
  
        ## Additional labels to add to the ServiceMonitor so it is picked up by the operator.
        ## If using the [Helm Chart](https://github.com/helm/charts/tree/master/stable/prometheus-operator) this is the name of the Helm release.
        selector:
          release: prometheus
  
        ## Interval at which Prometheus scrapes metrics
        interval: 10s
  
        ## Timeout at which Prometheus timeouts scrape run
        scrapeTimeout: 10s
  
        ## The path to scrape
        path: /auth/realms/master/metrics
  
      prometheusRules:
        ## Add Prometheus Rules?
        enabled: false
  
        ## Additional labels to add to the PrometheusRule so it is picked up by the operator.
        ## If using the [Helm Chart](https://github.com/helm/charts/tree/master/stable/prometheus-operator) this is the name of the Helm release and 'app: prometheus-operator'
        selector:
          app: prometheus-operator
          release: prometheus
  
        ## Some example rules.
        rules: {}
        #  - alert: keycloak-IngressHigh5xxRate
        #    annotations:
        #      message: The percentage of 5xx errors for keycloak over the last 5 minutes is over 1%.
        #    expr: (sum(rate(nginx_ingress_controller_response_duration_seconds_count{exported_namespace=&quot;mynamespace&quot;,ingress=&quot;mynamespace-keycloak&quot;,status=~&quot;5[0-9]{2}&quot;}[1m]))/sum(rate(nginx_ingress_controller_response_duration_seconds_count{exported_namespace=&quot;mynamespace&quot;,ingress=&quot;mynamespace-keycloak&quot;}[1m])))*100 &gt; 1
        #    for: 5m
        #    labels:
        #      severity: warning
        #  - alert: keycloak-IngressHigh5xxRate
        #    annotations:
        #      message: The percentage of 5xx errors for keycloak over the last 5 minutes is over 5%.
        #    expr: (sum(rate(nginx_ingress_controller_response_duration_seconds_count{exported_namespace=&quot;mynamespace&quot;,ingress=&quot;mynamespace-keycloak&quot;,status=~&quot;5[0-9]{2}&quot;}[1m]))/sum(rate(nginx_ingress_controller_response_duration_seconds_count{exported_namespace=&quot;mynamespace&quot;,ingress=&quot;mynamespace-keycloak&quot;}[1m])))*100 &gt; 5
        #    for: 5m
        #    labels:
        #      severity: critical
  </code></pre>
  <p>Volume</p>
  <pre><code>kind: PersistentVolume
  apiVersion: v1
  metadata:
    name: keycloak-postgres-volume
    labels:
      app: postgres
      type: local
  spec:
    storageClassName: postgres
    capacity:
      storage: 10Gi
    accessModes:
      - ReadWriteOnce
    hostPath:
      path: &quot;/mnt/disks/vol1/postgresdata&quot;
  </code></pre>
  <p>Claim:</p>
  <pre><code>kind: PersistentVolumeClaim
  apiVersion: v1
  metadata:
    name: data-keycloak-postgresql-0
    labels:
      app: postgres
  spec:
    storageClassName: postgres
    accessModes:
      - ReadWriteOnce
    resources:
      requests:
        storage: 10Gi
  </code></pre>
  <p>Storage</p>
  <pre class="lang-yaml prettyprint-override"><code>kind: StorageClass
  apiVersion: storage.k8s.io/v1
  metadata:
    name: postgres
  provisioner: kubernetes.io/no-provisioner
  volumeBindingMode: WaitForFirstConsumer
  </code></pre>