diff --git a/Makefile b/Makefile
index acf86ad8020fd736451b8384d979ce6bf077f3c0..05af93f94b55883cc9b81dd506f292647f494368 100644
--- a/Makefile
+++ b/Makefile
@@ -18,6 +18,10 @@ cli: ## Install koolbox CLI (Contains all tools used in this project)
 cli-config: ## Configure koolbox CLI (To setup terraform values as well was tokens)
 	cd ./cli && make config
 
+.PHONY: validate
+validate:
+	./scripts/validate.sh
+
 .PHONY: deploy
 deploy: check-machine ## Deploy infrastructure on Hetzner Cloud
 	cd ./terraform && make apply
diff --git a/scripts/validate.sh b/scripts/validate.sh
new file mode 100755
index 0000000000000000000000000000000000000000..b20a22ef7523b0ba6bcee5998d91c1e2ad907c62
--- /dev/null
+++ b/scripts/validate.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env bash
+
+# This script downloads the Flux OpenAPI schemas, then it validates the
+# Flux custom resources and the kustomize overlays using kubeval.
+# This script is meant to be run locally and in CI before the changes
+# are merged on the main branch that's synced by Flux.
+
+# Copyright 2020 The Flux authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script is meant to be run locally and in CI to validate the Kubernetes
+# manifests (including Flux custom resources) before changes are merged into
+# the branch synced by Flux in-cluster.
+
+# Prerequisites
+# - yq v4.6
+# - kustomize v4.1
+# - kubeval v0.15
+
+set -o errexit
+
+KUBERNETES_VERSION=1.21.8
+
+echo "INFO - Downloading Flux OpenAPI schemas"
+mkdir -p /tmp/flux-crd-schemas/master-standalone-strict
+curl -sL https://github.com/fluxcd/flux2/releases/latest/download/crd-schemas.tar.gz | tar zxf - -C /tmp/flux-crd-schemas/master-standalone-strict
+
+find . -type f -name '*.yaml' -print0 | while IFS= read -r -d $'\0' file;
+  do
+    echo "INFO - Validating $file"
+    yq e 'true' "$file" > /dev/null
+done
+
+echo "INFO - Validating clusters"
+find ./clusters -maxdepth 2 -type f -name '*.yaml' -print0 | while IFS= read -r -d $'\0' file;
+  do
+    kubeval ${file} --strict --ignore-missing-schemas --kubernetes-version ${KUBERNETES_VERSION} --additional-schema-locations=file:///tmp/flux-crd-schemas
+    if [[ ${PIPESTATUS[0]} != 0 ]]; then
+      exit 1
+    fi
+done
+
+# mirror kustomize-controller build options
+kustomize_flags="--load-restrictor=LoadRestrictionsNone --reorder=legacy"
+kustomize_config="kustomization.yaml"
+
+echo "INFO - Validating kustomize overlays"
+find . -type f -name $kustomize_config -print0 | while IFS= read -r -d $'\0' file;
+  do
+    echo "INFO - Validating kustomization ${file/%$kustomize_config}"
+    kustomize build "${file/%$kustomize_config}" $kustomize_flags | \
+      kubeval --ignore-missing-schemas --strict --kubernetes-version ${KUBERNETES_VERSION} --additional-schema-locations=file:///tmp/flux-crd-schemas
+    if [[ ${PIPESTATUS[0]} != 0 ]]; then
+      exit 1
+    fi
+done
+echo "INFO - Validation complete"