diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4b0b7a7f2d3f322d2815c83882e9af452b40a6df..a72f3db71e6472304aa7fbddb8ff75310daba584 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -263,7 +263,7 @@ create-vps:
     - *debug_information
     # Creates a VPS based on a custom CI image for which --install-kubernetes
     # has already run. See CONTRIBUTING.md#ci-pipeline-image for more info
-    - sh .gitlab/ci_scripts/create_vps.sh
+    - bash .gitlab/ci_scripts/create_vps.sh
     # Make sure .ci.env variables are not lost
     - cat .ci.env >> ${CLUSTER_DIR}/.cluster.env
   extends:
@@ -301,14 +301,17 @@ setup-openappstack:
     - *debug_information
     # Copy inventory files to ansible folder for use in install-apps step
     - chmod 700 ansible
-    - cp clusters/${CI_COMMIT_REF_SLUG}/inventory.yml ansible/
-    - cp clusters/${CI_COMMIT_REF_SLUG}/group_vars/all/settings.yml ansible/group_vars/all/
+    - cp ${CLUSTER_DIR}/inventory.yml ansible/
+    - cp ${CLUSTER_DIR}/group_vars/all/settings.yml ansible/group_vars/all/
     # Set up cluster
     # TODO: I set --no-install-openappstack to skip the old installation procedure, should be removed eventually
     - python3 -m openappstack $HOSTNAME install --install-kubernetes --no-install-openappstack
-    - scp -r ./install root@${FQDN}:/tmp/install
-    - ssh ${FQDN} -l root "/usr/bin/env /tmp/install/ci-write-variable-files.sh $IP_ADDRESS ${FQDN}"
-    - ssh ${FQDN} -l root "/usr/bin/env /tmp/install/install-openappstack.sh"
+    # Customize env file, remove all comments and empty lines
+    - sed "s/1.2.3.4/$IP_ADDRESS/; s/example.org/$FQDN/; s/acme_staging=false/acme_staging=true/; s/acme-v02/acme-staging-v02/; /^\s*#.*$/d; /^\s*$/d" install/.flux.env.example >> ${CLUSTER_DIR}/.flux.env
+    # Deploy secret/oas-cluster-variables
+    - cp install/kustomization.yaml ${CLUSTER_DIR}
+    - kubectl apply -k ${CLUSTER_DIR}
+    - bash ./install/install-openappstack.sh
     # TODO: Should also be removed or made up-to-date
     # Show versions of installed apps/binaries
     # - cd ansible
@@ -426,7 +429,7 @@ single-sign-on-helm-release:
   stage: install-apps
   script:
     - *debug_information
-    - ssh ${FQDN} -l root "/usr/bin/env /tmp/install/install-${APP}.sh"
+    - bash ./install/install-${APP}.sh
   extends:
     - .ssh_setup
   interruptible: true
diff --git a/Dockerfile b/Dockerfile
index 4b1d91e7be97a206c064549ee53d300d1ab235d7..56a5130d85b3211677c47d3c29b5c5e3ef4f9708 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,16 +1,20 @@
 FROM alpine:3.13.5
 
 LABEL name="OpenAppStack management"
-LABEL version="4.2"
+LABEL version="4.3"
 LABEL vendor1="Greenhost"
 
 
 # Download yq v4 from github until it's packaged in alpine > 3.13
 ADD https://github.com/mikefarah/yq/releases/download/v4.7.0/yq_linux_amd64 /usr/local/bin/yq
+ADD https://github.com/fluxcd/flux2/releases/download/v0.14.2/flux_0.14.2_linux_amd64.tar.gz /tmp/
+# Download kubectl until it's packaged in alpine > 3.14
+ADD https://dl.k8s.io/release/v1.21.0/bin/linux/amd64/kubectl /usr/local/bin/
 COPY ./test/pytest/le-staging-bundle.pem /usr/local/share/ca-certificates/le-staging-bundle.pem
 COPY ./requirements.txt /requirements.txt
 RUN \
   apk --no-cache add \
+    bash=5.1.0-r0 \
     cargo=~1.47.0-r2 \
     chromium=~86.0.4240.111-r0 \
     chromium-chromedriver=~86.0.4240.111-r0 \
