diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1987e5bd2ee3429267520f4f4d823a41ac1b0c3a..12925a95f48d54b62e672a613d70928f0a5aeb12 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -17,17 +17,14 @@ variables:
 
 ci_test_image:
   stage: build
-  variables:
-    DOCKER_DRIVER: overlay2
-  image: docker:stable
-  services:
-    - docker:18-dind  # FIXME This is an older version of DIND. Update when gitlab-runner fixes https://gitlab.com/gitlab-org/gitlab-runner/issues/4501
-  before_script:
-    - docker info
+  image:
+    # We need a shell to provide the registry credentials, so we need to use the
+    # kaniko debug image (https://github.com/GoogleContainerTools/kaniko#debug-image)
+    name: gcr.io/kaniko-project/executor:debug
+    entrypoint: [""]
   script:
-    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
-    - docker build -t ${CI_REGISTRY_IMAGE}/openappstack-ci:${CI_COMMIT_REF_NAME} .
-    - docker push ${CI_REGISTRY_IMAGE}/openappstack-ci:${CI_COMMIT_REF_NAME}
+    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
+    - /kaniko/executor --context ${CI_PROJECT_DIR} --dockerfile ${CI_PROJECT_DIR}/Dockerfile --destination $CI_REGISTRY_IMAGE/openappstack-ci:${CI_COMMIT_REF_NAME}
   only:
     changes:
       - Dockerfile
@@ -55,6 +52,7 @@ bootstrap:
     when: always
   only:
     changes:
+      - .gitlab-ci.yml
       - ansible/**/*
       - helmfiles/**/*
       - test/**/*
@@ -81,6 +79,7 @@ install:
     expire_in: 1 month
   only:
     changes:
+      - .gitlab-ci.yml
       - ansible/**/*
       - helmfiles/**/*
       - test/**/*
@@ -97,6 +96,7 @@ testinfra:
     - pytest -v -m 'testinfra' --connection=ansible --ansible-inventory=../clusters/$HOSTNAME/inventory.yml --hosts='ansible://*'
   only:
     changes:
+      - .gitlab-ci.yml
       - ansible/**/*
       - helmfiles/**/*
       - test/**/*
@@ -116,6 +116,7 @@ certs:
     - pytest -s -m 'certs' --connection=ansible --ansible-inventory=../clusters/$HOSTNAME/inventory.yml --hosts='ansible://*'
   only:
     changes:
+      - .gitlab-ci.yml
       - ansible/**/*
       - helmfiles/**/*
       - test/**/*
@@ -134,6 +135,7 @@ behave-nextcloud:
   retry: 2
   only:
     changes:
+      - .gitlab-ci.yml
       - ansible/**/*
       - helmfiles/**/*
       - test/**/*
@@ -151,6 +153,7 @@ behave-grafana:
     when: on_failure
   only:
     changes:
+      - .gitlab-ci.yml
       - ansible/**/*
       - helmfiles/**/*
       - test/**/*
@@ -163,9 +166,10 @@ terminate:
     # Remove droplet after successful tests
     - echo "$CI_COMMIT_MESSAGE" | grep '!ci_dont_terminate' && echo 'Termination of droplet disabled in commit message.' || python3 -m openappstack $HOSTNAME --terminate
     # Remove droplet older than 2 days
-    - python3 -c "import openappstack.cosmos; openappstack.cosmos.terminate_droplets_by_name(\"^ci-\", 2)"
+    - python3 -c "import greenhost_cloud; greenhost_cloud.terminate_droplets_by_name(\"^ci-\", 2)"
   only:
     changes:
+      - .gitlab-ci.yml
       - ansible/**/*
       - helmfiles/**/*
       - test/**/*
diff --git a/ansible/bootstrap.yml b/ansible/bootstrap.yml
index 373023b399b7b1fa94b09eb6b09e2a9d40552403..4b23ebb7d69bdf9fcfeaa39dfb1dc6e58106c1e6 100644
--- a/ansible/bootstrap.yml
+++ b/ansible/bootstrap.yml
@@ -45,3 +45,7 @@
       tags: ['rke_configuration']
     - role: setup
       tags: ['setup']
+    - role: apps
+      tags: ['apps']
+    - role: finalize
+      tags: ['finalize']
diff --git a/ansible/group_vars/all/oas.yml b/ansible/group_vars/all/oas.yml
index 3f456b8fc42f8ac3ea2b794abb3c17fd2079cd3f..de7e23a91ae2432ac1ff771dbe434a9dd9e0b93b 100644
--- a/ansible/group_vars/all/oas.yml
+++ b/ansible/group_vars/all/oas.yml
@@ -16,10 +16,13 @@ nextcloud_mariadb_password: "{{ lookup('password', '{{ cluster_dir }}/secrets/ne
 nextcloud_mariadb_root_password: "{{ lookup('password', '{{ cluster_dir }}/secrets/nextcloud_mariadb_root_password chars=ascii_letters') }}"
 grafana_admin_password: "{{ lookup('password', '{{ cluster_dir }}/secrets/grafana_admin_password chars=ascii_letters') }}"
 
