diff --git a/.circleci/config.yml b/.circleci/config.yml
index 8a24d49..76369af 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -20,18 +20,9 @@ jobs:
     steps:
       - checkout
       - run:
-          name: Install Kind
+          name: Install dependencies
           command: |
-            curl -Lo ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.11.1/kind-linux-amd64
-            chmod +x ./kind
-            sudo mv ./kind /usr/local/bin/kind
-      - run:
-          name: Install kubectl
-          command: |
-            curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
-            chmod +x ./kubectl
-            sudo mv ./kubectl /usr/local/bin/kubectl
-            export KUBECONFIG=$(kind get kubeconfig-path)
+            make deps
       - run:
           name: Run integration tests
           command: make test-integration
diff --git a/.deps/k3d.yaml b/.deps/k3d.yaml
new file mode 100644
index 0000000..d51a826
--- /dev/null
+++ b/.deps/k3d.yaml
@@ -0,0 +1,2 @@
+version: v5.4.9
+url: https://github.com/rancher/k3d/releases/download/{{.Version}}/k3d-{{.Os}}-{{.Architecture}}
diff --git a/.deps/kubectl.yaml b/.deps/kubectl.yaml
new file mode 100644
index 0000000..9e9ca31
--- /dev/null
+++ b/.deps/kubectl.yaml
@@ -0,0 +1,2 @@
+version: v1.26.5
+url: https://storage.googleapis.com/kubernetes-release/release/{{.Version}}/bin/{{.Os}}/{{.Architecture}}/kubectl
diff --git a/.deps/kustomize.yaml b/.deps/kustomize.yaml
new file mode 100644
index 0000000..d198504
--- /dev/null
+++ b/.deps/kustomize.yaml
@@ -0,0 +1,2 @@
+version: v5.0.3
+url: https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F{{.Version}}/kustomize_{{.Version}}_{{.Os}}_{{.Architecture}}.tar.gz
diff --git a/.github/actions/deps-setup/action.yaml b/.github/actions/deps-setup/action.yaml
new file mode 100644
index 0000000..18b99f1
--- /dev/null
+++ b/.github/actions/deps-setup/action.yaml
@@ -0,0 +1,39 @@
+name: "Dependencies setup"
+description: "Sets up dependencies, uses cache to speedup execution"
+runs:
+  using: "composite"
+  steps:
+    - name: Extract branch name
+      shell: bash
+      run: |
+        echo "branch=$(echo ${GITHUB_REF#refs/heads/})" >> "$GITHUB_ENV"
+      id: extract_branch
+
+    - uses: actions/cache@v3
+      id: cache-packages
+      with:
+        path: |
+          ~/go/pkg/mod
+          ~/go/bin
+          ~/.config/helm
+          ~/.local/share/helm
+          ~/.cache/helm
+          ${{ github.workspace }}/.bin
+        key:
+          ${{ runner.os }}-${{ steps.extract_branch.outputs.branch }}-${{
+          hashFiles('**/go.sum', '.deps/*') }}
+        restore-keys: |
+          ${{ runner.os }}-${{ steps.extract_branch.outputs.branch }}-
+
+    - name: Setup dependencies
+      if: steps.cache-packages.outputs.cache-hit != 'true'
+      shell: bash
+      env:
+        HELM_INSTALL_DIR: ${{ github.workspace }}/.bin
+        HELM_PLUGINS: ${{ github.workspace }}/.bin/plugins
+        K3D_INSTALL_DIR: ${{ github.workspace }}/.bin
+      run: |
+        #Export .bin into PATH so k3d doesn't fail when installing
+        export PATH=".bin:$PATH"
+        echo "PATH=.bin:$PATH" >> $GITHUB_ENV
+        make deps
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..b58bace
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,129 @@
+name: CI
+on:
+  create:
+  push:
+    branches:
+      - "master"
+    tags:
+      - "v*"
+  pull_request:
+
+concurrency:
+  group: ci-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  dependencies:
+    name: Prepare Dependencies
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - uses: actions/setup-go@v3
+        with:
+          go-version: "1.20"
+      - name: Setup dependencies
+        uses: ./.github/actions/deps-setup
+
+  detect-repo-changes:
+    name: Detected Repo Changes
+    runs-on: ubuntu-latest
+    outputs:
+      code-changed: ${{ steps.filter.outputs.code }}
+      dockerfile-changed: ${{ steps.filter.outputs.docker }}
+      cicd-definition-changed: ${{ steps.filter.outputs.cicd-definitions }}
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - uses: dorny/paths-filter@v2.11.1
+        id: filter
+        with:
+          base: master
+          filters: |
+            code:
+              - 'api/**'
+              - 'config/**'
+              - 'controllers/**'
+              - 'helpers/**'
+              - 'hydra/**'
+              - 'go.mod'
+              - 'go.sum'
+              - '*.go'
+              - 'PROJECT'
+            docker:
+              - 'Dockerfile'
+              - 'Dockerfile-kubebuilder'
+            cicd-definitions:
+              - '.github/workflows/**'
+              - '.github/actions/**'
+
+  gha-lint:
+    name: Lint GithubAction files
+    if: |
+      needs.detect-repo-changes.outputs.cicd-definition-changed == 'true'
+    needs:
+      - detect-repo-changes
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: actionlint
+        id: actionlint
+        uses: raven-actions/actionlint@v1
+        with:
+          fail-on-error: true
+
+  test-build:
+    name: Compile and test
+    runs-on: ubuntu-latest
+    if: |
+      needs.detect-repo-changes.outputs.code-changed == 'true'
+    needs:
+      - detect-repo-changes
+      - dependencies
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: Checkout dependencies
+        uses: ./.github/actions/deps-setup
+      - name: Build
+        run: make manager
+      - name: Test
+        run: make test
+
+  test-integration:
+    name: Run integration tests
+    runs-on: ubuntu-latest
+    if: |
+      needs.detect-repo-changes.outputs.code-changed == 'true' ||
+      needs.detect-repo-changes.outputs.dockerfile-changed == 'true'
+    needs:
+      - detect-repo-changes
+      - dependencies
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: Checkout dependencies
+        uses: ./.github/actions/deps-setup
+      - uses: actions/setup-go@v4
+        with:
+          go-version: "1.20"
+          cache: false
+      - name: Test
+        run: make test-integration
+
+  test-docker:
+    name: Build docker image
+    runs-on: ubuntu-latest
+    if: |
+      needs.detect-repo-changes.outputs.dockerfile-changed == 'true'
+    needs:
+      - detect-repo-changes
+      - dependencies
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: Checkout dependencies
+        uses: ./.github/actions/deps-setup
+      - name: Test
+        run: make test-integration
diff --git a/Makefile b/Makefile
index 56c9f56..064b10a 100644
--- a/Makefile
+++ b/Makefile
@@ -11,10 +11,14 @@ else
 	endif
 	ifeq ($(UNAME_S),Darwin)
 		OS=darwin
