diff --git a/templates/job-setup-apps.yaml b/templates/job-setup-apps.yaml
index 993f2f5367fd279e1e6f9ecd1650a64fb8d0b5d2..e240388a83136c8dad0c6b3306d91d8aac7cfb45 100644
--- a/templates/job-setup-apps.yaml
+++ b/templates/job-setup-apps.yaml
@@ -21,5 +21,25 @@ spec:
       serviceAccountName: {{ .Release.Name }}-setup-apps-job
       containers:
       - name: setup-apps-job
-        image: docker.io/bitnami/kubectl:1.25
-        command: ["kubectl", "exec", "deploy/{{ .Release.Name }}-nextcloud", "-n", {{ .Release.Namespace }}, "--", "/bin/bash", "/var/local/setup-apps.sh"]
+        image: docker.io/bitnami/kubectl:1.28
+        command:
+        - /bin/bash
+        - -c
+        - >
+          pod=$(kubectl get pod -n {{ .Release.Namespace }} -l app.kubernetes.io/name=nextcloud --no-headers -o custom-columns=":metadata.name" | head -n 1)
+          && cp -L /scripts/setup-apps.sh /tmp/setup-apps.sh
+          && kubectl cp
+          -n {{ .Release.Namespace }}
+          /tmp/setup-apps.sh
+          $pod:/tmp/setup-apps.sh
+          && kubectl exec
+          -n {{ .Release.Namespace }}
+          deploy/{{ .Release.Name }}-nextcloud
+          -- /bin/bash /tmp/setup-apps.sh
+        volumeMounts:
+        - mountPath: /scripts
+          name: setup-apps
+      volumes:
+      - name: setup-apps
+        configMap:
+          name: "{{ .Release.Name }}-setup-apps"
diff --git a/templates/nextcloud-onlyoffice-config.yaml b/templates/nextcloud-onlyoffice-config.yaml
index fe728f008a647bf36dbbbb1cf0b0455648e2377d..985dc62154a56cb5bac4df508cd94c5b65127e9c 100644
--- a/templates/nextcloud-onlyoffice-config.yaml
+++ b/templates/nextcloud-onlyoffice-config.yaml
@@ -10,172 +10,6 @@ metadata:
     app.kubernetes.io/instance: {{ .Release.Name | quote }}
     helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
 data:
-  setup-apps.sh: |
-    #!/bin/bash
-
-    set -o errexit
-
-    # This script gets executed by the Kubernetes Job `{{ .Release.Name }}-setup-apps`,
-    # which gets created by Helm after every chart install and upgrade.
-    #
-    # The script:
-    #
-    #   * Installs all apps declared in the `apps` helm values array
-    #     * Installs all apps
-    #     * Updates pinned apps if their pinned version has changed
-    #     * Always updates unpinned apps to their newest version
-    #   * Runs upgrade routines after installation of a new release or new
-    #     pinned apps.
-    #   * Configures single-sign-on
-    #   * Loads Nextcloud config from the config.json
-    #   * Updates database indices, columns, keys, etc needed after NC upgrade
-
-    # Copied from the NC docker entrypoint to run OCC commands
-    run_as() {
-        if [ "$(id -u)" = 0 ]; then
-            su -p "www-data" -s /bin/sh -c "$1"
-        else
-            sh -c "$1"
-        fi
-    }
-
-    echo "STARTING SETUP-APPS.SH"
-
-    # Starting in version 0.15.18 of this chart (Stackspin 2.10), we no longer
-    # copy config.json from /var/local to the web root. We remove the copied
-    # file if we think we can safely do so.
-    if [ -f /var/www/html/config.json ]
-    then
-        echo "Found copy of config.json in web root."
-        if diff -q /var/www/html/config.json /var/local/config.json >/dev/null
-        then
-            echo "It's identical to the current config from the configmap."
-            echo "Deleting it from the web root."
-            rm /var/www/html/config.json
-        else
-            echo "It's different from the current config from the configmap."
-            echo "Keeping it around for manual reconciliation."
-        fi
-    fi
-
-    occ="/var/www/html/occ"
-    count=0
-    limit=10
-
-    # There's a nextcloud setup process. First, wait for `occ` to exist
-    until [ -f "$occ" ] || [ "$count" -gt "$limit" ]
-    do
-        count=$((count+1))
-        wait=$((count*10))
-        echo "$occ doesn't exist yet, waiting $wait seconds"
-        sleep $wait
-    done
-
-    echo "$occ now exists!"
-
-    count=0
-
-    # As soon as the $occ command exist, we know that we can run occ, but
-    # Nextcloud might still be initializing, we use `occ` to find out whether
-    # the installation process has finished, and then we continue.
-    until [[ $(run_as "php occ status --output json") =~ '"installed":true' ]] || [ "$count" -gt "$limit" ]
-    do
-        count=$((count+1))
-        wait=$((count*10))
-        echo "Nextcloud is not installed yet. Waiting $wait seconds..."
-        sleep $wait
-    done
-
-    echo "Nextcloud is now installed, we can do our thing!"
-
-    app_versions=$(run_as "php $occ app:list --output json")
-
-    echo "app versions found"
-
-    # Install all apps declared in the `apps` helm values array
-    {{- range $apps_type, $apps := .Values.apps }}
-    {{- range $apps }}  
-    {{- if not .name }}
-      echo "Skipping app {{ . }} without name variable set"
-    {{- else if .version }}
-    # Apps with a pinned version number are downloaded from GitHub so we can
-    # update the pin with Renovatebot
-
-    desired_version=$(echo "{{ .version }}" | sed 's/^v//')
-
-    if [[ $app_versions =~ '"{{ .name }}":"'"$desired_version"'"' ]]
-    then
-      echo "Pinned app {{ .name }} is up-to-date at version {{ .version }}"
-    else
-      echo "No match in ${app_versions} for \"{{ .name }}\":\"$desired_version\","
-      echo "Installing app {{ .github_repository }} version '{{ .version }}'"
-      # Where to install the app
-      target_directory="/var/www/html/custom_apps"
-      # We need to edit $ to be able to use `tpl` inside a `range`,
-      # see https://github.com/helm/helm/issues/5979#issuecomment-518231758 .
-      # This allows us to use version variable in the `release_filename` or
-      # `raw_url`.
-      {{- $_ := set $ "version" .version}}
-      {{- if .raw_url }}
-      url="{{ tpl .raw_url $ }}"
-      {{- else }}
-      url="https://github.com/{{ .github_repository }}/releases/download/{{ .version }}/{{ tpl .release_filename $ }}"
-      {{- end }} # end if .raw_url
-      curl "$url" -Lo "{{ .name }}.tar.gz"
-      # If the above url results in a 404, curl will not complain and store the
-      # body of the 404 in the file :/. So we check here whether we actually
-      # got a valid archive; `tar` will fail if not.
-      tar -tf "{{ .name }}.tar.gz" >/dev/null
-      # Remove old version of the app
-      if [[ -d "$target_directory/{{ .name }}" ]]
-      then
-        rm -r "$target_directory/{{ .name }}"
-      fi
-      # Extract app into target directory. The app tars usually contain a folder
-      # named after the app name
-      tar -xf "{{ .name }}.tar.gz" -C "$target_directory"
-      # Ownership in the archive can be anything. Nextcloud wants it to be
-      # www-data.
-      chown -R 33:33 "$target_directory"
-      rm "{{ .name }}.tar.gz"
-    fi
-    {{- else }}
-    # Unpinned app
-    if [[ "$app_versions" =~ '"{{ .name }}"' ]]
-    then
-        # Update the app to its latest version
-        echo "Updating unpinned app {{ .name }}"
-        run_as "php $occ app:update {{ .name }} --no-interaction"
-    else
-        echo "Installing app {{ .name }}"
-        run_as "php $occ app:install {{ .name }} --keep-disabled --no-interaction"
-    fi
-    {{ end }} # end if and .github_repository .version
-    {{- if .enabled }}
-    # Enable {{ .name }} app
-    run_as "php $occ app:enable {{ .name }}"
-    {{- end }} # end if .enabled
-    {{- end }} # end range $apps
-    {{- end }} # end range .Values.apps
-
-    # Some of the manually installed apps might need to run upgrade scripts, run
-    # them now
-    run_as "php $occ upgrade"
-
-    # Config settings from the configmap above
-    run_as "php $occ config:import /var/local/config.json"
-
-    echo "Updating database indices, columns, keys, etc."
-    run_as "php $occ db:add-missing-indices --no-interaction"
-    run_as "php $occ db:add-missing-columns --no-interaction"
-    run_as "php $occ db:add-missing-primary-keys --no-interaction"
-    run_as "php $occ db:convert-filecache-bigint --no-interaction"
-
-    {{- if .Values.scim.token }}
-    set -x
-    run_as "php $occ config:app:set scimserviceprovider jwt-secret --value="'"{{ .Values.scim.token }}"'
-    {{- end }}
-  #
   # All values in config.json are applied by the nextcloud occ command
   #   config:import.
   # system.trusted_proxies contains a list of proxies that are considered
