diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8da179806aabe494cdcdb1764e587e0ba00ce3f3..eb826cf0957785083b776d8f38458848a7d6a38c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -128,30 +128,6 @@ include:
   extends:
     - .general_rules
 
-.rocketchat_rules:
-  rules:
-    - changes:
-        - flux2/apps/$RESOURCE/*.yaml
-        - flux2/cluster/optional/$RESOURCE/*.yaml
-        - install/install-app.sh
-        - test/taiko/*
-    - if: '$TRIGGER_JOBS =~ /enable-rocketchat/'
-    - if: '$CI_COMMIT_MESSAGE =~ /TRIGGER_JOBS=.*enable-rocketchat/'
-    # Because we're replacing Rocket.Chat with Zulip, and to spare resources, we
-    # do not test it in the all `main` CI pipelines anymore.
-    # - if: '$CI_COMMIT_BRANCH == "main"'
-
-.single_sign_on_rules:
-  rules:
-    - changes:
-        - flux2/core/base/$RESOURCE/*.yaml
-        - flux2/infrastructure/sources/single-sign-on.yaml
-        - install/install-stackspin.sh
-        - test/taiko/*
-    - if: '$TRIGGER_JOBS =~ /enable-single-sign-on/'
-    - if: '$CI_COMMIT_MESSAGE =~ /TRIGGER_JOBS=.*enable-single-sign-on/'
-    - if: '$CI_COMMIT_BRANCH == "main"'
-
 .wekan_rules:
   rules:
     - changes:
@@ -354,6 +330,7 @@ setup-stackspin:
     - cp install/kustomization.yaml ${CLUSTER_DIR}
     - kubectl create namespace flux-system
     - kubectl apply -k ${CLUSTER_DIR}
+    # Install flux and general, non-app specific secrets
     - bash ./install/install-stackspin.sh
   extends:
     - .ssh_setup
@@ -429,13 +406,6 @@ enable-nextcloud:
     - .enable_app_template
     - .nextcloud_rules
 
-enable-rocketchat:
-  variables:
-    RESOURCE: "rocketchat"
-  extends:
-    - .enable_app_template
-    - .rocketchat_rules
-
 enable-wekan:
   variables:
     RESOURCE: "wekan"
@@ -487,16 +457,6 @@ nextcloud-kustomization-ready:
     - .app-kustomization-ready
     - .nextcloud_rules
 
-rocketchat-kustomization-ready:
-  needs:
-    - job: setup-stackspin
-    - job: enable-rocketchat
-  variables:
-    RESOURCE: "rocketchat"
-  extends:
-    - .app-kustomization-ready
-    - .rocketchat_rules
-
 wekan-kustomization-ready:
   needs:
     - job: setup-stackspin
@@ -562,25 +522,23 @@ kube-prometheus-stack-cert:
     - .apps-cert
     - .kube_prometheus_stack_rules
 
-rocketchat-cert:
+single-sign-on-cert:
   variables:
-    RESOURCE: "rocketchat"
+    RESOURCE: "single-sign-on"
   needs:
-    - job: enable-rocketchat
+    - job: core-kustomizations-ready
     - job: setup-stackspin
   extends:
     - .apps-cert
-    - .rocketchat_rules
 
-single-sign-on-cert:
+dashboard-cert:
   variables:
-    RESOURCE: "single-sign-on"
+    RESOURCE: "dashboard"
   needs:
     - job: core-kustomizations-ready
     - job: setup-stackspin
   extends:
     - .apps-cert
-    - .single_sign_on_rules
 
 wekan-cert:
   variables:
@@ -700,17 +658,6 @@ nextcloud-taiko:
     - .taiko
     - .nextcloud_rules
 
-rocketchat-taiko:
-  variables:
-    RESOURCE: "rocketchat"
-  needs:
-    - job: rocketchat-cert
-    - job: setup-stackspin
-    - job: rocketchat-kustomization-ready
-  extends:
-    - .taiko
-    - .rocketchat_rules
-
 wekan-taiko:
   variables:
     RESOURCE: "wekan"
diff --git a/.gitlab/issue_templates/new_app.md b/.gitlab/issue_templates/new_app.md
index c6b2a20d693770f4f37c367c6c8f91305825eb44..6237067badeaef88ef572ef77847057328266e2f 100644
--- a/.gitlab/issue_templates/new_app.md
+++ b/.gitlab/issue_templates/new_app.md
@@ -5,15 +5,16 @@
 
 * [ ] Create new source if needed in `flux2/infrastructure/sources/APP.yaml`
 * [ ] Include `APP.yaml` in `flux2/infrastructure/sources/kustomization.yaml`
+* [ ] In case of a core app, add healthChecks to `core` `Kustomization`
+      (`flux2/cluster/base/core.yaml`)
 * [ ] Add app secret: `install/templates/stackspin-APP-variables.yaml.jinja`
-* Add `Kustomizations`:
-   * [ ] `flux2/cluster/optional/APP/APP.yaml`
-   * [ ] `flux2/apps/APP/kustomization.yaml`
-   * [ ] If needed, add PVCs in `flux2/apps/APP/pvc.yaml`
-   * [ ] Add `HelmRelease` in `flux2/apps/APP/release.yaml`
-     * Mem resource requests/limits: See https://open.greenhost.net/stackspin/stackspin/-/issues/1027
-      * [ ] mem request: the median weekly memory usage of an app
-      * [ ] mem limit: 150% of the weekly max memory usage
+ * [ ] Flux kustomization: `flux2/cluster/optional/APP/APP.yaml`
+ * [ ] K8s kustomization: `flux2/apps/APP/kustomization.yaml`
+ * [ ] If needed, add PVCs in `flux2/apps/APP/pvc.yaml`
+ * [ ] Add `HelmRelease` in `flux2/apps/APP/release.yaml`
+   * Mem resource requests/limits: See https://open.greenhost.net/stackspin/stackspin/-/issues/1027
+    * [ ] mem request: the median weekly memory usage of an app
+    * [ ] mem limit: 150% of the weekly max memory usage
 
 ### Single sign-on
 
@@ -72,3 +73,9 @@ Add the following elements to `.gitlab-ci.yml`:
   * [ ] `docs/installation_instructions.rst`
   * [ ] `docs/testing_instructions.rst`
   * [ ] `docs/usage.rst`
+
+## Follow-up issues
+
+Create follow-up issue with:
+
+* [ ] Fine-tune CPU and mem limits (https://open.greenhost.net/stackspin/stackspin/-/issues/1027)
diff --git a/flux2/cluster/base/core.yaml b/flux2/cluster/base/core.yaml
index c52b22b433d06d5d06d12cb9391e7e065c62cc92..8420863f265b788da90a4cb990b340098ab0a052 100644
--- a/flux2/cluster/base/core.yaml
+++ b/flux2/cluster/base/core.yaml
@@ -16,6 +16,8 @@ spec:
   validation: client
   postBuild:
     substituteFrom:
+      - kind: Secret
+        name: stackspin-dashboard-variables
       - kind: Secret
         name: stackspin-single-sign-on-variables
       - kind: Secret
@@ -35,6 +37,10 @@ spec:
       kind: HelmRelease
       name: single-sign-on
       namespace: stackspin
+    - apiVersion: helm.toolkit.fluxcd.io/v1beta1
+      kind: HelmRelease
+      name: dashboard
+      namespace: stackspin
     - apiVersion: apps/v1
       kind: Deployment
       name: single-sign-on-userbackend
@@ -47,3 +53,7 @@ spec:
       kind: Deployment
       name: single-sign-on-login
       namespace: stackspin
+    - apiVersion: apps/v1
+      kind: Deployment
+      name: dashboard
+      namespace: stackspin
diff --git a/flux2/core/base/dashboard/dashboard-release.yaml b/flux2/core/base/dashboard/dashboard-release.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..42112f55365af81e907d5fd70cb2417dd74ffc2b
--- /dev/null
+++ b/flux2/core/base/dashboard/dashboard-release.yaml
@@ -0,0 +1,33 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: dashboard
+spec:
+  releaseName: dashboard
+  dependsOn:
+    - name: nginx
+    - name: single-sign-on
+  chart:
+    spec:
+      chart: ./deployment/helmchart/
+      # NOTE: Change the GitRepository yaml file if you want a different version
+      sourceRef:
+        kind: GitRepository
+        name: dashboard
+        namespace: flux-system
+  interval: 1h
+  install:
+    remediation:
+      retries: 3
+    timeout: 10m
+  valuesFrom:
+    - kind: ConfigMap
+      name: stackspin-dashboard-values
+    # Allow overriding values by ConfigMap or Secret
+    - kind: ConfigMap
+      name: stackspin-dashboard-override
+      optional: true
+    - kind: Secret
+      name: stackspin-dashboard-override
+      optional: true
diff --git a/flux2/core/base/dashboard/dashboard-values-configmap.yaml b/flux2/core/base/dashboard/dashboard-values-configmap.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1948aa445bbd2e37da03755e1754c41b15b27079
--- /dev/null
+++ b/flux2/core/base/dashboard/dashboard-values-configmap.yaml
@@ -0,0 +1,42 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: stackspin-dashboard-values
+data:
+  values.yaml: |
+    fullnameOverride: dashboard
+    dashboard:
+      host: dashboard.${domain}
+      resources:
+        limits:
+          cpu: 20m
+          memory: 32Mi
+        requests:
+          cpu: 10m
+          memory: 16Mi
+    backend:
+      secretKey: ${backend_secret_key}
+      password: ${backend_password}
+      smtp:
+        enabled: ${outgoing_mail_enabled}
+        user: "${outgoing_mail_smtp_user}"
+        password: "${outgoing_mail_smtp_password}"
+        host: "${outgoing_mail_smtp_host}"
+        port: "${outgoing_mail_smtp_port}"
+      kratosUrl: single-sign-on-kratos-admin
+      resources:
+        limits:
+          cpu: 200m
+          memory: 512Mi
+        requests:
+          cpu: 100m
+          memory: 256Mi
+    ingress:
+      certManager: true
+      enabled: true
+      hostname: dashboard.${domain}
+      tls:
+      - hosts:
+        - dashboard.${domain}
+        secretName: stackspin-dashboard
diff --git a/flux2/core/base/dashboard/kustomization.yaml b/flux2/core/base/dashboard/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..93dd5096287c3350486e636d8b0ad98285569ca8
--- /dev/null
+++ b/flux2/core/base/dashboard/kustomization.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: stackspin
+resources:
+  - ./dashboard-release.yaml
+  - ./dashboard-values-configmap.yaml
diff --git a/flux2/core/base/single-sign-on/single-sign-on-values-configmap.yaml b/flux2/core/base/single-sign-on/single-sign-on-values-configmap.yaml
index 6fb10974384acf90bf346a8ec5c2e4860e701591..2a75dba82c0da1014bde3215a322a6eafa168fa7 100644
--- a/flux2/core/base/single-sign-on/single-sign-on-values-configmap.yaml
+++ b/flux2/core/base/single-sign-on/single-sign-on-values-configmap.yaml
@@ -28,6 +28,8 @@ data:
           description: "Wekan Kanban board."
         - name: &ZULIP zulip
           description: "Communicate and collaborate using team chat and switch to video or audio calls with screen sharing for more efficient teamwork."
+        - name: &DASHBOARD dashboard
+          description: "Stackspin dashboard."
       username: "${userbackend_admin_username}"
       password: "${userbackend_admin_password}"
       email: "${admin_email}"
@@ -180,6 +182,12 @@ data:
       scopes: "openid profile email"
       clientUri: "https://zulip.${domain}"
       clientLogoUri: "https://zulip.${domain}/static/images/zulip-logo.svg"
+    - clientName: *DASHBOARD
+      clientSecret: "${dashboard_oauth_client_secret}"
+      redirectUri: "https://dashboard.${domain}/_oauth/oidc"
+      scopes: "openid profile email"
+      clientUri: "https://dashboard.${domain}"
+      clientLogoUri: "https://dashboard.${domain}/assets/logo.svg"
       tokenEndpointAuthMethod: "client_secret_post"
       responseTypes:
         - "code"
diff --git a/flux2/infrastructure/sources/dashboard.yaml b/flux2/infrastructure/sources/dashboard.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e1358201759b14f16f216d86a7b0fa09b9c1bdda
--- /dev/null
+++ b/flux2/infrastructure/sources/dashboard.yaml
@@ -0,0 +1,11 @@
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: GitRepository
+metadata:
+  name: dashboard
+  namespace: stackspin
+spec:
+  interval: 1h
+  url: https://open.greenhost.net/stackspin/dashboard
+  ref:
+    tag: chart-0.1.1
diff --git a/flux2/infrastructure/sources/kustomization.yaml b/flux2/infrastructure/sources/kustomization.yaml
index c86eee7c82c3247b740e2aa36d3d6ec9e0c962a7..a689331ffaf49e9cd6678b3c58d2b58f7fdc5388 100644
--- a/flux2/infrastructure/sources/kustomization.yaml
+++ b/flux2/infrastructure/sources/kustomization.yaml
@@ -4,6 +4,7 @@ kind: Kustomization
 namespace: flux-system
 resources:
   - bitnami.yaml
+  - dashboard.yaml
   - grafana.yaml
   - ingress-nginx.yaml
   - jetstack.yaml
diff --git a/install/install-stackspin.sh b/install/install-stackspin.sh
index d151e0b09d6b4e505f08ed288e50a555791a6b3e..4a075e540c6573a331e838d3febc10a0e9621f32 100755
--- a/install/install-stackspin.sh
+++ b/install/install-stackspin.sh
@@ -32,6 +32,7 @@ kubectl get namespace stackspin 2>/dev/null || kubectl create namespace stackspi
 kubectl get namespace stackspin-apps 2>/dev/null || kubectl create namespace stackspin-apps
 
 # Generate oauth and SSO secrets
+python "$(dirname "$0")/generate_secrets.py" dashboard
 python "$(dirname "$0")/generate_secrets.py" single-sign-on
 python "$(dirname "$0")/generate_secrets.py" oauth
 
diff --git a/install/templates/stackspin-dashboard-variables.yaml.jinja b/install/templates/stackspin-dashboard-variables.yaml.jinja
new file mode 100644
index 0000000000000000000000000000000000000000..133b37f4f5ef2333fec8333406aa2fd2ca5ff582
--- /dev/null
+++ b/install/templates/stackspin-dashboard-variables.yaml.jinja
@@ -0,0 +1,7 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  name: stackspin-dashboard-variables
+data:
+  backend_secret_key: "{{ 32 | generate_password | b64encode }}"
+  backend_password: "{{ 32 | generate_password | b64encode }}"
diff --git a/install/templates/stackspin-oauth-variables.yaml.jinja b/install/templates/stackspin-oauth-variables.yaml.jinja
index 4c869b10d07aff8f3f05413308f83b9b51d30c00..b609a0f90624dd6d28d0b8ea921d8ee3a25bffa1 100644
--- a/install/templates/stackspin-oauth-variables.yaml.jinja
+++ b/install/templates/stackspin-oauth-variables.yaml.jinja
@@ -11,3 +11,4 @@ data:
   wekan_oauth_client_secret: "{{ 32 | generate_password | b64encode }}"
   wordpress_oauth_client_secret: "{{ 32 | generate_password | b64encode }}"
   zulip_oauth_client_secret: "{{ 32 | generate_password | b64encode }}"
+  dashboard_oauth_client_secret: "{{ 32 | generate_password | b64encode }}"
diff --git a/test/pytest/test_certs.py b/test/pytest/test_certs.py
index a4c9f4219f48c878ba7ca37b7199d4414fe6b8d0..95d31e861109ad00dc29fa0eddfb86c7e182ef36 100755
--- a/test/pytest/test_certs.py
+++ b/test/pytest/test_certs.py
@@ -95,10 +95,10 @@ def test_cert_validation(host, resource): # pylint: disable=too-many-statements
 
 
     app_subdomains = {
+        'dashboard': 'dashboard',
         'grafana': 'grafana',
         'nextcloud': 'files',
         'onlyoffice': 'office',
-        # prometheus is only exposed if opted-in in the settings
         'prometheus': 'prometheus',
         'rocketchat': 'chat',
         'single-sign-on': 'sso',
diff --git a/test/taiko/apps.js b/test/taiko/apps.js
index 8975afcc48e4b36c7459a3bd78bc084f081a0fad..0126ce49c00746c2835a38b706e210d82a658b2b 100644
--- a/test/taiko/apps.js
+++ b/test/taiko/apps.js
@@ -180,9 +180,8 @@ const assert = require('assert');
 
     // Dashboard
     if (taikoTests.includes('dashboard') || taikoTests === 'all') {
-      const dashboardUrl = 'https://admin.' + domain
-
-      console.log('• Dashboard')
+      const dashboardUrl = 'https://dashboard.' + domain
+      console.log('• Dashboard at ' + dashboardUrl)
       await goto(dashboardUrl)
     }