-		ARCH=amd64
+		ifeq ($(shell uname -m),x86_64)
+			ARCH=amd64
+		endif
+		ifeq ($(shell uname -m),arm64)
+			ARCH=arm64
+		endif
 	endif
 endif
-
 ##@ Build Dependencies
 
 ## Location to install dependencies to
@@ -22,22 +26,63 @@ LOCALBIN ?= $(shell pwd)/.bin
 $(LOCALBIN):
 	mkdir -p $(LOCALBIN)
 
+SHELL=/bin/bash -euo pipefail
+
+export PATH := .bin:${PATH}
+export PWD := $(shell pwd)
+export K3SIMAGE := docker.io/rancher/k3s:v1.26.1-k3s1
 ## Tool Binaries
-KUSTOMIZE ?= $(LOCALBIN)/kustomize
 CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
 ENVTEST ?= $(LOCALBIN)/setup-envtest
 
 ## Tool Versions
-KUSTOMIZE_VERSION ?= v5.1.1
 CONTROLLER_TOOLS_VERSION ?= v0.11.3
 ENVTEST_K8S_VERSION = 1.26.1
 
-HELL=/bin/bash -o pipefail
 # Image URL to use all building/pushing image targets
 IMG ?= controller:latest
 
 run-with-cleanup = $(1) && $(2) || (ret=$$?; $(2) && exit $$ret)
 