@@ -31,4 +35,5 @@ RUN \
   chmod a+x /usr/local/bin/* && \
   update-ca-certificates && \
   pip install --no-cache-dir -r /requirements.txt && \
-  ln -s /usr/bin/python3 /usr/bin/python
+  ln -s /usr/bin/python3 /usr/bin/python && \
+  tar -xzf /tmp/flux*.tar.gz && mv ./flux /usr/local/bin
diff --git a/flux2/apps/nextcloud/release.yaml b/flux2/apps/nextcloud/release.yaml
index 6be2ea9447c59c29dbb9fcb7d54ebb287b9086ce..555d5d19bc60edf07be0fe2702624f492d2882b3 100644
--- a/flux2/apps/nextcloud/release.yaml
+++ b/flux2/apps/nextcloud/release.yaml
@@ -24,7 +24,7 @@ spec:
         password: "${nextcloud_password}"
         mail:
           enabled: ${outgoing_mail_enabled}
-          fromAddress: "${outgoing_mail_from_prefix}"
+          fromAddress: "${outgoing_mail_from_local_part}"
           domain: "${outgoing_mail_domain}"
           smtp:
             host: "${outgoing_mail_smtp_host}"
diff --git a/install/.flux.env.example b/install/.flux.env.example
new file mode 100755
index 0000000000000000000000000000000000000000..c1fe971ecbff12573234a6da6435a98c04d2da1d
--- /dev/null
+++ b/install/.flux.env.example
@@ -0,0 +1,35 @@
+ip_address=1.2.3.4
+domain=example.org
+# Needs to be a real email address (or at least not @example.com) for LE
+admin_email=admin@example.org
+
+# Outgoing mail: even though we disable it, we need values for them, because
+# Kustomize still wants to substitute them.
+outgoing_mail_enabled=false
+outgoing_mail_from_local_part=admin
+outgoing_mail_from_address=admin@example.org
+outgoing_mail_domain=oas.example.org
+outgoing_mail_smtp_password=CHANGEME
+# Example data for Greenhost SMTP login
+outgoing_mail_smtp_host=smtp.greenhost.nl
+outgoing_mail_smtp_authtype=LOGIN
+outgoing_mail_smtp_port=587
+outgoing_mail_smtp_user=info@example.org
+
+# ACME staging server address
+acme_server=https://acme-v02.api.letsencrypt.org/directory
+# Used to let some programs accept insecure certificates
+acme_staging=false
+# On development setups please use Letsencrypts staging API *AND*
+# set the `acme_staging` var to true
+# acme_server=https://acme-staging-v02.api.letsencrypt.org/directory
+# acme_staging=true
+
+
+# (Example) backup data
+backup_s3_bucket=oas.greenhost.net
+backup_s3_prefix=ci-prefix
+backup_s3_url=https://store.greenhost.net
+backup_s3_region=ceph
+backup_s3_aws_access_key_id=example-access-key-id
+backup_s3_aws_secret_access_key=example-secret-access-key
diff --git a/install/ci-write-variable-files.sh b/install/ci-write-variable-files.sh
deleted file mode 100755
index df4ff331a5320addd72a5ca2caffd5ba9a544963..0000000000000000000000000000000000000000
--- a/install/ci-write-variable-files.sh
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/bin/bash
-# Writes variables to files, then applies the kustomization that makes the
-# `oas-cluster-variables` secret that is needed by OAS installation
-
-ip_address=$1
-domain=$2
-
-echo "running with IP address: '$ip_address' and domain: '$domain'"
-
-cd "$( dirname "${BASH_SOURCE[0]}" )/installation-kustomization"
-
-
-echo "$domain" > domain
-# Needs to be a real email address (or at least not @example.com) for LE
-echo "info@openappstack.net" > admin_email
-
-# Outgoing mail: even though we disable it, we need values for them, because
-# Kustomize still wants to substitute them.
-echo "false" > outgoing_mail_enabled
-echo "admin@example.com" > outgoing_mail_from_address
-echo "admin" > outgoing_mail_from_prefix
-echo "example.com" > outgoing_mail_domain
-echo "example-password" > outgoing_mail_smtp_password
-
-# Example data for Greenhost SMTP login
-echo "smtp.greenhost.nl" > outgoing_mail_smtp_host
-echo "LOGIN" > outgoing_mail_smtp_authtype
-echo "587" > outgoing_mail_smtp_port
-echo "info@example.com" > outgoing_mail_smtp_user
-
-# ACME staging server address
-echo "https://acme-staging-v02.api.letsencrypt.org/directory" > acme_server
-# Used to let some programs accept insecure certificates
-echo "true" > acme_staging
-
-echo "$ip_address" > ip_address
-
-# (Example) backup data
-echo "oas.greenhost.net" > backup_s3_bucket
-echo "ci-prefix" > backup_s3_prefix
-echo "https://store.greenhost.net" > backup_s3_url
-echo "ceph" > backup_s3_region
-echo "example-access-key-id" > backup_s3_aws_access_key_id
-echo "example-secret-access-key" > backup_s3_aws_secret_access_key
-
-kubectl apply -k .
diff --git a/install/install-nextcloud.sh b/install/install-nextcloud.sh
index c515e9839af6e872b88bfc9ff16c29360267701c..e6aea0346665a8490e3afbb01bdafa28eda5b8ea 100755
--- a/install/install-nextcloud.sh
+++ b/install/install-nextcloud.sh
@@ -1,4 +1,6 @@
-#!/bin/bash
+#!/usr/bin/env bash
+
+set -euo pipefail
 
 # First, add some overrides for values that are only useful in CI
 kubectl apply -n oas-apps -f $( dirname "${BASH_SOURCE[0]}" )/nextcloud-values-override.yaml
diff --git a/install/install-openappstack.sh b/install/install-openappstack.sh
index 84000f331008f511962f61399876f4a76226b2d7..0b0efe56a6deb9dcff08900589bcef5854597331 100755
--- a/install/install-openappstack.sh
+++ b/install/install-openappstack.sh
@@ -1,4 +1,6 @@
-#!/bin/bash
+#!/usr/bin/env bash
+
+set -euo pipefail
 
 flux install \
   --network-policy=false \
diff --git a/install/install-rocketchat.sh b/install/install-rocketchat.sh
index 576e21582e4bf5d693e4b7cb7188a14e6c11efdb..a0364c459d12cea53eda938188c1358e0ae62e64 100755
--- a/install/install-rocketchat.sh
+++ b/install/install-rocketchat.sh
@@ -1,4 +1,6 @@
-#!/bin/bash
+#!/usr/bin/env bash
+
+set -euo pipefail
 
 # This kustomization's only purpose is to add the kustomization that is in the
 # flxu2/cluster/optional/rocketchat folder. After this kustomization is applied
diff --git a/install/install-velero.sh b/install/install-velero.sh
index 7b849b9785663a157fef850fa2fde1fc8bff3f07..3b85c1f773d7e6e127e70cc851432ffa516e5388 100755
--- a/install/install-velero.sh
+++ b/install/install-velero.sh
@@ -1,4 +1,6 @@
-#!/bin/bash
+#!/usr/bin/env bash
+
+set -euo pipefail
 
 # This kustomization's only purpose is to add the kustomization that is in the
 # flxu2/cluster/optional/velero folder. After this kustomization is applied
diff --git a/install/install-wordpress.sh b/install/install-wordpress.sh
index 40d93607b6dbe92980a0a78ba4a9f5e853f9d06b..2db41fdd637caf22844afca9d5355b5f07c9328a 100755
--- a/install/install-wordpress.sh
+++ b/install/install-wordpress.sh
@@ -1,4 +1,6 @@
-#!/bin/bash
+#!/usr/bin/env bash
+
+set -euo pipefail
 
 # This kustomization's only purpose is to add the kustomization that is in the
 # flxu2/cluster/optional/wordpress folder. After this kustomization is applied
diff --git a/install/installation-kustomization/kustomization.yaml b/install/installation-kustomization/kustomization.yaml
deleted file mode 100644
index a7c09083978bebbfdd794f6a167a7a992083cac4..0000000000000000000000000000000000000000
--- a/install/installation-kustomization/kustomization.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
----
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-namespace: flux-system
-secretGenerator:
-  - name: oas-cluster-variables
-    files:
-      - domain
-      - admin_email
-      - outgoing_mail_enabled
-      - outgoing_mail_domain
-      - outgoing_mail_from_address
-      - outgoing_mail_from_prefix
-      - outgoing_mail_smtp_host
-      - outgoing_mail_smtp_authtype
-      - outgoing_mail_smtp_password
-      - outgoing_mail_smtp_port
-      - outgoing_mail_smtp_user
-      - acme_staging
-      - acme_server
-      - ip_address
-      - backup_s3_bucket
-      - backup_s3_prefix
-      - backup_s3_url
-      - backup_s3_region
-      - backup_s3_aws_access_key_id
-      - backup_s3_aws_secret_access_key
-generatorOptions:
-  disableNameSuffixHash: true
diff --git a/install/kustomization.yaml b/install/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..90e20008f7e82da75428c88dae93d51df1c66daa
--- /dev/null
+++ b/install/kustomization.yaml
@@ -0,0 +1,10 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: flux-system
+secretGenerator:
+  - name: oas-cluster-variables
+    envs:
+      - .flux.env
+generatorOptions:
+  disableNameSuffixHash: true