+# Kubernetes version
+kubernetes_version: "v1.14.3-rancher1-1"
+
 # git repo versions
 git_charts_version: 'HEAD'
 git_local_storage_version: 'HEAD'
-git_nextcloud_version: '897c800f7a1d632784d8dc721f34362d4e789743'
+git_nextcloud_version: '21aac1909edf5dc84eae067a536a16e16ca897fb'
 
 # Application versions
 # https://github.com/kubernetes-sigs/krew/releases
@@ -39,3 +42,10 @@ rke:
   # Also possible:
   # checksum: 'sha256:https://github.com/rancher/rke/releases/download/v0.2.4/sha256sum.txt'
   checksum: 'sha256:7c05727aa3d6f8c4b5f60b057f1fe7883af48d5a778e3b1668f178dda84883ee'
+
+cert_manager:
+  version: '0.9.1'
+  # cert-manager requires custom resource definitions applied before installing
+  # the helm chart. See https://hub.helm.sh/charts/jetstack/cert-manager for
+  # details
+  crd_version: '0.9'
diff --git a/ansible/roles/setup/tasks/cert-manager.yml b/ansible/roles/apps/tasks/cert-manager.yml
similarity index 89%
rename from ansible/roles/setup/tasks/cert-manager.yml
rename to ansible/roles/apps/tasks/cert-manager.yml
index 5f47596b6604b7f9ab81d5e063b0a9b83ce53594..f538e72dc4e09a576998ddb67250b9738884ef12 100644
--- a/ansible/roles/setup/tasks/cert-manager.yml
+++ b/ansible/roles/apps/tasks/cert-manager.yml
@@ -2,7 +2,7 @@
 - name: Install CRDs for cert-manager
   tags:
     - cert-manager
-  command: /snap/bin/kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.8/deploy/manifests/00-crds.yaml
+  command: '/snap/bin/kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-{{ cert_manager.crd_version }}/deploy/manifests/00-crds.yaml'
 
 - name: Prevent validation deadlock for cert-manager
   tags:
diff --git a/ansible/roles/apps/tasks/helmfiles.yml b/ansible/roles/apps/tasks/helmfiles.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1097135203bd4703f8c56cf8196e7f52880adf77
--- /dev/null
+++ b/ansible/roles/apps/tasks/helmfiles.yml
@@ -0,0 +1,28 @@
+---
+- name: Clone nextcloud repo
+  tags:
+    - git
+  git:
+    repo: 'https://open.greenhost.net/openappstack/nextcloud'
+    dest: '{{ data_directory }}/source/repos/nextcloud'
+    version: '{{ git_nextcloud_version }}'
+
+- name: Clone local-storage repo
+  tags:
+    - git
+  git:
+    repo: 'https://open.greenhost.net/openappstack/local-storage'
+    dest: '{{ data_directory }}/source/repos/local-storage'
+    version: '{{ git_local_storage_version }}'
+
+- name: Apply helmfiles
+  tags:
+    - helmfile
+  environment:
+    - NEXTCLOUD_PASSWORD: "{{ nextcloud_password }}"
+    - NEXTCLOUD_MARIADB_PASSWORD: "{{ nextcloud_mariadb_password }}"
+    - NEXTCLOUD_MARIADB_ROOT_PASSWORD: "{{ nextcloud_mariadb_root_password }}"
+    - GRAFANA_ADMIN_PASSWORD: "{{ grafana_admin_password }}"
+  shell: /usr/local/bin/helmfile -b /snap/bin/helm -e oas -f {{ data_directory }}/source/helmfiles/helmfile.d/{{ item }}.yaml apply --suppress-secrets | sed 's/\x1B\[[0-9;]*[JKmsu]//g' >> {{ log_directory }}/helmfile.log
+  with_items: "{{ helmfiles }}"
+  when: item is not search("cert-manager")
diff --git a/ansible/roles/setup/tasks/helmfiles.yml b/ansible/roles/apps/tasks/init.yml
similarity index 59%
rename from ansible/roles/setup/tasks/helmfiles.yml
rename to ansible/roles/apps/tasks/init.yml
index 7fa0494d3a78178e03bad6a1e0d0c7acf9f43576..07ff14dedabcb4b2f966ade8d8086207e1d7a599 100644
--- a/ansible/roles/setup/tasks/helmfiles.yml
+++ b/ansible/roles/apps/tasks/init.yml
@@ -20,22 +20,6 @@
     version: '{{ git_charts_version }}'
   become: true
 