+# find or download controller-gen
+# download controller-gen if necessary
+.PHONY: controller-gen
+controller-gen: $(CONTROLLER_GEN)
+$(CONTROLLER_GEN): $(LOCALBIN)
+	test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \
+	GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION)
+
+## Download envtest-setup locally if necessary.
+.PHONY: envtest
+envtest: $(ENVTEST)
+$(ENVTEST): $(LOCALBIN)
+	test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
+
+.bin/ory: Makefile
+	curl https://raw.githubusercontent.com/ory/meta/master/install.sh | bash -s -- -b .bin ory
+	touch .bin/ory
+
+.bin/kubectl: Makefile
+	@URL=$$(.bin/ory dev ci deps url -o ${OS} -a ${ARCH} -c .deps/kubectl.yaml); \
+	echo "Downloading 'kubectl' $${URL}...."; \
+	curl -Lo .bin/kubectl $${URL}; \
+	chmod +x .bin/kubectl;
+
+.bin/kustomize: Makefile
+	@URL=$$(.bin/ory dev ci deps url -o ${OS} -a ${ARCH} -c .deps/kustomize.yaml); \
+	echo "Downloading 'kustomize' $${URL}...."; \
+	curl -L $${URL} | tar -xmz -C .bin kustomize; \
+	chmod +x .bin/kustomize;
+
+.bin/k3d: Makefile
+	@URL=$$(.bin/ory dev ci deps url -o ${OS} -a ${ARCH} -c .deps/k3d.yaml); \
+	echo "Downloading 'k3d' $${URL}...."; \
+	curl -Lo .bin/k3d $${URL}; \
+	chmod +x .bin/k3d;
+
+.PHONY: deps
+deps: .bin/ory .bin/k3d .bin/kubectl .bin/kustomize
+
 .PHONY: all
 all: manager
 
@@ -46,36 +91,33 @@ all: manager
 test: manifests generate vet envtest
 	KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out
 
-# Start KIND pseudo-cluster
-.PHONY: kind-start
-kind-start:
-	kind create cluster
+.PHONY: k3d-up
+k3d-up:
+	k3d cluster create --image $${K3SIMAGE} ory \
+		--k3s-arg=--kube-apiserver-arg="enable-admission-plugins=NodeRestriction,ServiceAccount@server:0" \
+		--k3s-arg=feature-gates="NamespaceDefaultLabelName=true@server:0";
 
-# Stop KIND pseudo-cluster
-.PHONY: kind-stop
-kind-stop:
-	kind delete cluster
+.PHONY: k3d-down
+k3d-down:
+	k3d cluster delete ory || true
 
-# Deploy on KIND
-# Ensures the controller image is built, deploys the image to KIND cluster along with necessary configuration
-.PHONY: kind-deploy
-kind-deploy: manager manifests docker-build-notest kind-start kustomize
-	kubectl config set-context kind-kind
-	kind load docker-image controller:latest
+.PHONY: k3d-deploy
+k3d-deploy: manager manifests docker-build-notest k3d-up
+	kubectl config set-context k3d-ory
+	k3d image load controller:latest -c ory
 	kubectl apply -f config/crd/bases
-	$(KUSTOMIZE) build config/default | kubectl apply -f -
+	kustomize build config/default | kubectl apply -f -
 
-# private
-.PHONY: kind-test
-kind-test: kind-deploy
-	kubectl config set-context kind-kind
+.PHONY: k3d-test
+k3d-test: k3d-deploy
+	kubectl config set-context k3d-ory
 	go install github.com/onsi/ginkgo/ginkgo@latest
 	USE_EXISTING_CLUSTER=true ginkgo -v ./controllers/...
 
-# Run integration tests on local KIND cluster
+# Run integration tests on local cluster
 .PHONY: test-integration
 test-integration:
-	$(call run-with-cleanup, $(MAKE) kind-test, $(MAKE) kind-stop)
+	$(call run-with-cleanup, $(MAKE) k3d-test, $(MAKE) k3d-down)
 
 # Build manager binary
 .PHONY: manager
@@ -94,9 +136,9 @@ install: manifests
 
 # Deploy controller in the configured Kubernetes cluster in ~/.kube/config
 .PHONY: deploy
-deploy: manifests kustomize
+deploy: manifests
 	kubectl apply -f config/crd/bases
-	$(KUSTOMIZE) build config/default | kubectl apply -f -
+	kustomize build config/default | kubectl apply -f -
 
 # Generate manifests e.g. CRD, RBAC etc.
 .PHONY: manifests