@@ -200,7 +34,6 @@ data:
   # apps.core.backgroundjobs_mode set to cron disables the unreliable ajax
   #   scheduling that is enabled by default. Ajax scheduling is not needed
   #   because cronjobs are regularly executed by a kubernetes resource.
-  #
   config.json: |
     {
         "system":{
diff --git a/templates/setup-apps-configmap.yaml b/templates/setup-apps-configmap.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0d6e577f60c0d8eff4f28ed063ac06c6fce5dabe
--- /dev/null
+++ b/templates/setup-apps-configmap.yaml
@@ -0,0 +1,175 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: "{{ .Release.Name }}-setup-apps"
+  labels:
+    app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
+    app.kubernetes.io/instance: {{ .Release.Name | quote }}
+    helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
+data:
+  setup-apps.sh: |
+    #!/bin/bash
+
+    set -o errexit
+
+    # This script gets executed by the Kubernetes Job `{{ .Release.Name }}-setup-apps`,
+    # which gets created by Helm after every chart install and upgrade.
+    #
+    # The script:
+    #
+    #   * Installs all apps declared in the `apps` helm values array
+    #     * Installs all apps
+    #     * Updates pinned apps if their pinned version has changed
+    #     * Always updates unpinned apps to their newest version
+    #   * Runs upgrade routines after installation of a new release or new
+    #     pinned apps.
+    #   * Configures single-sign-on
+    #   * Loads Nextcloud config from the config.json
+    #   * Updates database indices, columns, keys, etc needed after NC upgrade
+
+    # Copied from the NC docker entrypoint to run OCC commands
+    run_as() {
+        if [ "$(id -u)" = 0 ]; then
+            su -p "www-data" -s /bin/sh -c "$1"
+        else
+            sh -c "$1"
+        fi
+    }
+
+    echo "STARTING SETUP-APPS.SH"
+
+    # Starting in version 0.15.18 of this chart (Stackspin 2.10), we no longer
+    # copy config.json from /var/local to the web root. We remove the copied
+    # file if we think we can safely do so.
+    if [ -f /var/www/html/config.json ]
+    then
+        echo "Found copy of config.json in web root."
+        if diff -q /var/www/html/config.json /var/local/config.json >/dev/null
+        then
+            echo "It's identical to the current config from the configmap."
+            echo "Deleting it from the web root."
+            rm /var/www/html/config.json
+        else
+            echo "It's different from the current config from the configmap."
+            echo "Keeping it around for manual reconciliation."
+        fi
+    fi
+
+    occ="/var/www/html/occ"
+    count=0
+    limit=10
+
+    # There's a nextcloud setup process. First, wait for `occ` to exist
+    until [ -f "$occ" ] || [ "$count" -gt "$limit" ]
+    do
+        count=$((count+1))
+        wait=$((count*10))
+        echo "$occ doesn't exist yet, waiting $wait seconds"
+        sleep $wait
+    done
+
+    echo "$occ now exists!"
+
+    count=0
+
+    # As soon as the $occ command exist, we know that we can run occ, but
+    # Nextcloud might still be initializing, we use `occ` to find out whether
+    # the installation process has finished, and then we continue.
+    until [[ $(run_as "php occ status --output json") =~ '"installed":true' ]] || [ "$count" -gt "$limit" ]
+    do
+        count=$((count+1))
+        wait=$((count*10))
+        echo "Nextcloud is not installed yet. Waiting $wait seconds..."
+        sleep $wait
+    done
+
+    echo "Nextcloud is now installed, we can do our thing!"
+
+    app_versions=$(run_as "php $occ app:list --output json")
+
+    echo "app versions found"
+
+    # Install all apps declared in the `apps` helm values array
+    {{- range $apps_type, $apps := .Values.apps }}
+    {{- range $apps }}  
+    {{- if not .name }}
+      echo "Skipping app {{ . }} without name variable set"
+    {{- else if .version }}
+    # Apps with a pinned version number are downloaded from GitHub so we can
+    # update the pin with Renovatebot
+
+    desired_version=$(echo "{{ .version }}" | sed 's/^v//')
+
+    if [[ $app_versions =~ '"{{ .name }}":"'"$desired_version"'"' ]]
+    then
+      echo "Pinned app {{ .name }} is up-to-date at version {{ .version }}"
+    else
+      echo "No match in ${app_versions} for \"{{ .name }}\":\"$desired_version\","
+      echo "Installing app {{ .github_repository }} version '{{ .version }}'"
+      # Where to install the app
+      target_directory="/var/www/html/custom_apps"
+      # We need to edit $ to be able to use `tpl` inside a `range`,
+      # see https://github.com/helm/helm/issues/5979#issuecomment-518231758 .
+      # This allows us to use version variable in the `release_filename` or
+      # `raw_url`.
+      {{- $_ := set $ "version" .version}}
+      {{- if .raw_url }}
+      url="{{ tpl .raw_url $ }}"
+      {{- else }}
+      url="https://github.com/{{ .github_repository }}/releases/download/{{ .version }}/{{ tpl .release_filename $ }}"
+      {{- end }} # end if .raw_url
+      curl "$url" -Lo "{{ .name }}.tar.gz"
+      # If the above url results in a 404, curl will not complain and store the
+      # body of the 404 in the file :/. So we check here whether we actually
+      # got a valid archive; `tar` will fail if not.
+      tar -tf "{{ .name }}.tar.gz" >/dev/null
+      # Remove old version of the app
+      if [[ -d "$target_directory/{{ .name }}" ]]
+      then
+        rm -r "$target_directory/{{ .name }}"
+      fi
+      # Extract app into target directory. The app tars usually contain a folder
+      # named after the app name
+      tar -xf "{{ .name }}.tar.gz" -C "$target_directory"
+      # Ownership in the archive can be anything. Nextcloud wants it to be
+      # www-data.
+      chown -R 33:33 "$target_directory"
+      rm "{{ .name }}.tar.gz"
+    fi
+    {{- else }}
+    # Unpinned app
+    if [[ "$app_versions" =~ '"{{ .name }}"' ]]
+    then
+        # Update the app to its latest version
+        echo "Updating unpinned app {{ .name }}"
+        run_as "php $occ app:update {{ .name }} --no-interaction"
+    else
+        echo "Installing app {{ .name }}"
+        run_as "php $occ app:install {{ .name }} --keep-disabled --no-interaction"
+    fi
+    {{ end }} # end if and .github_repository .version
+    {{- if .enabled }}
+    # Enable {{ .name }} app
+    run_as "php $occ app:enable {{ .name }}"
+    {{- end }} # end if .enabled
+    {{- end }} # end range $apps
+    {{- end }} # end range .Values.apps
+
+    # Some of the manually installed apps might need to run upgrade scripts, run
+    # them now
+    run_as "php $occ upgrade"
+
+    # Config settings from the configmap above
+    run_as "php $occ config:import /var/local/config.json"
+
+    echo "Updating database indices, columns, keys, etc."
+    run_as "php $occ db:add-missing-indices --no-interaction"
+    run_as "php $occ db:add-missing-columns --no-interaction"
+    run_as "php $occ db:add-missing-primary-keys --no-interaction"
+    run_as "php $occ db:convert-filecache-bigint --no-interaction"
+
+    {{- if .Values.scim.token }}
+    set -x
+    run_as "php $occ config:app:set scimserviceprovider jwt-secret --value="'"{{ .Values.scim.token }}"'
+    {{- end }}