-- name: Clone nextcloud repo
-  tags:
-    - git
-  git:
-    repo: 'https://open.greenhost.net/openappstack/nextcloud'
-    dest: '{{ data_directory }}/source/repos/nextcloud'
-    version: '{{ git_nextcloud_version }}'
-
-- name: Clone local-storage repo
-  tags:
-    - git
-  git:
-    repo: 'https://open.greenhost.net/openappstack/local-storage'
-    dest: '{{ data_directory }}/source/repos/local-storage'
-    version: '{{ git_local_storage_version }}'
-
 - name: Create OAS namespaces
   tags:
     - kubernetes
@@ -85,15 +69,3 @@
     - helmfile
   shell: 'helm ls --failed --short | xargs -L1 helm delete --purge'
   when: helm_failed_deployments.stdout != ""
-
-- name: Apply helmfiles
-  tags:
-    - helmfile
-    - tmp
-  environment:
-    - NEXTCLOUD_PASSWORD: "{{ nextcloud_password }}"
-    - NEXTCLOUD_MARIADB_PASSWORD: "{{ nextcloud_mariadb_password }}"
-    - NEXTCLOUD_MARIADB_ROOT_PASSWORD: "{{ nextcloud_mariadb_root_password }}"
-    - GRAFANA_ADMIN_PASSWORD: "{{ grafana_admin_password }}"
-  command: "/usr/local/bin/helmfile -b /snap/bin/helm -e oas -f {{ data_directory }}/source/helmfiles/helmfile.d/{{ item }}.yaml apply --suppress-secrets"
-  with_items: "{{ helmfiles }}"
diff --git a/ansible/roles/apps/tasks/main.yml b/ansible/roles/apps/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a366c9362eafedbb90090cb7ffea89272789066d
--- /dev/null
+++ b/ansible/roles/apps/tasks/main.yml
@@ -0,0 +1,4 @@
+---
+- import_tasks: init.yml
+- import_tasks: cert-manager.yml
+- import_tasks: helmfiles.yml
diff --git a/ansible/roles/finalize/tasks/main.yml b/ansible/roles/finalize/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7c1b9f9ca7461396b49d4e5e76344689bed3efac
--- /dev/null
+++ b/ansible/roles/finalize/tasks/main.yml
@@ -0,0 +1,2 @@
+---
+- import_tasks: sync_cluster_data.yml
diff --git a/ansible/roles/finalize/tasks/sync_cluster_data.yml b/ansible/roles/finalize/tasks/sync_cluster_data.yml
new file mode 100644
index 0000000000000000000000000000000000000000..33ea808939cb0043951a791cac18143f53ad9e82
--- /dev/null
+++ b/ansible/roles/finalize/tasks/sync_cluster_data.yml
@@ -0,0 +1,20 @@
+---
+- name: Copy cluster information to local folder
+  tags:
+    - fetch
+    - rke
+    - kubectl
+    - helmfile
+  fetch:
+    src: "{{ item.src }}"
+    dest: "{{ item.dest }}"
+    flat: yes
+  loop:
+    - src: "{{ data_directory }}/rke/kube_config_cluster.yml"
+      dest: "{{ secret_directory }}/kube_config_cluster.yml"
+    - src: "{{ log_directory }}/rke.log"
+      dest: cluster_data/rke.log
+    - src: "{{ data_directory }}/rke/cluster.yml"
+      dest: cluster_data/rke_cluster.yml
+    - src: "{{ log_directory }}/helmfile.log"
+      dest: cluster_data/helmfile.log
diff --git a/ansible/roles/rke_configuration/templates/cluster.yml.j2 b/ansible/roles/rke_configuration/templates/cluster.yml.j2
index e21b533d8c2798fef0726f314ca098c52cf6c08e..617ec446509ac115d64eb7b6def0bbf491a59079 100644
--- a/ansible/roles/rke_configuration/templates/cluster.yml.j2
+++ b/ansible/roles/rke_configuration/templates/cluster.yml.j2
@@ -72,7 +72,7 @@ authorization:
   mode: rbac
   options: {}
 ignore_docker_version: false
-kubernetes_version: "v1.13.5-rancher1-3"
+kubernetes_version: {{ kubernetes_version }}
 private_registries: []
 ingress:
   # Set this to none, so we can install nginx ourselves.