@@ -134,35 +176,6 @@ docker-build: test docker-build-notest
 docker-push:
 	docker push ${IMG}
 
-## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading.
-KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"
-.PHONY: kustomize
-kustomize: $(KUSTOMIZE)
-$(KUSTOMIZE): $(LOCALBIN)
-	@if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \
-		echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \
-		rm -rf $(LOCALBIN)/kustomize; \
-	fi
-	test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) --output install_kustomize.sh && bash install_kustomize.sh $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); rm install_kustomize.sh; }
-
-# find or download controller-gen
-# download controller-gen if necessary
-.PHONY: controller-gen
-controller-gen: $(CONTROLLER_GEN)
-$(CONTROLLER_GEN): $(LOCALBIN)
-	test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \
-	GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION)
-
-## Download envtest-setup locally if necessary.
-.PHONY: envtest
-envtest: $(ENVTEST)
-$(ENVTEST): $(LOCALBIN)
-	test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
-
-.bin/ory: Makefile
-	curl https://raw.githubusercontent.com/ory/meta/master/install.sh | bash -s -- -b .bin ory v0.1.48
-	touch .bin/ory
-
 licenses: .bin/licenses node_modules  # checks open-source licenses
 	.bin/licenses
 
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 12aa650..18f8abf 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -1,6 +1,3 @@
-// Copyright © 2023 Ory Corp
-// SPDX-License-Identifier: Apache-2.0
-
 //go:build !ignore_autogenerated
 // +build !ignore_autogenerated
 
diff --git a/config/crd/bases/hydra.ory.sh_oauth2clients.yaml b/config/crd/bases/hydra.ory.sh_oauth2clients.yaml
index 91bae05..e896e94 100644
--- a/config/crd/bases/hydra.ory.sh_oauth2clients.yaml
+++ b/config/crd/bases/hydra.ory.sh_oauth2clients.yaml
@@ -116,8 +116,8 @@ spec:
                   type: object
                 jwksUri:
                   description:
-                    Define the URL where the JSON Web Key Set should be fetched
-                    from when performing the private_key_jwt client
+                    JwksUri Define the URL where the JSON Web Key Set should be
+                    fetched from when performing the private_key_jwt client
                     authentication method.
                   pattern: (^$|^https?://.*)
                   type: string
diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml
index 3b6b65f..619a50e 100644
--- a/config/default/kustomization.yaml
+++ b/config/default/kustomization.yaml
@@ -12,32 +12,20 @@ namePrefix: hydra-maester-
 #commonLabels:
 #  someName: someValue
 
-bases:
-  - ../crd
-  - ../rbac
-  - ../manager
 # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml
 #- ../webhook
 # [CERTMANAGER] To enable cert-manager, uncomment next line. 'WEBHOOK' components are required.
 #- ../certmanager
 
-patchesStrategicMerge:
-  - manager_image_patch.yaml
-    # Protect the /metrics endpoint by putting it behind auth.
-    # Only one of manager_auth_proxy_patch.yaml and
-    # manager_prometheus_metrics_patch.yaml should be enabled.
-  - manager_auth_proxy_patch.yaml
-    # If you want your controller-manager to expose the /metrics
-    # endpoint w/o any authn/z, uncomment the following line and
-    # comment manager_auth_proxy_patch.yaml.
-    # Only one of manager_auth_proxy_patch.yaml and
-    # manager_prometheus_metrics_patch.yaml should be enabled.
-#- manager_prometheus_metrics_patch.yaml
-
-# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml
-#- manager_webhook_patch.yaml
-
-# [CAINJECTION] Uncomment next line to enable the CA injection in the admission webhooks.
-# Uncomment 'CAINJECTION' in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
-# 'CERTMANAGER' needs to be enabled to use ca injection
-#- webhookcainjection_patch.yaml
+# Protect the /metrics endpoint by putting it behind auth.
+# Only one of manager_auth_proxy_patch.yaml and
+# manager_prometheus_metrics_patch.yaml should be enabled.
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+resources:
+  - ../crd
+  - ../rbac
+  - ../manager
+patches:
+  - path: manager_image_patch.yaml
+  - path: manager_auth_proxy_patch.yaml