diff --git a/ansible/roles/setup/tasks/main.yml b/ansible/roles/setup/tasks/main.yml
index f630839baf5eb6f7ab8250d9f3adebee43c62f74..4ac4ed4cfafb0824d1a1d98fc658db63cb40829b 100644
--- a/ansible/roles/setup/tasks/main.yml
+++ b/ansible/roles/setup/tasks/main.yml
@@ -2,6 +2,4 @@
 - import_tasks: ssh.yml
 - import_tasks: rke.yml
 - import_tasks: tiller.yml
-- import_tasks: cert-manager.yml
-- import_tasks: helmfiles.yml
 - import_tasks: krew.yml
diff --git a/ansible/roles/setup/tasks/rke.yml b/ansible/roles/setup/tasks/rke.yml
index 2d577cc7569ac7971a3056386aed94b499419c17..8ea84379465b7c5f6d5518332d3df94933d21613 100644
--- a/ansible/roles/setup/tasks/rke.yml
+++ b/ansible/roles/setup/tasks/rke.yml
@@ -1,4 +1,13 @@
 ---
+
+- name: Check if there is an cluster.rkestate at the old location (/oas/control/local/rke/)
+  stat: path=/oas/control/local/rke/cluster.rkestate
+  register: old_cluster_rkestate
+
+- name: Move rke cluster state file from old to new location
+  command: mv /oas/control/local/rke/cluster.rkestate /var/lib/OpenAppStack/rke/cluster.rkestate
+  when: old_cluster_rkestate.stat.exists
+
 - name: Build Cluster
   tags:
     - rke
@@ -20,20 +29,3 @@
     state: link
     src: "{{ data_directory }}/rke/kube_config_cluster.yml"
     dest: /root/.kube/config
-
-- name: Copy cluster information to local folder
-  tags:
-    - fetch
-    - rke
-    - kubectl
-  fetch:
-    src: "{{ item.src }}"
-    dest: "{{ item.dest }}"
-    flat: yes
-  loop:
-    - src: "{{ data_directory }}/rke/kube_config_cluster.yml"
-      dest: "{{ cluster_dir }}/secrets/kube_config_cluster.yml"
-    - src: "{{ log_directory }}/rke.log"
-      dest: "{{ cluster_dir }}/rke.log"
-    - src: "{{ data_directory }}/rke/cluster.yml"
-      dest: "{{ cluster_dir }}/rke_cluster.yml"
diff --git a/helmfiles/helmfile.d/05-cert-manager.yaml b/helmfiles/helmfile.d/05-cert-manager.yaml
index 6b75e33e96129a4985eac4daf27376109e25fbfc..77c077926e32c21b5dfd36fc7a027674760ff2e9 100644
--- a/helmfiles/helmfile.d/05-cert-manager.yaml
+++ b/helmfiles/helmfile.d/05-cert-manager.yaml
@@ -11,7 +11,7 @@ releases:
   - name: "oas-{{ .Environment.Values.releaseName }}-cert-manager"
     namespace: "cert-manager"
     chart: "jetstack/cert-manager"
-    version: "0.9.1"
+    version: '{{ cert_manager.version }}'
     values:
     - "../values/cert-manager.yaml.gotmpl"
     wait: false
diff --git a/helmfiles/helmfile.d/15-monitoring.yaml b/helmfiles/helmfile.d/15-monitoring.yaml
index 221d81d3bbdc7b0954bd9fa35230218e135277cc..971134a83c931715d3e9fde93f0f3dd44fa9cccf 100644
--- a/helmfiles/helmfile.d/15-monitoring.yaml
+++ b/helmfiles/helmfile.d/15-monitoring.yaml
@@ -7,6 +7,7 @@ releases:
   - name: "oas-{{ .Environment.Values.releaseName }}-prometheus"
     namespace: "oas"
     chart: "stable/prometheus-operator"
+    version: 5.15.0
     values:
     - "../values/prometheus.yaml.gotmpl"
     - "/etc/OpenAppStack/values/apps/prometheus.yaml.gotmpl"
diff --git a/requirements.txt b/requirements.txt
index ecfc81d807db2967226e0221b2d27dd32501095a..377c7c9f97eac1d0c40fe4289561fc668c3902f9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,7 +7,6 @@ openshift>=0.8.6
 # Needed for testinfra using the ansible module
 paramiko
 psutil>=5.5.0
-pycurl>=7.43.0.2
 pyopenssl>=19.0.0
 pytest>=4.3.0
 requests>=2.19.1
@@ -16,4 +15,4 @@ testinfra>=3.0.0
 setuptools>=40.6.2
 wheel>=0.33.1
 pytz>=2019.1
-
+-e git+https://open.greenhost.net/greenhost/cloud-api#egg=greenhost_cloud