Repository: cozystack/cozystack

Issue:
LINSTOR should automatically reload new certificates
klin@asus:~$ linstor n l

```
+--------------------------------------------------------+
| Node | NodeType  | Addresses                 | State   |
|========================================================|
| srv1 | SATELLITE | 192.168.100.11:3367 (SSL) | OFFLINE |
| srv2 | SATELLITE | 192.168.100.12:3367 (SSL) | Online  |
| srv3 | SATELLITE | 192.168.100.13:3367 (SSL) | Online  |
+--------------------------------------------------------+
```

logs on 1st sattelite:
```
8:28:59.315 [SSLNetComService] ERROR LINSTOR/Satellite - SYSTEM - Peer 167.94.138.49:50694: SSL error: no cipher suites in common
00:43:54.056 [SSLNetComService] ERROR LINSTOR/Satellite - SYSTEM - Peer 162.142.125.214:56050: SSL error: Empty client certificate chain
00:43:54.616 [SSLNetComService] ERROR LINSTOR/Satellite - SYSTEM - Peer 162.142.125.214:56064: SSL error: Empty client certificate chain
00:43:59.337 [SSLNetComService] ERROR LINSTOR/Satellite - SYSTEM - Peer 162.142.125.214:56094: SSL error: Unrecognized SSL message, plaintext connection?
00:44:01.115 [SSLNetComService] ERROR LINSTOR/Satellite - SYSTEM - Peer 162.142.125.214:43896: SSL error: Empty server certificate chain
00:44:03.240 [SSLNetComService] ERROR LINSTOR/Satellite - SYSTEM - Peer 162.142.125.214:43906: SSL error: The client supported protocol versions [TLSv1.1, TLSv1] are not accepted by server preferences [TLS13, TLS12]
00:44:05.025 [SSLNetComService] ERROR LINSTOR/Satellite - SYSTEM - Peer 162.142.125.214:43908: SSL error: The client supported protocol versions [TLSv1] are not accepted by server preferences [TLS13, TLS12]
00:44:06.308 [SSLNetComService] ERROR LINSTOR/Satellite - SYSTEM - Peer 162.142.125.214:43954: SSL error: Client requested protocol SSLv3 is not enabled or supported in server context
01:15:00.476 [MainWorkerPool-17] INFO  LINSTOR/Satellite - SYSTEM - SpaceInfo: data -> 1443386244/1862270976
01:15:00.477 [MainWorkerPool-17] INFO  LINSTOR/Satellite - SYSTEM - SpaceInfo: DfltDisklessStorPool -> 9223372036854775807/9223372036854775807
01:15:00.477 [MainWorkerPool-17] INFO  LINSTOR/Satellite - SYSTEM - Storage pool data reports capacity 1862270976 kiB, allocated space 1443386244 kiB
01:15:00.477 [MainWorkerPool-17] INFO  LINSTOR/Satellite - SYSTEM - SpaceTracking: Satellite aggregate capacity is 1862270976 kiB, allocated capacity is 418884732 kiB, no errors
08:20:55.539 [SSLNetComService] ERROR LINSTOR/Satellite - SYSTEM - Peer 137.184.85.175:40138: SSL error: Unrecognized SSL message, plaintext connection?
08:47:18.691 [SSLNetComService] ERROR LINSTOR/Satellite - SYSTEM - Peer 45.227.254.49:65156: SSL error: Unrecognized SSL message, plaintext connection?
09:16:57.162 [MainWorkerPool-18] INFO  LINSTOR/Satellite - SYSTEM - LogArchive: Running log archive on directory: /var/log/linstor-satellite
09:16:57.166 [MainWorkerPool-18] INFO  LINSTOR/Satellite - SYSTEM - LogArchive: No logs to archive.
10:03:52.072 [SSLNetComService] ERROR LINSTOR/Satellite - SYSTEM - Peer 137.184.85.175:47146: SSL error: Empty client certificate chain
10:54:46.224 [SSLNetComService] ERROR LINSTOR/Satellite - SYSTEM - Peer 162.142.125.214:44188: SSL error: Empty client certificate chain
```

after restart 1st sattelite:
```
klin@asus:~$ linstor n l
+-------------------------------------------------------+
| Node | NodeType  | Addresses                 | State  |
|=======================================================|
| srv1 | SATELLITE | 192.168.100.11:3367 (SSL) | Online |
| srv2 | SATELLITE | 192.168.100.12:3367 (SSL) | Online |
| srv3 | SATELLITE | 192.168.100.13:3367 (SSL) | Online |
+-------------------------------------------------------+
```

need healthcheck or something anything

Discussion:
@dosubot: <!-- Greeting -->
Hey @klinch0, I'm here to help you with that linstor satellite bug. I'm a bot designed to assist you with bug fixes, answer your questions, and help you become a contributor. Let's tackle this together!

<!-- Answer -->
It seems like the issue with the LINSTOR Satellite going offline was related to SSL errors, specifically "no cipher suites in common" and "Empty client certificate chain." Restarting the satellite resolved the issue temporarily, but implementing a health check mechanism could help detect and address such issues automatically in the future.

Unfortunately, I couldn't find specific information about a built-in health check feature in the LINSTOR Satellite codebase. However, you might consider setting up an external monitoring tool to periodically check the status of your nodes and alert you if any go offline. Additionally, reviewing your SSL configuration to ensure compatibility between nodes could help prevent these errors from occurring.


<!-- Dosu Comment Footer -->
*To continue talking to [Dosu](https://dosu.dev), mention @dosu.*
@lllamnyp: We could work around this easily enough with [stakater/Reloader](https://github.com/stakater/Reloader).

Solution patch:
diff --git a/packages/apps/kubernetes/images/kubevirt-cloud-provider.tag b/packages/apps/kubernetes/images/kubevirt-cloud-provider.tag
index 28af4247..a2b34327 100644
--- a/packages/apps/kubernetes/images/kubevirt-cloud-provider.tag
+++ b/packages/apps/kubernetes/images/kubevirt-cloud-provider.tag
@@ -1 +1 @@
-ghcr.io/cozystack/cozystack/kubevirt-cloud-provider:0.15.2@sha256:5e054eae6274963b6e84f87bf3330c94325103c6407b08bfb1189da721333b5c
+ghcr.io/cozystack/cozystack/kubevirt-cloud-provider:latest@sha256:a2739ab1a400664207c96ca60d88004ce244dcbb81204a32a517ff59d0061459
diff --git a/packages/apps/kubernetes/images/kubevirt-cloud-provider/Dockerfile b/packages/apps/kubernetes/images/kubevirt-cloud-provider/Dockerfile
index a6485ba6..97760bd7 100644
--- a/packages/apps/kubernetes/images/kubevirt-cloud-provider/Dockerfile
+++ b/packages/apps/kubernetes/images/kubevirt-cloud-provider/Dockerfile
@@ -3,12 +3,11 @@ FROM --platform=linux/amd64 golang:1.20.6 AS builder
 
 RUN git clone https://github.com/kubevirt/cloud-provider-kubevirt /go/src/kubevirt.io/cloud-provider-kubevirt \
  && cd /go/src/kubevirt.io/cloud-provider-kubevirt \
- && git checkout da9e0cf
+ && git checkout 443a1fe
 
 WORKDIR /go/src/kubevirt.io/cloud-provider-kubevirt
 
 # see: https://github.com/kubevirt/cloud-provider-kubevirt/pull/335
-# see: https://github.com/kubevirt/cloud-provider-kubevirt/pull/336
 ADD patches /patches
 RUN git apply /patches/*.diff
 RUN go get 'k8s.io/endpointslice/util@v0.28' 'k8s.io/apiserver@v0.28'
diff --git a/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/335.diff b/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/335.diff
index 03ef6ddb..375c6538 100644
--- a/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/335.diff
+++ b/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/335.diff
@@ -1,8 +1,8 @@
 diff --git a/pkg/controller/kubevirteps/kubevirteps_controller.go b/pkg/controller/kubevirteps/kubevirteps_controller.go
-index a3c1aa33..95c31438 100644
+index 6f6e3d32..53388eb8 100644
 --- a/pkg/controller/kubevirteps/kubevirteps_controller.go
 +++ b/pkg/controller/kubevirteps/kubevirteps_controller.go
-@@ -412,11 +412,11 @@ func (c *Controller) reconcileByAddressType(service *v1.Service, tenantSlices []
+@@ -474,11 +474,11 @@ func (c *Controller) reconcileByAddressType(service *v1.Service, tenantSlices []
  	// Create the desired port configuration
  	var desiredPorts []discovery.EndpointPort
  
@@ -18,3 +18,102 @@ index a3c1aa33..95c31438 100644
  		})
  	}
  
+diff --git a/pkg/controller/kubevirteps/kubevirteps_controller_test.go b/pkg/controller/kubevirteps/kubevirteps_controller_test.go
+index 1fb86e25..5326faa4 100644
+--- a/pkg/controller/kubevirteps/kubevirteps_controller_test.go
++++ b/pkg/controller/kubevirteps/kubevirteps_controller_test.go
+@@ -13,6 +13,7 @@ import (
+ 	"k8s.io/apimachinery/pkg/runtime"
+ 	"k8s.io/apimachinery/pkg/runtime/schema"
+ 	"k8s.io/apimachinery/pkg/util/intstr"
++	"k8s.io/apimachinery/pkg/util/sets"
+ 	dfake "k8s.io/client-go/dynamic/fake"
+ 	"k8s.io/client-go/kubernetes/fake"
+ 	"k8s.io/client-go/testing"
+@@ -643,6 +644,86 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 
+ 		})
+ 
++		g.It("Should correctly handle multiple unique ports in EndpointSlice", func() {
++			// Create a VMI in the infra cluster
++			createAndAssertVMI("worker-0-test", "ip-10-32-5-13", "123.45.67.89")
++
++			// Create an EndpointSlice in the tenant cluster
++			createAndAssertTenantSlice("test-epslice", "tenant-service-name", discoveryv1.AddressTypeIPv4,
++				*createPort("http", 80, v1.ProtocolTCP),
++				[]discoveryv1.Endpoint{*createEndpoint("123.45.67.89", "worker-0-test", true, true, false)})
++
++			// Define several unique ports for the Service
++			servicePorts := []v1.ServicePort{
++				{
++					Name:        "client",
++					Protocol:    v1.ProtocolTCP,
++					Port:        10001,
++					TargetPort:  intstr.FromInt(30396),
++					NodePort:    30396,
++					AppProtocol: nil,
++				},
++				{
++					Name:        "dashboard",
++					Protocol:    v1.ProtocolTCP,
++					Port:        8265,
++					TargetPort:  intstr.FromInt(31003),
++					NodePort:    31003,
++					AppProtocol: nil,
++				},
++				{
++					Name:        "metrics",
++					Protocol:    v1.ProtocolTCP,
++					Port:        8080,
++					TargetPort:  intstr.FromInt(30452),
++					NodePort:    30452,
++					AppProtocol: nil,
++				},
++			}
++
++			// Create a Service with the first port
++			createAndAssertInfraServiceLB("infra-multiport-service", "tenant-service-name", "test-cluster",
++				servicePorts[0],
++				v1.ServiceExternalTrafficPolicyLocal)
++
++			// Update the Service by adding the remaining ports
++			svc, err := testVals.infraClient.CoreV1().Services(infraNamespace).Get(context.TODO(), "infra-multiport-service", metav1.GetOptions{})
++			Expect(err).To(BeNil())
++
++			svc.Spec.Ports = servicePorts
++
++			_, err = testVals.infraClient.CoreV1().Services(infraNamespace).Update(context.TODO(), svc, metav1.UpdateOptions{})
++			Expect(err).To(BeNil())
++
++			var epsListMultiPort *discoveryv1.EndpointSliceList
++
++			// Verify that the EndpointSlice is created with correct unique ports
++			Eventually(func() (bool, error) {
++				epsListMultiPort, err = testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
++				if len(epsListMultiPort.Items) != 1 {
++					return false, err
++				}
++
++				createdSlice := epsListMultiPort.Items[0]
++				expectedPortNames := []string{"client", "dashboard", "metrics"}
++				foundPortNames := []string{}
++
++				for _, port := range createdSlice.Ports {
++					if port.Name != nil {
++						foundPortNames = append(foundPortNames, *port.Name)
++					}
++				}
++
++				// Verify that all expected ports are present and without duplicates
++				if len(foundPortNames) != len(expectedPortNames) {
++					return false, err
++				}
++
++				portSet := sets.NewString(foundPortNames...)
++				expectedPortSet := sets.NewString(expectedPortNames...)
++				return portSet.Equal(expectedPortSet), err
++			}).Should(BeTrue(), "EndpointSlice should contain all unique ports from the Service without duplicates")
++
+ 		g.It("Should reconcile after infra EndpointSlice deletion and restore it", func() {
+ 			// Create a VMI in the infra cluster
+ 			// This ensures that when tenant EndpointSlice is created, it can be reconciled properly
diff --git a/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/336.diff b/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/336.diff
deleted file mode 100644
index 985e456b..00000000
--- a/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/336.diff
+++ /dev/null
@@ -1,129 +0,0 @@
-diff --git a/pkg/controller/kubevirteps/kubevirteps_controller.go b/pkg/controller/kubevirteps/kubevirteps_controller.go
-index a3c1aa33..6f6e3d32 100644
---- a/pkg/controller/kubevirteps/kubevirteps_controller.go
-+++ b/pkg/controller/kubevirteps/kubevirteps_controller.go
-@@ -108,32 +108,24 @@ func newRequest(reqType ReqType, obj interface{}, oldObj interface{}) *Request {
- }
- 
- func (c *Controller) Init() error {
--
--	// Act on events from Services on the infra cluster. These are created by the EnsureLoadBalancer function.
--	// We need to watch for these events so that we can update the EndpointSlices in the infra cluster accordingly.
-+	// Existing Service event handlers...
- 	_, err := c.infraFactory.Core().V1().Services().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
- 		AddFunc: func(obj interface{}) {
--			// cast obj to Service
- 			svc := obj.(*v1.Service)
--			// Only act on Services of type LoadBalancer
- 			if svc.Spec.Type == v1.ServiceTypeLoadBalancer {
- 				klog.Infof("Service added: %v/%v", svc.Namespace, svc.Name)
- 				c.queue.Add(newRequest(AddReq, obj, nil))
- 			}
- 		},
- 		UpdateFunc: func(oldObj, newObj interface{}) {
--			// cast obj to Service
- 			newSvc := newObj.(*v1.Service)
--			// Only act on Services of type LoadBalancer
- 			if newSvc.Spec.Type == v1.ServiceTypeLoadBalancer {
- 				klog.Infof("Service updated: %v/%v", newSvc.Namespace, newSvc.Name)
- 				c.queue.Add(newRequest(UpdateReq, newObj, oldObj))
- 			}
- 		},
- 		DeleteFunc: func(obj interface{}) {
--			// cast obj to Service
- 			svc := obj.(*v1.Service)
--			// Only act on Services of type LoadBalancer
- 			if svc.Spec.Type == v1.ServiceTypeLoadBalancer {
- 				klog.Infof("Service deleted: %v/%v", svc.Namespace, svc.Name)
- 				c.queue.Add(newRequest(DeleteReq, obj, nil))
-@@ -144,7 +136,7 @@ func (c *Controller) Init() error {
- 		return err
- 	}
- 
--	// Monitor endpoint slices that we are interested in based on known services in the infra cluster
-+	// Existing EndpointSlice event handlers in tenant cluster...
- 	_, err = c.tenantFactory.Discovery().V1().EndpointSlices().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
- 		AddFunc: func(obj interface{}) {
- 			eps := obj.(*discovery.EndpointSlice)
-@@ -194,10 +186,80 @@ func (c *Controller) Init() error {
- 		return err
- 	}
- 
--	//TODO: Add informer for EndpointSlices in the infra cluster to watch for (unwanted) changes
-+	// Add an informer for EndpointSlices in the infra cluster
-+	_, err = c.infraFactory.Discovery().V1().EndpointSlices().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
-+		AddFunc: func(obj interface{}) {
-+			eps := obj.(*discovery.EndpointSlice)
-+			if c.managedByController(eps) {
-+				svc, svcErr := c.getInfraServiceForEPS(context.TODO(), eps)
-+				if svcErr != nil {
-+					klog.Errorf("Failed to get infra Service for EndpointSlice %s/%s: %v", eps.Namespace, eps.Name, svcErr)
-+					return
-+				}
-+				if svc != nil {
-+					klog.Infof("Infra EndpointSlice added: %v/%v, requeuing Service: %v/%v", eps.Namespace, eps.Name, svc.Namespace, svc.Name)
-+					c.queue.Add(newRequest(AddReq, svc, nil))
-+				}
-+			}
-+		},
-+		UpdateFunc: func(oldObj, newObj interface{}) {
-+			eps := newObj.(*discovery.EndpointSlice)
-+			if c.managedByController(eps) {
-+				svc, svcErr := c.getInfraServiceForEPS(context.TODO(), eps)
-+				if svcErr != nil {
-+					klog.Errorf("Failed to get infra Service for EndpointSlice %s/%s: %v", eps.Namespace, eps.Name, svcErr)
-+					return
-+				}
-+				if svc != nil {
-+					klog.Infof("Infra EndpointSlice updated: %v/%v, requeuing Service: %v/%v", eps.Namespace, eps.Name, svc.Namespace, svc.Name)
-+					c.queue.Add(newRequest(UpdateReq, svc, nil))
-+				}
-+			}
-+		},
-+		DeleteFunc: func(obj interface{}) {
-+			eps := obj.(*discovery.EndpointSlice)
-+			if c.managedByController(eps) {
-+				svc, svcErr := c.getInfraServiceForEPS(context.TODO(), eps)
-+				if svcErr != nil {
-+					klog.Errorf("Failed to get infra Service for EndpointSlice %s/%s on delete: %v", eps.Namespace, eps.Name, svcErr)
-+					return
-+				}
-+				if svc != nil {
-+					klog.Infof("Infra EndpointSlice deleted: %v/%v, requeuing Service: %v/%v", eps.Namespace, eps.Name, svc.Namespace, svc.Name)
-+					c.queue.Add(newRequest(DeleteReq, svc, nil))
-+				}
-+			}
-+		},
-+	})
-+	if err != nil {
-+		return err
-+	}
-+
- 	return nil
- }
- 
-+// getInfraServiceForEPS returns the Service in the infra cluster associated with the given EndpointSlice.
-+// It does this by reading the "kubernetes.io/service-name" label from the EndpointSlice, which should correspond
-+// to the Service name. If not found or if the Service doesn't exist, it returns nil.
-+func (c *Controller) getInfraServiceForEPS(ctx context.Context, eps *discovery.EndpointSlice) (*v1.Service, error) {
-+	svcName := eps.Labels[discovery.LabelServiceName]
-+	if svcName == "" {
-+		// No service name label found, can't determine infra service.
-+		return nil, nil
-+	}
-+
-+	svc, err := c.infraClient.CoreV1().Services(c.infraNamespace).Get(ctx, svcName, metav1.GetOptions{})
-+	if err != nil {
-+		if k8serrors.IsNotFound(err) {
-+			// Service doesn't exist
-+			return nil, nil
-+		}
-+		return nil, err
-+	}
-+
-+	return svc, nil
-+}
-+
- // Run starts an asynchronous loop that monitors and updates GKENetworkParamSet in the cluster.
- func (c *Controller) Run(numWorkers int, stopCh <-chan struct{}, controllerManagerMetrics *controllersmetrics.ControllerManagerMetrics) {
- 	defer utilruntime.HandleCrash()
diff --git a/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/337.diff b/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/337.diff
new file mode 100644
index 00000000..c1be90a2
--- /dev/null
+++ b/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/337.diff
@@ -0,0 +1,437 @@
+diff --git a/pkg/controller/kubevirteps/kubevirteps_controller.go b/pkg/controller/kubevirteps/kubevirteps_controller.go
+index 53388eb8..1ca419b9 100644
+--- a/pkg/controller/kubevirteps/kubevirteps_controller.go
++++ b/pkg/controller/kubevirteps/kubevirteps_controller.go
+@@ -382,74 +382,115 @@ func (c *Controller) reconcile(ctx context.Context, r *Request) error {
+ 		return errors.New("could not cast object to service")
+ 	}
+ 
++	/*
++	   Skip if the given Service is not labeled with the keys that indicate
++	   it was created/managed by this controller (i.e., not a LoadBalancer
++	   that we handle).
++	*/
+ 	if service.Labels[kubevirt.TenantServiceNameLabelKey] == "" ||
+ 		service.Labels[kubevirt.TenantServiceNamespaceLabelKey] == "" ||
+ 		service.Labels[kubevirt.TenantClusterNameLabelKey] == "" {
+ 		klog.Infof("This LoadBalancer Service: %s is not managed by the %s. Skipping.", service.Name, ControllerName)
+ 		return nil
+ 	}
++
+ 	klog.Infof("Reconciling: %v", service.Name)
+ 
++	/*
++	   1) Check if Service in the infra cluster is actually present.
++	      If it's not found, mark it as 'deleted' so that we don't create new slices.
++	*/
+ 	serviceDeleted := false
+-	svc, err := c.infraFactory.Core().V1().Services().Lister().Services(c.infraNamespace).Get(service.Name)
++	infraSvc, err := c.infraFactory.Core().V1().Services().Lister().Services(c.infraNamespace).Get(service.Name)
+ 	if err != nil {
+-		klog.Infof("Service %s in namespace %s is deleted.", service.Name, service.Namespace)
++		// The Service is not present in the infra lister => treat as deleted
++		klog.Infof("Service %s in namespace %s is deleted (or not found).", service.Name, service.Namespace)
+ 		serviceDeleted = true
+ 	} else {
+-		service = svc
++		// Use the actual object from the lister, so we have the latest state
++		service = infraSvc
+ 	}
+ 
++	/*
++	   2) Get all existing EndpointSlices in the infra cluster that belong to this LB Service.
++	      We'll decide which of them should be updated or deleted.
++	*/
+ 	infraExistingEpSlices, err := c.getInfraEPSFromInfraService(ctx, service)
+ 	if err != nil {
+ 		return err
+ 	}
+ 
+-	// At this point we have the current state of the 3 main objects we are interested in:
+-	// 1. The Service in the infra cluster, the one created by the KubevirtCloudController.
+-	// 2. The EndpointSlices in the tenant cluster, created for the tenant cluster's Service.
+-	// 3. The EndpointSlices in the infra cluster, managed by this controller.
+-
+ 	slicesToDelete := []*discovery.EndpointSlice{}
+ 	slicesByAddressType := make(map[discovery.AddressType][]*discovery.EndpointSlice)
+ 
++	// For example, if the service is single-stack IPv4 => only AddressTypeIPv4
++	// or if dual-stack => IPv4 and IPv6, etc.
+ 	serviceSupportedAddressesTypes := getAddressTypesForService(service)
+-	// If the services switched to a different address type, we need to delete the old ones, because it's immutable.
+-	// If the services switched to a different externalTrafficPolicy, we need to delete the old ones.
++
++	/*
++	   3) Determine which slices to delete, and which to pass on to the normal
++	      "reconcileByAddressType" logic.
++
++	      - If 'serviceDeleted' is true OR service.Spec.Selector != nil, we remove them.
++	      - Also, if the slice's address type is unsupported by the Service, we remove it.
++	*/
+ 	for _, eps := range infraExistingEpSlices {
+-		if service.Spec.Selector != nil || serviceDeleted {
+-			klog.Infof("Added for deletion EndpointSlice %s in namespace %s because it has a selector", eps.Name, eps.Namespace)
+-			// to be sure we don't delete any slice that is not managed by us
++		// If service is deleted or has a non-nil selector => remove slices
++		if serviceDeleted || service.Spec.Selector != nil {
++			/*
++			   Only remove if it is clearly labeled as managed by us:
++			   we do not want to accidentally remove slices that are not
++			   created by this controller.
++			*/
+ 			if c.managedByController(eps) {
++				klog.Infof("Added for deletion EndpointSlice %s in namespace %s because service is deleted or has a selector",
++					eps.Name, eps.Namespace)
+ 				slicesToDelete = append(slicesToDelete, eps)
+ 			}
+ 			continue
+ 		}
++
++		// If the Service does not support this slice's AddressType => remove
+ 		if !serviceSupportedAddressesTypes.Has(eps.AddressType) {
+-			klog.Infof("Added for deletion EndpointSlice %s in namespace %s because it has an unsupported address type: %v", eps.Name, eps.Namespace, eps.AddressType)
++			klog.Infof("Added for deletion EndpointSlice %s in namespace %s because it has an unsupported address type: %v",
++				eps.Name, eps.Namespace, eps.AddressType)
+ 			slicesToDelete = append(slicesToDelete, eps)
+ 			continue
+ 		}
++
++		/*
++		   Otherwise, this slice is potentially still valid for the given AddressType,
++		   we'll send it to reconcileByAddressType for final merging and updates.
++		*/
+ 		slicesByAddressType[eps.AddressType] = append(slicesByAddressType[eps.AddressType], eps)
+ 	}
+ 
+-	if !serviceDeleted {
+-		// Get tenant's endpoint slices for this service
++	/*
++	   4) If the Service was NOT deleted and has NO selector (i.e., it's a "no-selector" LB Service),
++	      we proceed to handle creation and updates. That means:
++	      - Gather Tenant's EndpointSlices
++	      - Reconcile them by each AddressType
++	*/
++	if !serviceDeleted && service.Spec.Selector == nil {
+ 		tenantEpSlices, err := c.getTenantEPSFromInfraService(ctx, service)
+ 		if err != nil {
+ 			return err
+ 		}
+ 
+-		// Reconcile the EndpointSlices for each address type e.g. ipv4, ipv6
++		// For each addressType (ipv4, ipv6, etc.) reconcile the infra slices
+ 		for addressType := range serviceSupportedAddressesTypes {
+ 			existingSlices := slicesByAddressType[addressType]
+-			err := c.reconcileByAddressType(service, tenantEpSlices, existingSlices, addressType)
+-			if err != nil {
++			if err := c.reconcileByAddressType(service, tenantEpSlices, existingSlices, addressType); err != nil {
+ 				return err
+ 			}
+ 		}
+ 	}
+ 
+-	// Delete the EndpointSlices that are no longer needed
++	/*
++	   5) Perform the actual deletion of all slices we flagged.
++	      In many cases (serviceDeleted or .Spec.Selector != nil),
++	      we end up with only "delete" actions and no new slice creation.
++	*/
+ 	for _, eps := range slicesToDelete {
+ 		err := c.infraClient.DiscoveryV1().EndpointSlices(eps.Namespace).Delete(context.TODO(), eps.Name, metav1.DeleteOptions{})
+ 		if err != nil {
+@@ -588,55 +629,114 @@ func ownedBy(endpointSlice *discovery.EndpointSlice, svc *v1.Service) bool {
+ 	return false
+ }
+ 
+-func (c *Controller) finalize(service *v1.Service, slicesToCreate []*discovery.EndpointSlice, slicesToUpdate []*discovery.EndpointSlice, slicesToDelete []*discovery.EndpointSlice) error {
+-	// If there are slices to delete and slices to create, make them as update
+-	for i := 0; i < len(slicesToDelete); {
++func (c *Controller) finalize(
++	service *v1.Service,
++	slicesToCreate []*discovery.EndpointSlice,
++	slicesToUpdate []*discovery.EndpointSlice,
++	slicesToDelete []*discovery.EndpointSlice,
++) error {
++	/*
++	   We try to turn a "delete + create" pair into a single "update" operation
++	   if the original slice (slicesToDelete[i]) has the same address type as
++	   the first slice in slicesToCreate, and is owned by the same Service.
++
++	   However, we must re-check the lengths of slicesToDelete and slicesToCreate
++	   within the loop to avoid an out-of-bounds index in slicesToCreate.
++	*/
++
++	i := 0
++	for i < len(slicesToDelete) {
++		// If there is nothing to create, break early
+ 		if len(slicesToCreate) == 0 {
+ 			break
+ 		}
+-		if slicesToDelete[i].AddressType == slicesToCreate[0].AddressType && ownedBy(slicesToDelete[i], service) {
+-			slicesToCreate[0].Name = slicesToDelete[i].Name
++
++		sd := slicesToDelete[i]
++		sc := slicesToCreate[0] // We can safely do this now, because len(slicesToCreate) > 0
++
++		// If the address type matches, and the slice is owned by the same Service,
++		// then instead of deleting sd and creating sc, we'll transform it into an update:
++		// we rename sc with sd's name, remove sd from the delete list, remove sc from the create list,
++		// and add sc to the update list.
++		if sd.AddressType == sc.AddressType && ownedBy(sd, service) {
++			sliceToUpdate := sc
++			sliceToUpdate.Name = sd.Name
++
++			// Remove the first element from slicesToCreate
+ 			slicesToCreate = slicesToCreate[1:]
+-			slicesToUpdate = append(slicesToUpdate, slicesToCreate[0])
++
++			// Remove the slice from slicesToDelete
+ 			slicesToDelete = append(slicesToDelete[:i], slicesToDelete[i+1:]...)
++
++			// Now add the renamed slice to the list of slices we want to update
++			slicesToUpdate = append(slicesToUpdate, sliceToUpdate)
++
++			/*
++			   Do not increment i here, because we've just removed an element from
++			   slicesToDelete. The next slice to examine is now at the same index i.
++			*/
+ 		} else {
++			// If they don't match, move on to the next slice in slicesToDelete.
+ 			i++
+ 		}
+ 	}
+ 
+-	// Create the new slices if service is not marked for deletion
++	/*
++	   If the Service is not being deleted, create all remaining slices in slicesToCreate.
++	   (If the Service has a DeletionTimestamp, it means it is going away, so we do not
++	   want to create new EndpointSlices.)
++	*/
+ 	if service.DeletionTimestamp == nil {
+ 		for _, slice := range slicesToCreate {
+-			createdSlice, err := c.infraClient.DiscoveryV1().EndpointSlices(slice.Namespace).Create(context.TODO(), slice, metav1.CreateOptions{})
++			createdSlice, err := c.infraClient.DiscoveryV1().EndpointSlices(slice.Namespace).Create(
++				context.TODO(),
++				slice,
++				metav1.CreateOptions{},
++			)
+ 			if err != nil {
+-				klog.Errorf("Failed to create EndpointSlice %s in namespace %s: %v", slice.Name, slice.Namespace, err)
++				klog.Errorf("Failed to create EndpointSlice %s in namespace %s: %v",
++					slice.Name, slice.Namespace, err)
++				// If the namespace is terminating, it's safe to ignore the error.
+ 				if k8serrors.HasStatusCause(err, v1.NamespaceTerminatingCause) {
+-					return nil
++					continue
+ 				}
+ 				return err
+ 			}
+-			klog.Infof("Created EndpointSlice %s in namespace %s", createdSlice.Name, createdSlice.Namespace)
++			klog.Infof("Created EndpointSlice %s in namespace %s",
++				createdSlice.Name, createdSlice.Namespace)
+ 		}
+ 	}
+ 
+-	// Update slices
++	// Update slices that are in the slicesToUpdate list.
+ 	for _, slice := range slicesToUpdate {
+-		_, err := c.infraClient.DiscoveryV1().EndpointSlices(slice.Namespace).Update(context.TODO(), slice, metav1.UpdateOptions{})
++		_, err := c.infraClient.DiscoveryV1().EndpointSlices(slice.Namespace).Update(
++			context.TODO(),
++			slice,
++			metav1.UpdateOptions{},
++		)
+ 		if err != nil {
+-			klog.Errorf("Failed to update EndpointSlice %s in namespace %s: %v", slice.Name, slice.Namespace, err)
++			klog.Errorf("Failed to update EndpointSlice %s in namespace %s: %v",
++				slice.Name, slice.Namespace, err)
+ 			return err
+ 		}
+-		klog.Infof("Updated EndpointSlice %s in namespace %s", slice.Name, slice.Namespace)
++		klog.Infof("Updated EndpointSlice %s in namespace %s",
++			slice.Name, slice.Namespace)
+ 	}
+ 
+-	// Delete slices
++	// Finally, delete slices that are in slicesToDelete and are no longer needed.
+ 	for _, slice := range slicesToDelete {
+-		err := c.infraClient.DiscoveryV1().EndpointSlices(slice.Namespace).Delete(context.TODO(), slice.Name, metav1.DeleteOptions{})
++		err := c.infraClient.DiscoveryV1().EndpointSlices(slice.Namespace).Delete(
++			context.TODO(),
++			slice.Name,
++			metav1.DeleteOptions{},
++		)
+ 		if err != nil {
+-			klog.Errorf("Failed to delete EndpointSlice %s in namespace %s: %v", slice.Name, slice.Namespace, err)
++			klog.Errorf("Failed to delete EndpointSlice %s in namespace %s: %v",
++				slice.Name, slice.Namespace, err)
+ 			return err
+ 		}
+-		klog.Infof("Deleted EndpointSlice %s in namespace %s", slice.Name, slice.Namespace)
++		klog.Infof("Deleted EndpointSlice %s in namespace %s",
++			slice.Name, slice.Namespace)
+ 	}
+ 
+ 	return nil
+diff --git a/pkg/controller/kubevirteps/kubevirteps_controller_test.go b/pkg/controller/kubevirteps/kubevirteps_controller_test.go
+index 5326faa4..c3167911 100644
+--- a/pkg/controller/kubevirteps/kubevirteps_controller_test.go
++++ b/pkg/controller/kubevirteps/kubevirteps_controller_test.go
+@@ -653,51 +653,43 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 				*createPort("http", 80, v1.ProtocolTCP),
+ 				[]discoveryv1.Endpoint{*createEndpoint("123.45.67.89", "worker-0-test", true, true, false)})
+ 
+-			// Define several unique ports for the Service
++			// Define multiple ports for the Service
+ 			servicePorts := []v1.ServicePort{
+ 				{
+-					Name:        "client",
+-					Protocol:    v1.ProtocolTCP,
+-					Port:        10001,
+-					TargetPort:  intstr.FromInt(30396),
+-					NodePort:    30396,
+-					AppProtocol: nil,
++					Name:       "client",
++					Protocol:   v1.ProtocolTCP,
++					Port:       10001,
++					TargetPort: intstr.FromInt(30396),
++					NodePort:   30396,
+ 				},
+ 				{
+-					Name:        "dashboard",
+-					Protocol:    v1.ProtocolTCP,
+-					Port:        8265,
+-					TargetPort:  intstr.FromInt(31003),
+-					NodePort:    31003,
+-					AppProtocol: nil,
++					Name:       "dashboard",
++					Protocol:   v1.ProtocolTCP,
++					Port:       8265,
++					TargetPort: intstr.FromInt(31003),
++					NodePort:   31003,
+ 				},
+ 				{
+-					Name:        "metrics",
+-					Protocol:    v1.ProtocolTCP,
+-					Port:        8080,
+-					TargetPort:  intstr.FromInt(30452),
+-					NodePort:    30452,
+-					AppProtocol: nil,
++					Name:       "metrics",
++					Protocol:   v1.ProtocolTCP,
++					Port:       8080,
++					TargetPort: intstr.FromInt(30452),
++					NodePort:   30452,
+ 				},
+ 			}
+ 
+-			// Create a Service with the first port
+ 			createAndAssertInfraServiceLB("infra-multiport-service", "tenant-service-name", "test-cluster",
+-				servicePorts[0],
+-				v1.ServiceExternalTrafficPolicyLocal)
++				servicePorts[0], v1.ServiceExternalTrafficPolicyLocal)
+ 
+-			// Update the Service by adding the remaining ports
+ 			svc, err := testVals.infraClient.CoreV1().Services(infraNamespace).Get(context.TODO(), "infra-multiport-service", metav1.GetOptions{})
+ 			Expect(err).To(BeNil())
+ 
+ 			svc.Spec.Ports = servicePorts
+-
+ 			_, err = testVals.infraClient.CoreV1().Services(infraNamespace).Update(context.TODO(), svc, metav1.UpdateOptions{})
+ 			Expect(err).To(BeNil())
+ 
+ 			var epsListMultiPort *discoveryv1.EndpointSliceList
+ 
+-			// Verify that the EndpointSlice is created with correct unique ports
+ 			Eventually(func() (bool, error) {
+ 				epsListMultiPort, err = testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+ 				if len(epsListMultiPort.Items) != 1 {
+@@ -714,7 +706,6 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 					}
+ 				}
+ 
+-				// Verify that all expected ports are present and without duplicates
+ 				if len(foundPortNames) != len(expectedPortNames) {
+ 					return false, err
+ 				}
+@@ -767,5 +758,81 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 				return false, err
+ 			}).Should(BeTrue(), "EndpointSlice in infra cluster should be recreated by the controller after deletion")
+ 		})
++
++		g.It("Should remove EndpointSlices and not recreate them when a previously no-selector Service obtains a selector", func() {
++			testVals.infraClient.Fake.PrependReactor("create", "endpointslices", func(action testing.Action) (bool, runtime.Object, error) {
++				createAction := action.(testing.CreateAction)
++				slice := createAction.GetObject().(*discoveryv1.EndpointSlice)
++				if slice.Name == "" && slice.GenerateName != "" {
++					slice.Name = slice.GenerateName + "-fake001"
++				}
++				return false, slice, nil
++			})
++
++			createAndAssertVMI("worker-0-test", "ip-10-32-5-13", "123.45.67.89")
++
++			createAndAssertTenantSlice("test-epslice", "tenant-service-name", discoveryv1.AddressTypeIPv4,
++				*createPort("http", 80, v1.ProtocolTCP),
++				[]discoveryv1.Endpoint{
++					*createEndpoint("123.45.67.89", "worker-0-test", true, true, false),
++				},
++			)
++
++			noSelectorSvcName := "svc-without-selector"
++			svc := &v1.Service{
++				ObjectMeta: metav1.ObjectMeta{
++					Name:      noSelectorSvcName,
++					Namespace: infraNamespace,
++					Labels: map[string]string{
++						kubevirt.TenantServiceNameLabelKey:      "tenant-service-name",
++						kubevirt.TenantServiceNamespaceLabelKey: tenantNamespace,
++						kubevirt.TenantClusterNameLabelKey:      "test-cluster",
++					},
++				},
++				Spec: v1.ServiceSpec{
++					Ports: []v1.ServicePort{
++						{
++							Name:       "web",
++							Port:       80,
++							NodePort:   31900,
++							Protocol:   v1.ProtocolTCP,
++							TargetPort: intstr.IntOrString{IntVal: 30390},
++						},
++					},
++					Type:                  v1.ServiceTypeLoadBalancer,
++					ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyLocal,
++				},
++			}
++
++			_, err := testVals.infraClient.CoreV1().Services(infraNamespace).Create(context.TODO(), svc, metav1.CreateOptions{})
++			Expect(err).To(BeNil())
++
++			Eventually(func() (bool, error) {
++				epsList, err := testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).
++					List(context.TODO(), metav1.ListOptions{})
++				if err != nil {
++					return false, err
++				}
++				return len(epsList.Items) == 1, nil
++			}).Should(BeTrue(), "Controller should create an EndpointSlice in infra cluster for the no-selector LB service")
++
++			svcWithSelector, err := testVals.infraClient.CoreV1().Services(infraNamespace).Get(
++				context.TODO(), noSelectorSvcName, metav1.GetOptions{})
++			Expect(err).To(BeNil())
++
++			svcWithSelector.Spec.Selector = map[string]string{"app": "test-value"}
++			_, err = testVals.infraClient.CoreV1().Services(infraNamespace).
++				Update(context.TODO(), svcWithSelector, metav1.UpdateOptions{})
++			Expect(err).To(BeNil())
++
++			Eventually(func() (bool, error) {
++				epsList, err := testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).
++					List(context.TODO(), metav1.ListOptions{})
++				if err != nil {
++					return false, err
++				}
++				return len(epsList.Items) == 0, nil
++			}).Should(BeTrue(), "All EndpointSlices should be removed after Service acquires a selector (no new slices created)")
++		})
+ 	})
+ })
diff --git a/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/999.diff b/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/999.diff
new file mode 100644
index 00000000..3d2f84ca
--- /dev/null
+++ b/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/999.diff
@@ -0,0 +1,769 @@
+diff --git a/cmd/kubevirt-cloud-controller-manager/kubevirteps.go b/cmd/kubevirt-cloud-controller-manager/kubevirteps.go
+index 74166b5d..4e744f8d 100644
+--- a/cmd/kubevirt-cloud-controller-manager/kubevirteps.go
++++ b/cmd/kubevirt-cloud-controller-manager/kubevirteps.go
+@@ -101,7 +101,18 @@ func startKubevirtCloudController(
+ 
+ 	klog.Infof("Setting up kubevirtEPSController")
+ 
+-	kubevirtEPSController := kubevirteps.NewKubevirtEPSController(tenantClient, infraClient, infraDynamic, kubevirtCloud.Namespace())
++	clusterName := ccmConfig.ComponentConfig.KubeCloudShared.ClusterName
++	if clusterName == "" {
++		klog.Fatalf("Required flag --cluster-name is missing")
++	}
++
++	kubevirtEPSController := kubevirteps.NewKubevirtEPSController(
++		tenantClient,
++		infraClient,
++		infraDynamic,
++		kubevirtCloud.Namespace(),
++		clusterName,
++	)
+ 
+ 	klog.Infof("Initializing kubevirtEPSController")
+ 
+diff --git a/pkg/controller/kubevirteps/kubevirteps_controller.go b/pkg/controller/kubevirteps/kubevirteps_controller.go
+index 1ca419b9..b56882c1 100644
+--- a/pkg/controller/kubevirteps/kubevirteps_controller.go
++++ b/pkg/controller/kubevirteps/kubevirteps_controller.go
+@@ -54,10 +54,10 @@ type Controller struct {
+ 	infraDynamic dynamic.Interface
+ 	infraFactory informers.SharedInformerFactory
+ 
+-	infraNamespace string
+-	queue          workqueue.RateLimitingInterface
+-	maxRetries     int
+-
++	infraNamespace       string
++	clusterName          string
++	queue                workqueue.RateLimitingInterface
++	maxRetries           int
+ 	maxEndPointsPerSlice int
+ }
+ 
+@@ -65,8 +65,9 @@ func NewKubevirtEPSController(
+ 	tenantClient kubernetes.Interface,
+ 	infraClient kubernetes.Interface,
+ 	infraDynamic dynamic.Interface,
+-	infraNamespace string) *Controller {
+-
++	infraNamespace string,
++	clusterName string,
++) *Controller {
+ 	tenantFactory := informers.NewSharedInformerFactory(tenantClient, 0)
+ 	infraFactory := informers.NewSharedInformerFactoryWithOptions(infraClient, 0, informers.WithNamespace(infraNamespace))
+ 	queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
+@@ -79,6 +80,7 @@ func NewKubevirtEPSController(
+ 		infraDynamic:         infraDynamic,
+ 		infraFactory:         infraFactory,
+ 		infraNamespace:       infraNamespace,
++		clusterName:          clusterName,
+ 		queue:                queue,
+ 		maxRetries:           25,
+ 		maxEndPointsPerSlice: 100,
+@@ -320,22 +322,30 @@ func (c *Controller) processNextItem(ctx context.Context) bool {
+ 
+ // getInfraServiceFromTenantEPS returns the Service in the infra cluster that is associated with the given tenant endpoint slice.
+ func (c *Controller) getInfraServiceFromTenantEPS(ctx context.Context, slice *discovery.EndpointSlice) (*v1.Service, error) {
+-	infraServices, err := c.infraClient.CoreV1().Services(c.infraNamespace).List(ctx,
+-		metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s,%s=%s", kubevirt.TenantServiceNameLabelKey, slice.Labels["kubernetes.io/service-name"],
+-			kubevirt.TenantServiceNamespaceLabelKey, slice.Namespace)})
++	tenantServiceName := slice.Labels[discovery.LabelServiceName]
++	tenantServiceNamespace := slice.Namespace
++
++	labelSelector := fmt.Sprintf(
++		"%s=%s,%s=%s,%s=%s",
++		kubevirt.TenantServiceNameLabelKey, tenantServiceName,
++		kubevirt.TenantServiceNamespaceLabelKey, tenantServiceNamespace,
++		kubevirt.TenantClusterNameLabelKey, c.clusterName,
++	)
++
++	svcList, err := c.infraClient.CoreV1().Services(c.infraNamespace).List(ctx, metav1.ListOptions{
++		LabelSelector: labelSelector,
++	})
+ 	if err != nil {
+-		klog.Errorf("Failed to get Service in Infra for EndpointSlice %s in namespace %s: %v", slice.Name, slice.Namespace, err)
++		klog.Errorf("Failed to get Service in Infra for EndpointSlice %s in namespace %s: %v", slice.Name, tenantServiceNamespace, err)
+ 		return nil, err
+ 	}
+-	if len(infraServices.Items) > 1 {
+-		// This should never be possible, only one service should exist for a given tenant endpoint slice
+-		klog.Errorf("Multiple services found for tenant endpoint slice %s in namespace %s", slice.Name, slice.Namespace)
++	if len(svcList.Items) > 1 {
++		klog.Errorf("Multiple services found for tenant endpoint slice %s in namespace %s", slice.Name, tenantServiceNamespace)
+ 		return nil, errors.New("multiple services found for tenant endpoint slice")
+ 	}
+-	if len(infraServices.Items) == 1 {
+-		return &infraServices.Items[0], nil
++	if len(svcList.Items) == 1 {
++		return &svcList.Items[0], nil
+ 	}
+-	// No service found, possible if service is deleted.
+ 	return nil, nil
+ }
+ 
+@@ -363,16 +373,27 @@ func (c *Controller) getTenantEPSFromInfraService(ctx context.Context, svc *v1.S
+ // getInfraEPSFromInfraService returns the EndpointSlices in the infra cluster that are associated with the given infra service.
+ func (c *Controller) getInfraEPSFromInfraService(ctx context.Context, svc *v1.Service) ([]*discovery.EndpointSlice, error) {
+ 	var infraEPSSlices []*discovery.EndpointSlice
+-	klog.Infof("Searching for endpoints on infra cluster for service %s in namespace %s.", svc.Name, svc.Namespace)
+-	result, err := c.infraClient.DiscoveryV1().EndpointSlices(svc.Namespace).List(ctx,
+-		metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", discovery.LabelServiceName, svc.Name)})
++
++	klog.Infof("Searching for EndpointSlices in infra cluster for service %s/%s", svc.Namespace, svc.Name)
++
++	labelSelector := fmt.Sprintf(
++		"%s=%s,%s=%s",
++		discovery.LabelServiceName, svc.Name,
++		kubevirt.TenantClusterNameLabelKey, c.clusterName,
++	)
++
++	result, err := c.infraClient.DiscoveryV1().EndpointSlices(svc.Namespace).List(ctx, metav1.ListOptions{
++		LabelSelector: labelSelector,
++	})
+ 	if err != nil {
+ 		klog.Errorf("Failed to get EndpointSlices for Service %s in namespace %s: %v", svc.Name, svc.Namespace, err)
+ 		return nil, err
+ 	}
++
+ 	for _, eps := range result.Items {
+ 		infraEPSSlices = append(infraEPSSlices, &eps)
+ 	}
++
+ 	return infraEPSSlices, nil
+ }
+ 
+@@ -382,15 +403,17 @@ func (c *Controller) reconcile(ctx context.Context, r *Request) error {
+ 		return errors.New("could not cast object to service")
+ 	}
+ 
+-	/*
+-	   Skip if the given Service is not labeled with the keys that indicate
+-	   it was created/managed by this controller (i.e., not a LoadBalancer
+-	   that we handle).
+-	*/
++	// Skip services not managed by this controller (missing required labels)
+ 	if service.Labels[kubevirt.TenantServiceNameLabelKey] == "" ||
+ 		service.Labels[kubevirt.TenantServiceNamespaceLabelKey] == "" ||
+ 		service.Labels[kubevirt.TenantClusterNameLabelKey] == "" {
+-		klog.Infof("This LoadBalancer Service: %s is not managed by the %s. Skipping.", service.Name, ControllerName)
++		klog.Infof("Service %s is not managed by this controller. Skipping.", service.Name)
++		return nil
++	}
++
++	// Skip services for other clusters
++	if service.Labels[kubevirt.TenantClusterNameLabelKey] != c.clusterName {
++		klog.Infof("Skipping Service %s: cluster label %q doesn't match our clusterName %q", service.Name, service.Labels[kubevirt.TenantClusterNameLabelKey], c.clusterName)
+ 		return nil
+ 	}
+ 
+diff --git a/pkg/controller/kubevirteps/kubevirteps_controller_test.go b/pkg/controller/kubevirteps/kubevirteps_controller_test.go
+index c3167911..7525aaa5 100644
+--- a/pkg/controller/kubevirteps/kubevirteps_controller_test.go
++++ b/pkg/controller/kubevirteps/kubevirteps_controller_test.go
+@@ -120,7 +120,8 @@ func createTenantEPSlice(
+ 			Name:      name,
+ 			Namespace: tenantNamespace,
+ 			Labels: map[string]string{
+-				discoveryv1.LabelServiceName: labelServiceName,
++				discoveryv1.LabelServiceName:       labelServiceName,
++				kubevirt.TenantClusterNameLabelKey: "test-cluster",
+ 			},
+ 		},
+ 		AddressType: addressType,
+@@ -150,7 +151,7 @@ func createAndAssertVMI(node, nodeName, ip string) {
+ func createAndAssertTenantSlice(name, labelServiceName string, addressType discoveryv1.AddressType, port discoveryv1.EndpointPort, endpoints []discoveryv1.Endpoint) {
+ 	epSlice := createTenantEPSlice(name, labelServiceName, addressType, port, endpoints)
+ 	_, _ = testVals.tenantClient.DiscoveryV1().EndpointSlices(tenantNamespace).Create(context.TODO(), epSlice, metav1.CreateOptions{})
+-	// Check if tenant Endpointslice is created
++	// Check if tenant EndpointSlice is created
+ 	Eventually(func() (bool, error) {
+ 		eps, err := testVals.tenantClient.DiscoveryV1().EndpointSlices(tenantNamespace).Get(context.TODO(), name, metav1.GetOptions{})
+ 		if err == nil || eps != nil {
+@@ -190,7 +191,8 @@ func setupTestKubevirtEPSController() *testKubevirtEPSController {
+ 		}: "VirtualMachineInstanceList",
+ 	})
+ 
+-	controller := NewKubevirtEPSController(tenantClient, infraClient, infraDynamic, "test")
++	// Pass the cluster name ("test-cluster") as an argument.
++	controller := NewKubevirtEPSController(tenantClient, infraClient, infraDynamic, "test", "test-cluster")
+ 
+ 	err := controller.Init()
+ 	if err != nil {
+@@ -262,16 +264,16 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 			stop()
+ 		})
+ 
+-		g.It("Should reconcile a new Endpointslice on the infra cluster", func() {
++		g.It("Should reconcile a new EndpointSlice on the infra cluster", func() {
+ 			// Create VMI in infra cluster
+ 			createAndAssertVMI("worker-0-test", "ip-10-32-5-13", "123.45.67.89")
+ 
+-			// Create Endpoinslices in tenant cluster
++			// Create EndpointSlice in tenant cluster
+ 			createAndAssertTenantSlice("test-epslice", "tenant-service-name", discoveryv1.AddressTypeIPv4,
+ 				*createPort("http", 80, v1.ProtocolTCP),
+ 				[]discoveryv1.Endpoint{*createEndpoint("123.45.67.89", "worker-0-test", true, true, false)})
+ 
+-			// Create service in infra cluster
++			// Create Service in infra cluster
+ 			createAndAssertInfraServiceLB("infra-service-name", "tenant-service-name", "test-cluster",
+ 				v1.ServicePort{Name: "web", Port: 80, NodePort: 31900, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{IntVal: 30390}},
+ 				v1.ServiceExternalTrafficPolicyLocal)
+@@ -281,17 +283,13 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 			// Check if the controller creates the EndpointSlice in the infra cluster
+ 			Eventually(func() (bool, error) {
+ 				epsList, err = testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+-				if len(epsList.Items) == 1 {
+-					return true, err
+-				} else {
+-					return false, err
+-				}
++				return len(epsList.Items) == 1, err
+ 			}).Should(BeTrue(), "EndpointSlice in infra cluster should be created by the controller reconciler")
+ 
+ 			Expect(*epsList.Items[0].Endpoints[0].NodeName).To(Equal("ip-10-32-5-13"))
+ 		})
+ 
+-		g.It("Should update the Endpointslice when a tenant Endpointslice is updated", func() {
++		g.It("Should update the EndpointSlice when a tenant EndpointSlice is updated", func() {
+ 
+ 			ipAddr1 := "123.45.67.11"
+ 			ipAddr2 := "123.99.99.99"
+@@ -299,12 +297,12 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 			createAndAssertVMI("worker-0-test", "ip-10-32-5-13", ipAddr1)
+ 			createAndAssertVMI("worker-1-test", "ip-10-32-5-15", ipAddr2)
+ 
+-			// Create Endpoinslices in tenant cluster
++			// Create EndpointSlice in tenant cluster
+ 			createAndAssertTenantSlice("test-epslice", "tenant-service-name", discoveryv1.AddressTypeIPv4,
+ 				*createPort("http", 80, v1.ProtocolTCP),
+ 				[]discoveryv1.Endpoint{*createEndpoint(ipAddr1, "worker-0-test", true, true, false)})
+ 
+-			// Create service in infra cluster
++			// Create Service in infra cluster
+ 			createAndAssertInfraServiceLB("infra-service-name", "tenant-service-name", "test-cluster",
+ 				v1.ServicePort{Name: "web", Port: 80, NodePort: 31900, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{IntVal: 30390}},
+ 				v1.ServiceExternalTrafficPolicyLocal)
+@@ -316,12 +314,11 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 					len(epsList.Items[0].Endpoints) == 1 &&
+ 					*epsList.Items[0].Endpoints[0].NodeName == "ip-10-32-5-13" {
+ 					return true, err
+-				} else {
+-					return false, err
+ 				}
++				return false, err
+ 			}).Should(BeTrue(), "EndpointSlice in infra cluster should be created by the controller reconciler")
+ 
+-			// Update the tenant Endpointslice
++			// Update the tenant EndpointSlice
+ 			epSlice := createTenantEPSlice("test-epslice", "tenant-service-name", discoveryv1.AddressTypeIPv4,
+ 				*createPort("http", 80, v1.ProtocolTCP),
+ 				[]discoveryv1.Endpoint{
+@@ -331,119 +328,89 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 			_, err := testVals.tenantClient.DiscoveryV1().EndpointSlices(tenantNamespace).Update(context.TODO(), epSlice, metav1.UpdateOptions{})
+ 			Expect(err).To(BeNil())
+ 
+-			// Check if tenant Endpointslice is updated
++			// Check if tenant EndpointSlice is updated
+ 			Eventually(func() (bool, error) {
+ 				epsList, err := testVals.tenantClient.DiscoveryV1().EndpointSlices(tenantNamespace).List(context.TODO(), metav1.ListOptions{})
+-				if len(epsList.Items) == 1 && len(epsList.Items[0].Endpoints) == 2 {
+-					return true, err
+-				} else {
+-					return false, err
+-				}
++				return len(epsList.Items) == 1 && len(epsList.Items[0].Endpoints) == 2, err
+ 			}).Should(BeTrue(), "EndpointSlice in tenant cluster should be updated")
+ 
+ 			// Check if the controller updates the EndpointSlice in the infra cluster
+ 			Eventually(func() (bool, error) {
+ 				epsList, err := testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+-				if len(epsList.Items) == 1 && len(epsList.Items[0].Endpoints) == 2 {
+-					return true, err
+-				} else {
+-					return false, err
+-				}
++				return len(epsList.Items) == 1 && len(epsList.Items[0].Endpoints) == 2, err
+ 			}).Should(BeTrue(), "EndpointSlice in infra cluster should be updated by the controller reconciler")
+ 		})
+ 
+-		g.It("Should update the Endpointslice when the infra Service external traffic policy changes.", func() {
++		g.It("Should update the EndpointSlice when the infra Service external traffic policy changes", func() {
+ 			// Create VMI in infra cluster
+ 			createAndAssertVMI("worker-0-test", "ip-10-32-5-13", "123.45.67.89")
+ 
+-			// Create Endpoinslices in tenant cluster
++			// Create EndpointSlice in tenant cluster
+ 			createAndAssertTenantSlice("test-epslice", "tenant-service-name", discoveryv1.AddressTypeIPv4,
+ 				*createPort("http", 80, v1.ProtocolTCP),
+ 				[]discoveryv1.Endpoint{*createEndpoint("123.45.67.89", "worker-0-test", true, true, false)})
+ 
+-			// Create service in infra cluster
++			// Create Service in infra cluster
+ 			createAndAssertInfraServiceLB("infra-service-name", "tenant-service-name", "test-cluster",
+ 				v1.ServicePort{Name: "web", Port: 80, NodePort: 31900, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{IntVal: 30390}},
+ 				v1.ServiceExternalTrafficPolicyLocal)
+ 
+ 			var epsList *discoveryv1.EndpointSliceList
+ 			var err error
+-			// Check if the controller creates the EndpointSlice in the infra cluster
+ 			Eventually(func() (bool, error) {
+ 				epsList, err = testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+-				if len(epsList.Items) == 1 {
+-					return true, err
+-				} else {
+-					return false, err
+-				}
++				return len(epsList.Items) == 1, err
+ 			}).Should(BeTrue(), "EndpointSlice in infra cluster should be created by the controller reconciler")
+-
+ 			Expect(*epsList.Items[0].Endpoints[0].NodeName).To(Equal("ip-10-32-5-13"))
+ 
+-			// Update the service's external traffic policy to Cluster
++			// Update the Service's external traffic policy to Cluster
+ 			svc := createInfraServiceLB("infra-service-name", "tenant-service-name", "test-cluster",
+ 				v1.ServicePort{Name: "web", Port: 80, NodePort: 31900, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{IntVal: 30390}},
+ 				v1.ServiceExternalTrafficPolicyCluster)
+-
+ 			_, err = testVals.infraClient.CoreV1().Services(infraNamespace).Update(context.TODO(), svc, metav1.UpdateOptions{})
+ 			Expect(err).To(BeNil())
+ 
+ 			Eventually(func() (bool, error) {
+ 				epsList, err = testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+-				if len(epsList.Items) == 0 {
+-					return true, err
+-				} else {
+-					return false, err
+-				}
++				return len(epsList.Items) == 0, err
+ 			}).Should(BeTrue(), "EndpointSlice in infra cluster should be deleted by the controller reconciler")
+ 
+-			// Update the service's external traffic policy to Local
++			// Update the Service's external traffic policy back to Local
+ 			svc = createInfraServiceLB("infra-service-name", "tenant-service-name", "test-cluster",
+ 				v1.ServicePort{Name: "web", Port: 80, NodePort: 31900, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{IntVal: 30390}},
+ 				v1.ServiceExternalTrafficPolicyLocal)
+-
+ 			_, err = testVals.infraClient.CoreV1().Services(infraNamespace).Update(context.TODO(), svc, metav1.UpdateOptions{})
+ 			Expect(err).To(BeNil())
+ 
+ 			Eventually(func() (bool, error) {
+ 				epsList, err = testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+-				if len(epsList.Items) == 1 {
+-					return true, err
+-				} else {
+-					return false, err
+-				}
+-			}).Should(BeTrue(), "EndpointSlice in infra cluster should be created by the controller reconciler")
++				return len(epsList.Items) == 1, err
++			}).Should(BeTrue(), "EndpointSlice in infra cluster should be recreated by the controller reconciler")
+ 		})
+ 
+-		g.It("Should update the Endpointslice when the infra Service labels are updated.", func() {
++		g.It("Should update the EndpointSlice when the infra Service labels are updated", func() {
+ 			// Create VMI in infra cluster
+ 			createAndAssertVMI("worker-0-test", "ip-10-32-5-13", "123.45.67.89")
+ 
+-			// Create Endpoinslices in tenant cluster
++			// Create EndpointSlice in tenant cluster
+ 			createAndAssertTenantSlice("test-epslice", "tenant-service-name", discoveryv1.AddressTypeIPv4,
+ 				*createPort("http", 80, v1.ProtocolTCP),
+ 				[]discoveryv1.Endpoint{*createEndpoint("123.45.67.89", "worker-0-test", true, true, false)})
+ 
+-			// Create service in infra cluster
++			// Create Service in infra cluster
+ 			createAndAssertInfraServiceLB("infra-service-name", "tenant-service-name", "test-cluster",
+ 				v1.ServicePort{Name: "web", Port: 80, NodePort: 31900, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{IntVal: 30390}},
+ 				v1.ServiceExternalTrafficPolicyLocal)
+ 
+ 			var epsList *discoveryv1.EndpointSliceList
+ 			var err error
+-			// Check if the controller creates the EndpointSlice in the infra cluster
+ 			Eventually(func() (bool, error) {
+ 				epsList, err = testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+-				if len(epsList.Items) == 1 {
+-					return true, err
+-				} else {
+-					return false, err
+-				}
++				return len(epsList.Items) == 1, err
+ 			}).Should(BeTrue(), "EndpointSlice in infra cluster should be created by the controller reconciler")
+-
+ 			Expect(*epsList.Items[0].Endpoints[0].NodeName).To(Equal("ip-10-32-5-13"))
+ 
+-			// Update the service's labels
++			// Update the Service's labels
+ 			svc := createInfraServiceLB("infra-service-name", "tenant-service-name", "test-cluster",
+ 				v1.ServicePort{Name: "web", Port: 80, NodePort: 31900, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{IntVal: 30390}},
+ 				v1.ServiceExternalTrafficPolicyLocal)
+@@ -460,17 +427,14 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 						return true, err
+ 					}
+ 					return false, err
+-				} else {
+-					return false, err
+ 				}
++				return false, err
+ 			}).Should(BeTrue(), "EndpointSlice in infra cluster should have the two added labels")
+ 
+-			// Update the service's external traffic policy to Cluster
+ 			svc = createInfraServiceLB("infra-service-name", "tenant-service-name", "test-cluster",
+ 				v1.ServicePort{Name: "web", Port: 80, NodePort: 31900, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{IntVal: 30390}},
+ 				v1.ServiceExternalTrafficPolicyLocal)
+ 			svc.Labels["test-label"] = "test-value"
+-
+ 			_, err = testVals.infraClient.CoreV1().Services(infraNamespace).Update(context.TODO(), svc, metav1.UpdateOptions{})
+ 			Expect(err).To(BeNil())
+ 
+@@ -481,29 +445,22 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 						return true, err
+ 					}
+ 					return false, err
+-				} else {
+-					return false, err
+ 				}
++				return false, err
+ 			}).Should(BeTrue(), "EndpointSlice in infra cluster still has the two added labels")
+ 		})
+ 
+-		g.It("Should update the Endpointslice when the infra Service port is updated.", func() {
+-			// Create VMI in infra cluster
++		g.It("Should update the EndpointSlice when the infra Service port is updated", func() {
+ 			createAndAssertVMI("worker-0-test", "ip-10-32-5-13", "123.45.67.89")
+-
+-			// Create Endpoinslices in tenant cluster
+ 			createAndAssertTenantSlice("test-epslice", "tenant-service-name", discoveryv1.AddressTypeIPv4,
+ 				*createPort("http", 80, v1.ProtocolTCP),
+ 				[]discoveryv1.Endpoint{*createEndpoint("123.45.67.89", "worker-0-test", true, true, false)})
+-
+-			// Create service in infra cluster
+ 			createAndAssertInfraServiceLB("infra-service-name", "tenant-service-name", "test-cluster",
+ 				v1.ServicePort{Name: "web", Port: 80, NodePort: 31900, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{IntVal: 30390}},
+ 				v1.ServiceExternalTrafficPolicyLocal)
+ 
+ 			var epsList *discoveryv1.EndpointSliceList
+ 			var err error
+-			// Check if the controller creates the EndpointSlice in the infra cluster
+ 			Eventually(func() (bool, error) {
+ 				epsList, err = testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+ 				if len(epsList.Items) == 1 {
+@@ -511,18 +468,14 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 						return true, err
+ 					}
+ 					return false, err
+-				} else {
+-					return false, err
+ 				}
++				return false, err
+ 			}).Should(BeTrue(), "EndpointSlice in infra cluster should be created by the controller reconciler")
+-
+ 			Expect(*epsList.Items[0].Endpoints[0].NodeName).To(Equal("ip-10-32-5-13"))
+ 
+-			// Update the service's port
+ 			svc := createInfraServiceLB("infra-service-name", "tenant-service-name", "test-cluster",
+ 				v1.ServicePort{Name: "web", Port: 80, NodePort: 31900, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{IntVal: 30440}},
+ 				v1.ServiceExternalTrafficPolicyLocal)
+-
+ 			_, err = testVals.infraClient.CoreV1().Services(infraNamespace).Update(context.TODO(), svc, metav1.UpdateOptions{})
+ 			Expect(err).To(BeNil())
+ 
+@@ -533,29 +486,22 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 						return true, err
+ 					}
+ 					return false, err
+-				} else {
+-					return false, err
+ 				}
+-			}).Should(BeTrue(), "EndpointSlice in infra cluster should have the two added labels")
++				return false, err
++			}).Should(BeTrue(), "EndpointSlice in infra cluster should reflect updated port")
+ 		})
+ 
+-		g.It("Should delete the Endpointslice when the Service in infra is deleted", func() {
+-			// Create VMI in infra cluster
++		g.It("Should delete the EndpointSlice when the Service in infra is deleted", func() {
+ 			createAndAssertVMI("worker-0-test", "ip-10-32-5-13", "123.45.67.89")
+-
+-			// Create Endpoinslices in tenant cluster
+ 			createAndAssertTenantSlice("test-epslice", "tenant-service-name", discoveryv1.AddressTypeIPv4,
+ 				*createPort("http", 80, v1.ProtocolTCP),
+ 				[]discoveryv1.Endpoint{*createEndpoint("123.45.67.89", "worker-0-test", true, true, false)})
+-
+-			// Create service in infra cluster
+ 			createAndAssertInfraServiceLB("infra-service-name", "tenant-service-name", "test-cluster",
+ 				v1.ServicePort{Name: "web", Port: 80, NodePort: 31900, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{IntVal: 30390}},
+ 				v1.ServiceExternalTrafficPolicyLocal)
+ 
+ 			var epsList *discoveryv1.EndpointSliceList
+ 			var err error
+-			// Check if the controller creates the EndpointSlice in the infra cluster
+ 			Eventually(func() (bool, error) {
+ 				epsList, err = testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+ 				if len(epsList.Items) == 1 {
+@@ -563,72 +509,47 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 						return true, err
+ 					}
+ 					return false, err
+-				} else {
+-					return false, err
+ 				}
+-			}).Should(BeTrue(), "EndpointSlice in infra cluster should be created by the controller reconciler")
+-
++				return false, err
++			}).Should(BeTrue(), "EndpointSlice should be created by the controller")
+ 			Expect(*epsList.Items[0].Endpoints[0].NodeName).To(Equal("ip-10-32-5-13"))
+ 
+-			// Delete the service
+ 			err = testVals.infraClient.CoreV1().Services(infraNamespace).Delete(context.TODO(), "infra-service-name", metav1.DeleteOptions{})
+ 			Expect(err).To(BeNil())
+ 
+ 			Eventually(func() (bool, error) {
+ 				epsList, err = testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+-				if len(epsList.Items) == 0 {
+-					return true, err
+-				} else {
+-					return false, err
+-				}
+-			}).Should(BeTrue(), "EndpointSlice in infra cluster should be deleted.")
++				return len(epsList.Items) == 0, err
++			}).Should(BeTrue(), "EndpointSlice should be deleted when the Service is removed")
+ 		})
+ 
+-		g.It("Should not update the Endpointslice on the infra cluster because VMI is not present", func() {
+-			// Create VMI in infra cluster
++		g.It("Should not update the EndpointSlice on the infra cluster because VMI is not present", func() {
+ 			createAndAssertVMI("worker-0-test", "ip-10-32-5-13", "123.45.67.89")
+-
+-			// Create Endpoinslices in tenant cluster
+ 			createAndAssertTenantSlice("test-epslice", "tenant-service-name", discoveryv1.AddressTypeIPv4,
+ 				*createPort("http", 80, v1.ProtocolTCP),
+ 				[]discoveryv1.Endpoint{*createEndpoint("123.45.67.89", "worker-0-test", true, true, false)})
+-
+-			// Create service in infra cluster
+ 			createAndAssertInfraServiceLB("infra-service-name", "tenant-service-name", "test-cluster",
+ 				v1.ServicePort{Name: "web", Port: 80, NodePort: 31900, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{IntVal: 30390}}, v1.ServiceExternalTrafficPolicyLocal)
+ 
+-			// Check if the controller creates the EndpointSlice in the infra cluster
+ 			Eventually(func() (bool, error) {
+ 				epsList, err := testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+-				if len(epsList.Items) == 1 {
+-					return true, err
+-				} else {
+-					return false, err
+-				}
+-			}).Should(BeTrue(), "EndpointSlice in infra cluster should be created by the controller reconciler")
++				return len(epsList.Items) == 1, err
++			}).Should(BeTrue(), "EndpointSlice should be created by the controller")
+ 
+-			//
+ 			epSlice := createTenantEPSlice("test-epslice", "tenant-service-name", discoveryv1.AddressTypeIPv4,
+ 				*createPort("http", 80, v1.ProtocolTCP),
+ 				[]discoveryv1.Endpoint{
+ 					*createEndpoint("123.45.67.89", "worker-0-test", true, true, false),
+ 					*createEndpoint("112.34.56.78", "worker-1-test", true, true, false),
+ 				})
+-
+ 			_, err := testVals.tenantClient.DiscoveryV1().EndpointSlices(tenantNamespace).Update(context.TODO(), epSlice, metav1.UpdateOptions{})
+ 			Expect(err).To(BeNil())
+ 
+-			// Check if tenant Endpointslice is updated
+ 			Eventually(func() (bool, error) {
+ 				epsList, err := testVals.tenantClient.DiscoveryV1().EndpointSlices(tenantNamespace).List(context.TODO(), metav1.ListOptions{})
+-				if len(epsList.Items) == 1 && len(epsList.Items[0].Endpoints) == 2 {
+-					return true, err
+-				} else {
+-					return false, err
+-				}
++				return len(epsList.Items) == 1 && len(epsList.Items[0].Endpoints) == 2, err
+ 			}).Should(BeTrue(), "EndpointSlice in tenant cluster should be updated")
+ 
+-			//Expect call to the infraDynamic.Get to return the VMI
+ 			Eventually(func() (bool, error) {
+ 				for _, action := range testVals.infraDynamic.Actions() {
+ 					if action.Matches("get", "virtualmachineinstances") &&
+@@ -641,19 +562,14 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 				}
+ 				return false, nil
+ 			}).Should(BeTrue(), "Expect call to the infraDynamic.Get to return the VMI")
+-
+ 		})
+ 
+ 		g.It("Should correctly handle multiple unique ports in EndpointSlice", func() {
+-			// Create a VMI in the infra cluster
+ 			createAndAssertVMI("worker-0-test", "ip-10-32-5-13", "123.45.67.89")
+-
+-			// Create an EndpointSlice in the tenant cluster
+ 			createAndAssertTenantSlice("test-epslice", "tenant-service-name", discoveryv1.AddressTypeIPv4,
+ 				*createPort("http", 80, v1.ProtocolTCP),
+ 				[]discoveryv1.Endpoint{*createEndpoint("123.45.67.89", "worker-0-test", true, true, false)})
+ 
+-			// Define multiple ports for the Service
+ 			servicePorts := []v1.ServicePort{
+ 				{
+ 					Name:       "client",
+@@ -689,7 +605,6 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 			Expect(err).To(BeNil())
+ 
+ 			var epsListMultiPort *discoveryv1.EndpointSliceList
+-
+ 			Eventually(func() (bool, error) {
+ 				epsListMultiPort, err = testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+ 				if len(epsListMultiPort.Items) != 1 {
+@@ -698,34 +613,26 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 
+ 				createdSlice := epsListMultiPort.Items[0]
+ 				expectedPortNames := []string{"client", "dashboard", "metrics"}
+-				foundPortNames := []string{}
+-
++				var foundPortNames []string
+ 				for _, port := range createdSlice.Ports {
+ 					if port.Name != nil {
+ 						foundPortNames = append(foundPortNames, *port.Name)
+ 					}
+ 				}
+-
+ 				if len(foundPortNames) != len(expectedPortNames) {
+ 					return false, err
+ 				}
+-
+ 				portSet := sets.NewString(foundPortNames...)
+ 				expectedPortSet := sets.NewString(expectedPortNames...)
+ 				return portSet.Equal(expectedPortSet), err
+ 			}).Should(BeTrue(), "EndpointSlice should contain all unique ports from the Service without duplicates")
++		})
+ 
+ 		g.It("Should reconcile after infra EndpointSlice deletion and restore it", func() {
+-			// Create a VMI in the infra cluster
+-			// This ensures that when tenant EndpointSlice is created, it can be reconciled properly
+ 			createAndAssertVMI("worker-0-test", "ip-10-32-5-13", "123.45.67.89")
+-
+-			// Create an EndpointSlice in the tenant cluster representing the desired state
+ 			createAndAssertTenantSlice("test-epslice-infra", "tenant-service-name", discoveryv1.AddressTypeIPv4,
+ 				*createPort("http", 80, v1.ProtocolTCP),
+ 				[]discoveryv1.Endpoint{*createEndpoint("123.45.67.89", "worker-0-test", true, true, false)})
+-
+-			// Create a Service in the infra cluster that should trigger the creation of an EndpointSlice in the infra cluster
+ 			createAndAssertInfraServiceLB("infra-service-restore", "tenant-service-name", "test-cluster",
+ 				v1.ServicePort{Name: "web", Port: 80, NodePort: 31900, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{IntVal: 30390}},
+ 				v1.ServiceExternalTrafficPolicyLocal)
+@@ -733,106 +640,38 @@ var _ = g.Describe("KubevirtEPSController", g.Ordered, func() {
+ 			var epsList *discoveryv1.EndpointSliceList
+ 			var err error
+ 
+-			// Wait until the infra EndpointSlice is created by the controller
+ 			Eventually(func() (bool, error) {
+ 				epsList, err = testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+-				if len(epsList.Items) == 1 {
+-					return true, err
+-				} else {
+-					return false, err
+-				}
++				return len(epsList.Items) == 1, err
+ 			}).Should(BeTrue(), "Infra EndpointSlice should be created by the controller")
+ 
+-			// Now, simulate an external deletion of the EndpointSlice in the infra cluster
+ 			err = testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).Delete(context.TODO(), epsList.Items[0].Name, metav1.DeleteOptions{})
+ 			Expect(err).To(BeNil(), "Deleting infra EndpointSlice should succeed")
+ 
+-			// The controller, now watching infra EndpointSlices, should detect the removal
+-			// and trigger a reconcile to restore it.
+ 			Eventually(func() (bool, error) {
+ 				epsList, err = testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+-				// After some time, we expect exactly one EndpointSlice to be recreated.
+-				if err == nil && len(epsList.Items) == 1 {
+-					return true, nil
+-				}
+-				return false, err
+-			}).Should(BeTrue(), "EndpointSlice in infra cluster should be recreated by the controller after deletion")
++				return len(epsList.Items) == 1, err
++			}).Should(BeTrue(), "EndpointSlice should be recreated after deletion")
+ 		})
+ 
+-		g.It("Should remove EndpointSlices and not recreate them when a previously no-selector Service obtains a selector", func() {
+-			testVals.infraClient.Fake.PrependReactor("create", "endpointslices", func(action testing.Action) (bool, runtime.Object, error) {
+-				createAction := action.(testing.CreateAction)
+-				slice := createAction.GetObject().(*discoveryv1.EndpointSlice)
+-				if slice.Name == "" && slice.GenerateName != "" {
+-					slice.Name = slice.GenerateName + "-fake001"
+-				}
+-				return false, slice, nil
+-			})
+-
+-			createAndAssertVMI("worker-0-test", "ip-10-32-5-13", "123.45.67.89")
+-
+-			createAndAssertTenantSlice("test-epslice", "tenant-service-name", discoveryv1.AddressTypeIPv4,
+-				*createPort("http", 80, v1.ProtocolTCP),
+-				[]discoveryv1.Endpoint{
+-					*createEndpoint("123.45.67.89", "worker-0-test", true, true, false),
+-				},
+-			)
+-
+-			noSelectorSvcName := "svc-without-selector"
+-			svc := &v1.Service{
+-				ObjectMeta: metav1.ObjectMeta{
+-					Name:      noSelectorSvcName,
+-					Namespace: infraNamespace,
+-					Labels: map[string]string{
+-						kubevirt.TenantServiceNameLabelKey:      "tenant-service-name",
+-						kubevirt.TenantServiceNamespaceLabelKey: tenantNamespace,
+-						kubevirt.TenantClusterNameLabelKey:      "test-cluster",
+-					},
+-				},
+-				Spec: v1.ServiceSpec{
+-					Ports: []v1.ServicePort{
+-						{
+-							Name:       "web",
+-							Port:       80,
+-							NodePort:   31900,
+-							Protocol:   v1.ProtocolTCP,
+-							TargetPort: intstr.IntOrString{IntVal: 30390},
+-						},
+-					},
+-					Type:                  v1.ServiceTypeLoadBalancer,
+-					ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyLocal,
+-				},
+-			}
+-
++		// New test: verify that Services with a different cluster label are ignored
++		g.It("Should ignore Services from a different cluster", func() {
++			// Create a Service with cluster label "other-cluster"
++			svc := createInfraServiceLB("infra-service-conflict", "tenant-service-name", "other-cluster",
++				v1.ServicePort{Name: "web", Port: 80, NodePort: 31900, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{IntVal: 30390}},
++				v1.ServiceExternalTrafficPolicyLocal)
+ 			_, err := testVals.infraClient.CoreV1().Services(infraNamespace).Create(context.TODO(), svc, metav1.CreateOptions{})
+ 			Expect(err).To(BeNil())
+ 
++			// The controller should ignore this Service, so no EndpointSlice should be created.
+ 			Eventually(func() (bool, error) {
+-				epsList, err := testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).
+-					List(context.TODO(), metav1.ListOptions{})
+-				if err != nil {
+-					return false, err
+-				}
+-				return len(epsList.Items) == 1, nil
+-			}).Should(BeTrue(), "Controller should create an EndpointSlice in infra cluster for the no-selector LB service")
+-
+-			svcWithSelector, err := testVals.infraClient.CoreV1().Services(infraNamespace).Get(
+-				context.TODO(), noSelectorSvcName, metav1.GetOptions{})
+-			Expect(err).To(BeNil())
+-
+-			svcWithSelector.Spec.Selector = map[string]string{"app": "test-value"}
+-			_, err = testVals.infraClient.CoreV1().Services(infraNamespace).
+-				Update(context.TODO(), svcWithSelector, metav1.UpdateOptions{})
+-			Expect(err).To(BeNil())
+-
+-			Eventually(func() (bool, error) {
+-				epsList, err := testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).
+-					List(context.TODO(), metav1.ListOptions{})
++				epsList, err := testVals.infraClient.DiscoveryV1().EndpointSlices(infraNamespace).List(context.TODO(), metav1.ListOptions{})
+ 				if err != nil {
+ 					return false, err
+ 				}
++				// Expect zero slices since cluster label does not match "test-cluster"
+ 				return len(epsList.Items) == 0, nil
+-			}).Should(BeTrue(), "All EndpointSlices should be removed after Service acquires a selector (no new slices created)")
++			}).Should(BeTrue(), "Services with a different cluster label should be ignored")
+ 		})
+ 	})
+ })
diff --git a/packages/core/platform/bundles/distro-full.yaml b/packages/core/platform/bundles/distro-full.yaml
index d44eb3df..05a23963 100644
--- a/packages/core/platform/bundles/distro-full.yaml
+++ b/packages/core/platform/bundles/distro-full.yaml
@@ -218,3 +218,8 @@ releases:
   privileged: true
   optional: true
   dependsOn: [cilium]
+
+- name: reloader
+  releaseName: reloader
+  chart: cozy-reloader
+  namespace: cozy-reloader
diff --git a/packages/core/platform/bundles/paas-full.yaml b/packages/core/platform/bundles/paas-full.yaml
index cd9cb806..42133a73 100644
--- a/packages/core/platform/bundles/paas-full.yaml
+++ b/packages/core/platform/bundles/paas-full.yaml
@@ -205,7 +205,7 @@ releases:
   releaseName: piraeus-operator
   chart: cozy-piraeus-operator
   namespace: cozy-linstor
-  dependsOn: [cilium,kubeovn,cert-manager]
+  dependsOn: [cilium,kubeovn,cert-manager,victoria-metrics-operator]
 
 - name: linstor
   releaseName: linstor
@@ -380,3 +380,8 @@ releases:
   namespace: cozy-vertical-pod-autoscaler
   privileged: true
   dependsOn: [monitoring-agents]
+
+- name: reloader
+  releaseName: reloader
+  chart: cozy-reloader
+  namespace: cozy-reloader
diff --git a/packages/extra/bootbox/Chart.yaml b/packages/extra/bootbox/Chart.yaml
index f5d8dd39..ee45ed33 100644
--- a/packages/extra/bootbox/Chart.yaml
+++ b/packages/extra/bootbox/Chart.yaml
@@ -3,4 +3,4 @@ name: bootbox
 description: PXE hardware provisioning
 icon: /logos/bootbox.svg
 type: application
-version: 0.1.0
+version: 0.1.1
diff --git a/packages/extra/bootbox/templates/matchbox/machines.yaml b/packages/extra/bootbox/templates/matchbox/machines.yaml
index 64231f23..e2733b89 100644
--- a/packages/extra/bootbox/templates/matchbox/machines.yaml
+++ b/packages/extra/bootbox/templates/matchbox/machines.yaml
@@ -17,7 +17,7 @@ spec:
   {{- range $mac := $m.mac }}
   - dhcp:
       hostname: {{ $m.hostname }}
-      mac: {{ $mac }}
+      mac: {{ lower $mac }}
       {{- with $m.arch }}
       arch: {{ . }}
       {{- end }}
diff --git a/packages/extra/versions_map b/packages/extra/versions_map
index f190ebcb..4f56e742 100644
--- a/packages/extra/versions_map
+++ b/packages/extra/versions_map
@@ -1,4 +1,5 @@
-bootbox 0.1.0 HEAD
+bootbox 0.1.0 d557050e
+bootbox 0.1.1 HEAD
 etcd 1.0.0 f7eaab0
 etcd 2.0.0 a6d0f7cf
 etcd 2.0.1 6fc1cc7d
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/Chart.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/Chart.yaml
index 39142ef4..fd7b8103 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/Chart.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/Chart.yaml
@@ -1,9 +1,9 @@
 annotations:
   artifacthub.io/changes: |
-    - Update Ingress-Nginx version controller-v1.11.5
+    - Update Ingress-Nginx version controller-v1.11.2
   artifacthub.io/prerelease: "false"
 apiVersion: v2
-appVersion: 1.11.5
+appVersion: 1.11.2
 description: Ingress controller for Kubernetes using NGINX as a reverse proxy and
   load balancer
 home: https://github.com/kubernetes/ingress-nginx
@@ -15,9 +15,11 @@ kubeVersion: '>=1.21.0-0'
 maintainers:
 - name: cpanato
 - name: Gacko
+- name: puerco
+- name: rikatz
 - name: strongjz
 - name: tao12345666333
 name: ingress-nginx
 sources:
 - https://github.com/kubernetes/ingress-nginx
-version: 4.11.5
+version: 4.11.2
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/OWNERS b/packages/system/ingress-nginx/charts/ingress-nginx/OWNERS
index 428474f6..d588ede6 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/OWNERS
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/OWNERS
@@ -1,4 +1,10 @@
 # See the OWNERS docs: https://www.kubernetes.dev/docs/guide/owners
 
+approvers:
+- ingress-nginx-helm-maintainers
+
+reviewers:
+- ingress-nginx-helm-reviewers
+
 labels:
 - area/helm
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/README.md b/packages/system/ingress-nginx/charts/ingress-nginx/README.md
index d36a564c..26eab285 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/README.md
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/README.md
@@ -2,7 +2,7 @@
 
 [ingress-nginx](https://github.com/kubernetes/ingress-nginx) Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer
 
-![Version: 4.11.5](https://img.shields.io/badge/Version-4.11.5-informational?style=flat-square) ![AppVersion: 1.11.5](https://img.shields.io/badge/AppVersion-1.11.5-informational?style=flat-square)
+![Version: 4.11.2](https://img.shields.io/badge/Version-4.11.2-informational?style=flat-square) ![AppVersion: 1.11.2](https://img.shields.io/badge/AppVersion-1.11.2-informational?style=flat-square)
 
 To use, add `ingressClassName: nginx` spec field or the `kubernetes.io/ingress.class: nginx` annotation to your Ingress resources.
 
@@ -229,24 +229,6 @@ Detail of how and why are in [this issue](https://github.com/helm/charts/pull/13
 
 As of version `1.26.0` of this chart, by simply not providing any clusterIP value, `invalid: spec.clusterIP: Invalid value: "": field is immutable` will no longer occur since `clusterIP: ""` will not be rendered.
 
-### Pod Security Admission
-
-You can use Pod Security Admission by applying labels to the `ingress-nginx` namespace as instructed by the [documentation](https://kubernetes.io/docs/tasks/configure-pod-container/enforce-standards-namespace-labels).
-
-Example:
-
-```yaml
-apiVersion: v1
-kind: Namespace
-metadata:
-  name: ingress-nginx
-  labels:
-    kubernetes.io/metadata.name: ingress-nginx
-    name: ingress-nginx
-    pod-security.kubernetes.io/enforce: restricted
-    pod-security.kubernetes.io/enforce-version: v1.31
-```
-
 ## Values
 
 | Key | Type | Default | Description |
@@ -271,11 +253,11 @@ metadata:
 | controller.admissionWebhooks.namespaceSelector | object | `{}` |  |
 | controller.admissionWebhooks.objectSelector | object | `{}` |  |
 | controller.admissionWebhooks.patch.enabled | bool | `true` |  |
-| controller.admissionWebhooks.patch.image.digest | string | `"sha256:e8825994b7a2c7497375a9b945f386506ca6a3eda80b89b74ef2db743f66a5ea"` |  |
+| controller.admissionWebhooks.patch.image.digest | string | `"sha256:a320a50cc91bd15fd2d6fa6de58bd98c1bd64b9a6f926ce23a600d87043455a3"` |  |
 | controller.admissionWebhooks.patch.image.image | string | `"ingress-nginx/kube-webhook-certgen"` |  |
 | controller.admissionWebhooks.patch.image.pullPolicy | string | `"IfNotPresent"` |  |
 | controller.admissionWebhooks.patch.image.registry | string | `"registry.k8s.io"` |  |
-| controller.admissionWebhooks.patch.image.tag | string | `"v1.5.2"` |  |
+| controller.admissionWebhooks.patch.image.tag | string | `"v1.4.3"` |  |
 | controller.admissionWebhooks.patch.labels | object | `{}` | Labels to be added to patch job resources |
 | controller.admissionWebhooks.patch.networkPolicy.enabled | bool | `false` | Enable 'networkPolicy' or not |
 | controller.admissionWebhooks.patch.nodeSelector."kubernetes.io/os" | string | `"linux"` |  |
@@ -343,8 +325,8 @@ metadata:
 | controller.hostname | object | `{}` | Optionally customize the pod hostname. |
 | controller.image.allowPrivilegeEscalation | bool | `false` |  |
 | controller.image.chroot | bool | `false` |  |
-| controller.image.digest | string | `"sha256:a1cbad75b0a7098bf9325132794dddf9eef917e8a7fe246749a4cea7ff6f01eb"` |  |
-| controller.image.digestChroot | string | `"sha256:ec9df3eb6b06563a079ee46045da94cbf750f7dbb16fdbcb9e3265b551ed72ad"` |  |
+| controller.image.digest | string | `"sha256:d5f8217feeac4887cb1ed21f27c2674e58be06bd8f5184cacea2a69abaf78dce"` |  |
+| controller.image.digestChroot | string | `"sha256:21b55a2f0213a18b91612a8c0850167e00a8e34391fd595139a708f9c047e7a8"` |  |
 | controller.image.image | string | `"ingress-nginx/controller"` |  |
 | controller.image.pullPolicy | string | `"IfNotPresent"` |  |
 | controller.image.readOnlyRootFilesystem | bool | `false` |  |
@@ -352,7 +334,7 @@ metadata:
 | controller.image.runAsNonRoot | bool | `true` |  |
 | controller.image.runAsUser | int | `101` |  |
 | controller.image.seccompProfile.type | string | `"RuntimeDefault"` |  |
-| controller.image.tag | string | `"v1.11.5"` |  |
+| controller.image.tag | string | `"v1.11.2"` |  |
 | controller.ingressClass | string | `"nginx"` | For backwards compatibility with ingress.class annotation, use ingressClass. Algorithm is as follows, first ingressClassName is considered, if not present, controller looks for ingress.class annotation |
 | controller.ingressClassByName | bool | `false` | Process IngressClass per name (additionally as per spec.controller). |
 | controller.ingressClassResource | object | `{"aliases":[],"annotations":{},"controllerValue":"k8s.io/ingress-nginx","default":false,"enabled":true,"name":"nginx","parameters":{}}` | This section refers to the creation of the IngressClass resource. IngressClasses are immutable and cannot be changed after creation. We do not support namespaced IngressClasses, yet, so a ClusterRole and a ClusterRoleBinding is required. |
@@ -384,7 +366,7 @@ metadata:
 | controller.livenessProbe.periodSeconds | int | `10` |  |
 | controller.livenessProbe.successThreshold | int | `1` |  |
 | controller.livenessProbe.timeoutSeconds | int | `1` |  |
-| controller.maxmindLicenseKey | string | `""` | Maxmind license key to download GeoLite2 Databases. # https://blog.maxmind.com/2019/12/significant-changes-to-accessing-and-using-geolite2-databases/ |
+| controller.maxmindLicenseKey | string | `""` | Maxmind license key to download GeoLite2 Databases. # https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-geolite2-databases |
 | controller.metrics.enabled | bool | `false` |  |
 | controller.metrics.port | int | `10254` |  |
 | controller.metrics.portName | string | `"metrics"` |  |
@@ -398,7 +380,7 @@ metadata:
 | controller.metrics.service.servicePort | int | `10254` |  |
 | controller.metrics.service.type | string | `"ClusterIP"` |  |
 | controller.metrics.serviceMonitor.additionalLabels | object | `{}` |  |
-| controller.metrics.serviceMonitor.annotations | object | `{}` | Annotations to be added to the ServiceMonitor. |
+| controller.metrics.serviceMonitor.annotations | object | `{}` |  |
 | controller.metrics.serviceMonitor.enabled | bool | `false` |  |
 | controller.metrics.serviceMonitor.metricRelabelings | list | `[]` |  |
 | controller.metrics.serviceMonitor.namespace | string | `""` |  |
@@ -533,7 +515,7 @@ metadata:
 | defaultBackend.livenessProbe.periodSeconds | int | `10` |  |
 | defaultBackend.livenessProbe.successThreshold | int | `1` |  |
 | defaultBackend.livenessProbe.timeoutSeconds | int | `5` |  |
-| defaultBackend.minAvailable | int | `1` | Minimum available pods set in PodDisruptionBudget. |
+| defaultBackend.minAvailable | int | `1` |  |
 | defaultBackend.minReadySeconds | int | `0` | `minReadySeconds` to avoid killing pods before we are ready # |
 | defaultBackend.name | string | `"defaultbackend"` |  |
 | defaultBackend.networkPolicy.enabled | bool | `false` | Enable 'networkPolicy' or not |
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/README.md.gotmpl b/packages/system/ingress-nginx/charts/ingress-nginx/README.md.gotmpl
index 3cb9d565..17b029bb 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/README.md.gotmpl
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/README.md.gotmpl
@@ -226,22 +226,4 @@ Detail of how and why are in [this issue](https://github.com/helm/charts/pull/13
 
 As of version `1.26.0` of this chart, by simply not providing any clusterIP value, `invalid: spec.clusterIP: Invalid value: "": field is immutable` will no longer occur since `clusterIP: ""` will not be rendered.
 
-### Pod Security Admission
-
-You can use Pod Security Admission by applying labels to the `ingress-nginx` namespace as instructed by the [documentation](https://kubernetes.io/docs/tasks/configure-pod-container/enforce-standards-namespace-labels).
-
-Example:
-
-```yaml
-apiVersion: v1
-kind: Namespace
-metadata:
-  name: ingress-nginx
-  labels:
-    kubernetes.io/metadata.name: ingress-nginx
-    name: ingress-nginx
-    pod-security.kubernetes.io/enforce: restricted
-    pod-security.kubernetes.io/enforce-version: v1.31
-```
-
 {{ template "chart.valuesSection" . }}
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-admission-tls-cert-manager-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-admission-tls-cert-manager-values.yaml
new file mode 100644
index 00000000..a13241cd
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-admission-tls-cert-manager-values.yaml
@@ -0,0 +1,6 @@
+controller:
+  admissionWebhooks:
+    certManager:
+      enabled: true
+  service:
+    type: ClusterIP
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-custom-ingressclass-flags.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-custom-ingressclass-flags.yaml
new file mode 100644
index 00000000..b28a2326
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-custom-ingressclass-flags.yaml
@@ -0,0 +1,7 @@
+controller:
+  watchIngressWithoutClass: true
+  ingressClassResource:
+    name: custom-nginx
+    enabled: true
+    default: true
+    controllerValue: "k8s.io/custom-nginx"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-extra-modules-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-extra-modules-values.yaml
deleted file mode 100644
index edf12e77..00000000
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-extra-modules-values.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
-controller:
-  image:
-    repository: ingress-controller/controller
-    tag: 1.0.0-dev
-    digest: null
-
-  service:
-    type: ClusterIP
-
-  kind: DaemonSet
-
-  extraModules:
-  - name: opentelemetry
-    image:
-      registry: registry.k8s.io
-      image: ingress-nginx/opentelemetry-1.25.3
-      tag: v20240813-b933310d
-      digest: sha256:f7604ac0547ed64d79b98d92133234e66c2c8aade3c1f4809fed5eec1fb7f922
-      distroless: true
-    containerSecurityContext:
-      runAsNonRoot: true
-      runAsUser: 65532
-      runAsGroup: 65532
-      allowPrivilegeEscalation: false
-      seccompProfile:
-        type: RuntimeDefault
-      capabilities:
-        drop:
-        - ALL
-      readOnlyRootFilesystem: true
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-extra-modules-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-extra-modules-values.yaml
deleted file mode 100644
index d4083cc3..00000000
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-extra-modules-values.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
-controller:
-  image:
-    repository: ingress-controller/controller
-    tag: 1.0.0-dev
-    digest: null
-
-  service:
-    type: ClusterIP
-
-  kind: Deployment
-
-  extraModules:
-  - name: opentelemetry
-    image:
-      registry: registry.k8s.io
-      image: ingress-nginx/opentelemetry-1.25.3
-      tag: v20240813-b933310d
-      digest: sha256:f7604ac0547ed64d79b98d92133234e66c2c8aade3c1f4809fed5eec1fb7f922
-      distroless: true
-    containerSecurityContext:
-      runAsNonRoot: true
-      runAsUser: 65532
-      runAsGroup: 65532
-      allowPrivilegeEscalation: false
-      seccompProfile:
-        type: RuntimeDefault
-      capabilities:
-        drop:
-        - ALL
-      readOnlyRootFilesystem: true
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-opentelemetry-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-opentelemetry-values.yaml
deleted file mode 100644
index 9443ddef..00000000
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-opentelemetry-values.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-controller:
-  image:
-    repository: ingress-controller/controller
-    tag: 1.0.0-dev
-    digest: null
-
-  service:
-    type: ClusterIP
-
-  kind: Deployment
-
-  opentelemetry:
-    enabled: true
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-ingressclass-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-ingressclass-values.yaml
deleted file mode 100644
index c06429f9..00000000
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-ingressclass-values.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-controller:
-  image:
-    repository: ingress-controller/controller
-    tag: 1.0.0-dev
-    digest: null
-
-  service:
-    type: ClusterIP
-
-  ingressClassResource:
-    name: custom-nginx
-    default: true
-    controllerValue: k8s.io/custom-nginx
-
-  watchIngressWithoutClass: true
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-customconfig-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-customconfig-values.yaml
new file mode 100644
index 00000000..4393a5bc
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-customconfig-values.yaml
@@ -0,0 +1,14 @@
+controller:
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  kind: DaemonSet
+  allowSnippetAnnotations: false
+  admissionWebhooks:
+    enabled: false
+  service:
+    type: ClusterIP
+
+  config:
+    use-proxy-protocol: "true"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-customnodeport-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-customnodeport-values.yaml
new file mode 100644
index 00000000..1d94be21
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-customnodeport-values.yaml
@@ -0,0 +1,22 @@
+controller:
+  kind: DaemonSet
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  admissionWebhooks:
+    enabled: false
+
+  service:
+    type: NodePort
+    nodePorts:
+      tcp:
+        9000: 30090
+      udp:
+        9001: 30091
+
+tcp:
+  9000: "default/test:8080"
+
+udp:
+  9001: "default/test:8080"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-extra-modules.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-extra-modules.yaml
new file mode 100644
index 00000000..52a32fcb
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-extra-modules.yaml
@@ -0,0 +1,13 @@
+controller:
+  kind: DaemonSet
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+  service:
+    type: ClusterIP
+  extraModules:
+    - name: opentelemetry
+      image:
+        registry: registry.k8s.io
+        image: busybox
+        tag: latest
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-headers-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-headers-values.yaml
new file mode 100644
index 00000000..ab7d47bd
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-headers-values.yaml
@@ -0,0 +1,14 @@
+controller:
+  kind: DaemonSet
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  admissionWebhooks:
+    enabled: false
+  addHeaders:
+    X-Frame-Options: deny
+  proxySetHeaders:
+    X-Forwarded-Proto: https
+  service:
+    type: ClusterIP
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-service-internal-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-internal-lb-values.yaml
similarity index 81%
rename from packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-service-internal-values.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-internal-lb-values.yaml
index 11108fbc..0a200a74 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-service-internal-values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-internal-lb-values.yaml
@@ -1,12 +1,13 @@
 controller:
+  kind: DaemonSet
   image:
     repository: ingress-controller/controller
     tag: 1.0.0-dev
     digest: null
-
+  admissionWebhooks:
+    enabled: false
   service:
     type: ClusterIP
-
     internal:
       enabled: true
       annotations:
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-nodeport-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-nodeport-values.yaml
new file mode 100644
index 00000000..3b7aa2fc
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-nodeport-values.yaml
@@ -0,0 +1,10 @@
+controller:
+  kind: DaemonSet
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  admissionWebhooks:
+    enabled: false
+  service:
+    type: NodePort
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-podannotations-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-podannotations-values.yaml
similarity index 81%
rename from packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-podannotations-values.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-podannotations-values.yaml
index 405992ef..0b55306a 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-podannotations-values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-podannotations-values.yaml
@@ -1,16 +1,17 @@
 controller:
+  kind: DaemonSet
   image:
     repository: ingress-controller/controller
     tag: 1.0.0-dev
     digest: null
-
+  admissionWebhooks:
+    enabled: false
+  metrics:
+    enabled: true
   service:
     type: ClusterIP
-
-  kind: DaemonSet
-
   podAnnotations:
-    prometheus.io/scrape: "true"
+    prometheus.io/path: /metrics
     prometheus.io/port: "10254"
     prometheus.io/scheme: http
-    prometheus.io/path: /metrics
+    prometheus.io/scrape: "true"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-tcp-udp-configMapNamespace-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-tcp-udp-configMapNamespace-values.yaml
new file mode 100644
index 00000000..acd86a77
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-tcp-udp-configMapNamespace-values.yaml
@@ -0,0 +1,20 @@
+controller:
+  kind: DaemonSet
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  admissionWebhooks:
+    enabled: false
+  service:
+    type: ClusterIP
+  tcp:
+    configMapNamespace: default
+  udp:
+    configMapNamespace: default
+
+tcp:
+  9000: "default/test:8080"
+
+udp:
+  9001: "default/test:8080"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-tcp-udp-portNamePrefix-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-tcp-udp-portNamePrefix-values.yaml
new file mode 100644
index 00000000..90b0f57a
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-tcp-udp-portNamePrefix-values.yaml
@@ -0,0 +1,18 @@
+controller:
+  kind: DaemonSet
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  admissionWebhooks:
+    enabled: false
+  service:
+    type: ClusterIP
+
+tcp:
+  9000: "default/test:8080"
+
+udp:
+  9001: "default/test:8080"
+
+portNamePrefix: "port"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-tcp-udp-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-tcp-udp-values.yaml
new file mode 100644
index 00000000..25ee64d8
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-tcp-udp-values.yaml
@@ -0,0 +1,16 @@
+controller:
+  kind: DaemonSet
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  admissionWebhooks:
+    enabled: false
+  service:
+    type: ClusterIP
+
+tcp:
+  9000: "default/test:8080"
+
+udp:
+  9001: "default/test:8080"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-tcp-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-tcp-values.yaml
new file mode 100644
index 00000000..380c8b4b
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/daemonset-tcp-values.yaml
@@ -0,0 +1,14 @@
+controller:
+  kind: DaemonSet
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  admissionWebhooks:
+    enabled: false
+  service:
+    type: ClusterIP
+
+tcp:
+  9000: "default/test:8080"
+  9001: "default/test:8080"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/admission-webhooks-cert-manager-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deamonset-default-values.yaml
similarity index 79%
rename from packages/system/ingress-nginx/charts/ingress-nginx/ci/admission-webhooks-cert-manager-values.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/ci/deamonset-default-values.yaml
index 7eafd0c5..82fa23e8 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/admission-webhooks-cert-manager-values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deamonset-default-values.yaml
@@ -1,12 +1,10 @@
 controller:
+  kind: DaemonSet
   image:
     repository: ingress-controller/controller
     tag: 1.0.0-dev
     digest: null
-
+  admissionWebhooks:
+    enabled: false
   service:
     type: ClusterIP
-
-  admissionWebhooks:
-    certManager:
-      enabled: true
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/deamonset-metrics-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deamonset-metrics-values.yaml
new file mode 100644
index 00000000..cb3cb54b
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deamonset-metrics-values.yaml
@@ -0,0 +1,12 @@
+controller:
+  kind: DaemonSet
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  admissionWebhooks:
+    enabled: false
+  metrics:
+    enabled: true
+  service:
+    type: ClusterIP
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-metrics-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deamonset-webhook-values.yaml
similarity index 89%
rename from packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-metrics-values.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/ci/deamonset-webhook-values.yaml
index 7a98580c..54d364df 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-metrics-values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deamonset-webhook-values.yaml
@@ -1,13 +1,10 @@
 controller:
+  kind: DaemonSet
   image:
     repository: ingress-controller/controller
     tag: 1.0.0-dev
     digest: null
-
+  admissionWebhooks:
+    enabled: true
   service:
     type: ClusterIP
-
-  kind: DaemonSet
-
-  metrics:
-    enabled: true
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-hpa-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-autoscaling-behavior-values.yaml
similarity index 71%
rename from packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-hpa-values.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-autoscaling-behavior-values.yaml
index 54a0d2f7..dca3f35f 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-hpa-values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-autoscaling-behavior-values.yaml
@@ -1,12 +1,4 @@
 controller:
-  image:
-    repository: ingress-controller/controller
-    tag: 1.0.0-dev
-    digest: null
-
-  service:
-    type: ClusterIP
-
   autoscaling:
     enabled: true
     behavior:
@@ -16,3 +8,7 @@ controller:
         - type: Pods
           value: 1
           periodSeconds: 180
+  admissionWebhooks:
+    enabled: false
+  service:
+    type: ClusterIP
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-autoscaling-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-autoscaling-values.yaml
new file mode 100644
index 00000000..b8b3ac68
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-autoscaling-values.yaml
@@ -0,0 +1,11 @@
+controller:
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  autoscaling:
+    enabled: true
+  admissionWebhooks:
+    enabled: false
+  service:
+    type: ClusterIP
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-configmap-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-customconfig-values.yaml
similarity index 70%
rename from packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-configmap-values.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-customconfig-values.yaml
index a7029895..17494184 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-configmap-values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-customconfig-values.yaml
@@ -3,9 +3,10 @@ controller:
     repository: ingress-controller/controller
     tag: 1.0.0-dev
     digest: null
-
-  service:
-    type: ClusterIP
-
   config:
     use-proxy-protocol: "true"
+  allowSnippetAnnotations: false
+  admissionWebhooks:
+    enabled: false
+  service:
+    type: ClusterIP
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-service-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-customnodeport-values.yaml
similarity index 69%
rename from packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-service-values.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-customnodeport-values.yaml
index 9039368c..a564eaf9 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-service-values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-customnodeport-values.yaml
@@ -3,20 +3,18 @@ controller:
     repository: ingress-controller/controller
     tag: 1.0.0-dev
     digest: null
-
+  admissionWebhooks:
+    enabled: false
   service:
     type: NodePort
-
     nodePorts:
       tcp:
         9000: 30090
       udp:
         9001: 30091
 
-portNamePrefix: port
-
 tcp:
-  9000: default/test:8080
+  9000: "default/test:8080"
 
 udp:
-  9001: default/test:8080
+  9001: "default/test:8080"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-default-values.yaml
similarity index 78%
rename from packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-values.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-default-values.yaml
index d34025c8..9f46b4e7 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-default-values.yaml
@@ -1,10 +1,8 @@
+# Left blank to test default values
 controller:
   image:
     repository: ingress-controller/controller
     tag: 1.0.0-dev
     digest: null
-
   service:
     type: ClusterIP
-
-  kind: DaemonSet
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-extra-modules-default-container-sec-context.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-extra-modules-default-container-sec-context.yaml
new file mode 100644
index 00000000..91b1b98a
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-extra-modules-default-container-sec-context.yaml
@@ -0,0 +1,15 @@
+controller:
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  service:
+    type: ClusterIP
+  containerSecurityContext:
+    allowPrivilegeEscalation: false
+  extraModules:
+    - name: opentelemetry
+      image:
+        registry: registry.k8s.io
+        image: busybox
+        tag: latest
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-extra-modules-specific-container-sec-context.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-extra-modules-specific-container-sec-context.yaml
new file mode 100644
index 00000000..b6013c7d
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-extra-modules-specific-container-sec-context.yaml
@@ -0,0 +1,15 @@
+controller:
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  service:
+    type: ClusterIP
+  extraModules:
+    - name: opentelemetry
+      image:
+        registry: registry.k8s.io
+        image: busybox
+        tag: latest
+      containerSecurityContext:
+        allowPrivilegeEscalation: false
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-extra-modules.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-extra-modules.yaml
new file mode 100644
index 00000000..2fbe1cc0
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-extra-modules.yaml
@@ -0,0 +1,13 @@
+controller:
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  service:
+    type: ClusterIP
+  extraModules:
+    - name: opentelemetry
+      image:
+        registry: registry.k8s.io
+        image: busybox
+        tag: latest
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-configmap-proxyheaders-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-headers-values.yaml
similarity index 69%
rename from packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-configmap-proxyheaders-values.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-headers-values.yaml
index e23a13c0..17a11ac3 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-configmap-proxyheaders-values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-headers-values.yaml
@@ -3,9 +3,11 @@ controller:
     repository: ingress-controller/controller
     tag: 1.0.0-dev
     digest: null
-
-  service:
-    type: ClusterIP
-
+  admissionWebhooks:
+    enabled: false
+  addHeaders:
+    X-Frame-Options: deny
   proxySetHeaders:
     X-Forwarded-Proto: https
+  service:
+    type: ClusterIP
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-internal-lb-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-internal-lb-values.yaml
new file mode 100644
index 00000000..663ccb9d
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-internal-lb-values.yaml
@@ -0,0 +1,19 @@
+controller:
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  admissionWebhooks:
+    enabled: false
+  service:
+    type: ClusterIP
+    internal:
+      enabled: true
+      annotations:
+        service.beta.kubernetes.io/aws-load-balancer-internal: "true"
+      ports:
+        http: 443
+        https: 80
+      targetPorts:
+        http: 443
+        https: 80
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-metrics-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-metrics-values.yaml
new file mode 100644
index 00000000..9209ad5a
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-metrics-values.yaml
@@ -0,0 +1,11 @@
+controller:
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  admissionWebhooks:
+    enabled: false
+  metrics:
+    enabled: true
+  service:
+    type: ClusterIP
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-nodeport-values.yaml
similarity index 65%
rename from packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-values.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-nodeport-values.yaml
index 1b092dc0..cd9b3235 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-nodeport-values.yaml
@@ -3,8 +3,7 @@ controller:
     repository: ingress-controller/controller
     tag: 1.0.0-dev
     digest: null
-
+  admissionWebhooks:
+    enabled: false
   service:
-    type: ClusterIP
-
-  kind: Deployment
+    type: NodePort
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-opentelemetry-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-opentelemetry-customregistry-values.yaml
similarity index 88%
rename from packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-opentelemetry-values.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-opentelemetry-customregistry-values.yaml
index 179ab2a8..fb3ef444 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-daemonset-opentelemetry-values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-opentelemetry-customregistry-values.yaml
@@ -3,11 +3,7 @@ controller:
     repository: ingress-controller/controller
     tag: 1.0.0-dev
     digest: null
-
   service:
     type: ClusterIP
-
-  kind: DaemonSet
-
   opentelemetry:
     enabled: true
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-podannotations-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-podannotations-values.yaml
similarity index 80%
rename from packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-podannotations-values.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-podannotations-values.yaml
index cf1f2611..b48d93c4 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-podannotations-values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-podannotations-values.yaml
@@ -3,14 +3,14 @@ controller:
     repository: ingress-controller/controller
     tag: 1.0.0-dev
     digest: null
-
+  admissionWebhooks:
+    enabled: false
+  metrics:
+    enabled: true
   service:
     type: ClusterIP
-
-  kind: Deployment
-
   podAnnotations:
-    prometheus.io/scrape: "true"
+    prometheus.io/path: /metrics
     prometheus.io/port: "10254"
     prometheus.io/scheme: http
-    prometheus.io/path: /metrics
+    prometheus.io/scrape: "true"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-tcp-udp-configMapNamespace-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-tcp-udp-configMapNamespace-values.yaml
new file mode 100644
index 00000000..c51a4e91
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-tcp-udp-configMapNamespace-values.yaml
@@ -0,0 +1,19 @@
+controller:
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  admissionWebhooks:
+    enabled: false
+  service:
+    type: ClusterIP
+  tcp:
+    configMapNamespace: default
+  udp:
+    configMapNamespace: default
+
+tcp:
+  9000: "default/test:8080"
+
+udp:
+  9001: "default/test:8080"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-tcp-udp-portNamePrefix-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-tcp-udp-portNamePrefix-values.yaml
new file mode 100644
index 00000000..56323c5e
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-tcp-udp-portNamePrefix-values.yaml
@@ -0,0 +1,17 @@
+controller:
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  admissionWebhooks:
+    enabled: false
+  service:
+    type: ClusterIP
+
+tcp:
+  9000: "default/test:8080"
+
+udp:
+  9001: "default/test:8080"
+
+portNamePrefix: "port"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-tcp-udp-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-tcp-udp-values.yaml
new file mode 100644
index 00000000..5b45b69d
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-tcp-udp-values.yaml
@@ -0,0 +1,15 @@
+controller:
+  image:
+    repository: ingress-controller/controller
+    tag: 1.0.0-dev
+    digest: null
+  admissionWebhooks:
+    enabled: false
+  service:
+    type: ClusterIP
+
+tcp:
+  9000: "default/test:8080"
+
+udp:
+  9001: "default/test:8080"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-configmap-addheaders-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-tcp-values.yaml
similarity index 68%
rename from packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-configmap-addheaders-values.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-tcp-values.yaml
index 460a610b..ac0b6e60 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-configmap-addheaders-values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-tcp-values.yaml
@@ -3,9 +3,9 @@ controller:
     repository: ingress-controller/controller
     tag: 1.0.0-dev
     digest: null
-
   service:
     type: ClusterIP
 
-  addHeaders:
-    X-Frame-Options: deny
+tcp:
+  9000: "default/test:8080"
+  9001: "default/test:8080"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-webhook-extraEnvs-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-webhook-extraEnvs-values.yaml
new file mode 100644
index 00000000..95487b07
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-webhook-extraEnvs-values.yaml
@@ -0,0 +1,12 @@
+controller:
+  service:
+    type: ClusterIP
+  admissionWebhooks:
+    enabled: true
+    extraEnvs:
+      - name: FOO
+        value: foo
+      - name: TEST
+        value: test
+    patch:
+      enabled: true
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-webhook-resources-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-webhook-resources-values.yaml
new file mode 100644
index 00000000..49ebbb02
--- /dev/null
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-webhook-resources-values.yaml
@@ -0,0 +1,23 @@
+controller:
+  service:
+    type: ClusterIP
+  admissionWebhooks:
+    enabled: true
+    createSecretJob:
+      resources:
+        limits:
+          cpu: 10m
+          memory: 20Mi
+        requests:
+          cpu: 10m
+          memory: 20Mi
+    patchWebhookJob:
+      resources:
+        limits:
+          cpu: 10m
+          memory: 20Mi
+        requests:
+          cpu: 10m
+          memory: 20Mi
+    patch:
+      enabled: true
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-metrics-values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-webhook-values.yaml
similarity index 82%
rename from packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-metrics-values.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-webhook-values.yaml
index 9c95d347..76669a53 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/ci/controller-deployment-metrics-values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/ci/deployment-webhook-values.yaml
@@ -3,11 +3,7 @@ controller:
     repository: ingress-controller/controller
     tag: 1.0.0-dev
     digest: null
-
+  admissionWebhooks:
+    enabled: true
   service:
     type: ClusterIP
-
-  kind: Deployment
-
-  metrics:
-    enabled: true
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/templates/_helpers.tpl b/packages/system/ingress-nginx/charts/ingress-nginx/templates/_helpers.tpl
index 24cfd14a..99246888 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/templates/_helpers.tpl
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/templates/_helpers.tpl
@@ -203,7 +203,7 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this
 {{- end -}}
 
 {{/*
-Create the name of the default backend service account to use
+Create the name of the backend service account to use - only used when podsecuritypolicy is also enabled
 */}}
 {{- define "ingress-nginx.defaultBackend.serviceAccountName" -}}
 {{- if .Values.defaultBackend.serviceAccount.create -}}
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/templates/admission-webhooks/validating-webhook.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/templates/admission-webhooks/validating-webhook.yaml
index 0949cea7..4cd36a62 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/templates/admission-webhooks/validating-webhook.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/templates/admission-webhooks/validating-webhook.yaml
@@ -40,7 +40,6 @@ webhooks:
       service:
         name: {{ include "ingress-nginx.controller.fullname" . }}-admission
         namespace: {{ include "ingress-nginx.namespace" . }}
-        port: {{ .Values.controller.admissionWebhooks.service.servicePort }}
         path: /networking/v1/ingresses
     {{- if .Values.controller.admissionWebhooks.timeoutSeconds }}
     timeoutSeconds: {{ .Values.controller.admissionWebhooks.timeoutSeconds }}
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/templates/controller-prometheusrule.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/templates/controller-prometheusrules.yaml
similarity index 100%
rename from packages/system/ingress-nginx/charts/ingress-nginx/templates/controller-prometheusrule.yaml
rename to packages/system/ingress-nginx/charts/ingress-nginx/templates/controller-prometheusrules.yaml
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/templates/controller-service-webhook.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/templates/controller-service-webhook.yaml
index 67aac0d9..6dcf1a10 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/templates/controller-service-webhook.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/templates/controller-service-webhook.yaml
@@ -29,7 +29,7 @@ spec:
 {{- end }}
   ports:
     - name: https-webhook
-      port: {{ .Values.controller.admissionWebhooks.service.servicePort }}
+      port: 443
       targetPort: webhook
     {{- if semverCompare ">=1.20.0-0" .Capabilities.KubeVersion.Version }}
       appProtocol: https
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/templates/controller-servicemonitor.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/templates/controller-servicemonitor.yaml
index 93ab4d24..62301da4 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/templates/controller-servicemonitor.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/templates/controller-servicemonitor.yaml
@@ -3,48 +3,51 @@ apiVersion: monitoring.coreos.com/v1
 kind: ServiceMonitor
 metadata:
   name: {{ include "ingress-nginx.controller.fullname" . }}
-  {{- if .Values.controller.metrics.serviceMonitor.namespace }}
+{{- if .Values.controller.metrics.serviceMonitor.namespace }}
   namespace: {{ .Values.controller.metrics.serviceMonitor.namespace }}
-  {{- else }}
+{{- else }}
   namespace: {{ include "ingress-nginx.namespace" . }}
-  {{- end }}
+{{- end }}
   labels:
     {{- include "ingress-nginx.labels" . | nindent 4 }}
     app.kubernetes.io/component: controller
-    {{- if .Values.controller.metrics.serviceMonitor.additionalLabels }}
+  {{- if .Values.controller.metrics.serviceMonitor.additionalLabels }}
     {{- toYaml .Values.controller.metrics.serviceMonitor.additionalLabels | nindent 4 }}
-    {{- end }}
+  {{- end }}
   {{- if .Values.controller.metrics.serviceMonitor.annotations }}
   annotations: {{ toYaml .Values.controller.metrics.serviceMonitor.annotations | nindent 4 }}
   {{- end }}
 spec:
-  {{- if .Values.controller.metrics.serviceMonitor.namespaceSelector }}
-  namespaceSelector: {{ toYaml .Values.controller.metrics.serviceMonitor.namespaceSelector | nindent 4 }}
-  {{- else }}
-  namespaceSelector:
-    matchNames:
-    - {{ include "ingress-nginx.namespace" . }}
-  {{- end }}
-  selector:
-    matchLabels:
-      {{- include "ingress-nginx.selectorLabels" . | nindent 6 }}
-      app.kubernetes.io/component: controller
   endpoints:
-  - port: {{ .Values.controller.metrics.portName }}
-    interval: {{ .Values.controller.metrics.serviceMonitor.scrapeInterval }}
+    - port: {{ .Values.controller.metrics.portName }}
+      interval: {{ .Values.controller.metrics.serviceMonitor.scrapeInterval }}
     {{- if .Values.controller.metrics.serviceMonitor.honorLabels }}
-    honorLabels: true
+      honorLabels: true
     {{- end }}
     {{- if .Values.controller.metrics.serviceMonitor.relabelings }}
-    relabelings: {{ toYaml .Values.controller.metrics.serviceMonitor.relabelings | nindent 4 }}
+      relabelings: {{ toYaml .Values.controller.metrics.serviceMonitor.relabelings | nindent 8 }}
     {{- end }}
     {{- if .Values.controller.metrics.serviceMonitor.metricRelabelings }}
-    metricRelabelings: {{ toYaml .Values.controller.metrics.serviceMonitor.metricRelabelings | nindent 4 }}
+      metricRelabelings: {{ toYaml .Values.controller.metrics.serviceMonitor.metricRelabelings | nindent 8 }}
     {{- end }}
-  {{- if .Values.controller.metrics.serviceMonitor.jobLabel }}
+{{- if .Values.controller.metrics.serviceMonitor.jobLabel }}
   jobLabel: {{ .Values.controller.metrics.serviceMonitor.jobLabel | quote }}
+{{- end }}
+{{- if .Values.controller.metrics.serviceMonitor.namespaceSelector }}
+  namespaceSelector: {{ toYaml .Values.controller.metrics.serviceMonitor.namespaceSelector | nindent 4 }}
+{{- else }}
+  namespaceSelector:
+    matchNames:
+      - {{ include "ingress-nginx.namespace" . }}
+{{- end }}
+{{- if .Values.controller.metrics.serviceMonitor.targetLabels }}
+  targetLabels:
+  {{- range .Values.controller.metrics.serviceMonitor.targetLabels }}
+    - {{ . }}
   {{- end }}
-  {{- if .Values.controller.metrics.serviceMonitor.targetLabels }}
-  targetLabels: {{ toYaml .Values.controller.metrics.serviceMonitor.targetLabels | nindent 2 }}
-  {{- end }}
+{{- end }}
+  selector:
+    matchLabels:
+      {{- include "ingress-nginx.selectorLabels" . | nindent 6 }}
+      app.kubernetes.io/component: controller
 {{- end }}
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/templates/default-backend-deployment.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/templates/default-backend-deployment.yaml
index 6755e237..c6ccdd5c 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/templates/default-backend-deployment.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/templates/default-backend-deployment.yaml
@@ -102,7 +102,7 @@ spec:
     {{- if .Values.defaultBackend.nodeSelector }}
       nodeSelector: {{ toYaml .Values.defaultBackend.nodeSelector | nindent 8 }}
     {{- end }}
-      serviceAccountName: {{ include "ingress-nginx.defaultBackend.serviceAccountName" . }}
+      serviceAccountName: {{ template "ingress-nginx.defaultBackend.serviceAccountName" . }}
     {{- if .Values.defaultBackend.tolerations }}
       tolerations: {{ toYaml .Values.defaultBackend.tolerations | nindent 8 }}
     {{- end }}
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/templates/default-backend-poddisruptionbudget.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/templates/default-backend-poddisruptionbudget.yaml
index c8363fd4..f869e453 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/templates/default-backend-poddisruptionbudget.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/templates/default-backend-poddisruptionbudget.yaml
@@ -1,9 +1,5 @@
 {{- if .Values.defaultBackend.enabled -}}
-{{- $replicas := .Values.defaultBackend.replicaCount }}
-{{- if .Values.defaultBackend.autoscaling.enabled }}
-{{- $replicas = .Values.defaultBackend.autoscaling.minReplicas }}
-{{- end }}
-{{- if gt ($replicas | int) 1 }}
+{{- if or (gt (.Values.defaultBackend.replicaCount | int) 1) (gt (.Values.defaultBackend.autoscaling.minReplicas | int) 1) }}
 apiVersion: {{ ternary "policy/v1" "policy/v1beta1" (semverCompare ">=1.21.0-0" .Capabilities.KubeVersion.Version) }}
 kind: PodDisruptionBudget
 metadata:
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/templates/default-backend-serviceaccount.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/templates/default-backend-serviceaccount.yaml
index 6fd2d623..2afaf0c0 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/templates/default-backend-serviceaccount.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/templates/default-backend-serviceaccount.yaml
@@ -1,4 +1,4 @@
-{{- if and .Values.defaultBackend.enabled .Values.defaultBackend.serviceAccount.create -}}
+{{- if and .Values.defaultBackend.enabled  .Values.defaultBackend.serviceAccount.create -}}
 apiVersion: v1
 kind: ServiceAccount
 metadata:
@@ -8,7 +8,7 @@ metadata:
     {{- with .Values.defaultBackend.labels }}
     {{- toYaml . | nindent 4 }}
     {{- end }}
-  name: {{ include "ingress-nginx.defaultBackend.serviceAccountName" . }}
+  name: {{ template "ingress-nginx.defaultBackend.serviceAccountName" . }}
   namespace: {{ include "ingress-nginx.namespace" . }}
 automountServiceAccountToken: {{ .Values.defaultBackend.serviceAccount.automountServiceAccountToken }}
 {{- end }}
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/values.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/values.yaml
index 1c9f1c58..fbd0b31c 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/values.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/values.yaml
@@ -26,9 +26,9 @@ controller:
     ## for backwards compatibility consider setting the full image url via the repository value below
     ## use *either* current default registry/image or repository format or installing chart by providing the values.yaml will fail
     ## repository:
-    tag: "v1.11.5"
-    digest: sha256:a1cbad75b0a7098bf9325132794dddf9eef917e8a7fe246749a4cea7ff6f01eb
-    digestChroot: sha256:ec9df3eb6b06563a079ee46045da94cbf750f7dbb16fdbcb9e3265b551ed72ad
+    tag: "v1.11.2"
+    digest: sha256:d5f8217feeac4887cb1ed21f27c2674e58be06bd8f5184cacea2a69abaf78dce
+    digestChroot: sha256:21b55a2f0213a18b91612a8c0850167e00a8e34391fd595139a708f9c047e7a8
     pullPolicy: IfNotPresent
     runAsNonRoot: true
     # www-data -> uid 101
@@ -194,7 +194,7 @@ controller:
     # -- Annotations to be added to the udp config configmap
     annotations: {}
   # -- Maxmind license key to download GeoLite2 Databases.
-  ## https://blog.maxmind.com/2019/12/significant-changes-to-accessing-and-using-geolite2-databases/
+  ## https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-geolite2-databases
   maxmindLicenseKey: ""
   # -- Additional command line arguments to pass to Ingress-Nginx Controller
   # E.g. to specify the default SSL certificate you can use
@@ -299,8 +299,6 @@ controller:
   #       app.kubernetes.io/name: '{{ include "ingress-nginx.name" . }}'
   #       app.kubernetes.io/instance: '{{ .Release.Name }}'
   #       app.kubernetes.io/component: controller
-  #   matchLabelKeys:
-  #   - pod-template-hash
   #   topologyKey: topology.kubernetes.io/zone
   #   maxSkew: 1
   #   whenUnsatisfiable: ScheduleAnyway
@@ -309,8 +307,6 @@ controller:
   #       app.kubernetes.io/name: '{{ include "ingress-nginx.name" . }}'
   #       app.kubernetes.io/instance: '{{ .Release.Name }}'
   #       app.kubernetes.io/component: controller
-  #   matchLabelKeys:
-  #   - pod-template-hash
   #   topologyKey: kubernetes.io/hostname
   #   maxSkew: 1
   #   whenUnsatisfiable: ScheduleAnyway
@@ -808,8 +804,8 @@ controller:
         ## for backwards compatibility consider setting the full image url via the repository value below
         ## use *either* current default registry/image or repository format or installing chart by providing the values.yaml will fail
         ## repository:
-        tag: v1.5.2
-        digest: sha256:e8825994b7a2c7497375a9b945f386506ca6a3eda80b89b74ef2db743f66a5ea
+        tag: v1.4.3
+        digest: sha256:a320a50cc91bd15fd2d6fa6de58bd98c1bd64b9a6f926ce23a600d87043455a3
         pullPolicy: IfNotPresent
       # -- Provide a priority class name to the webhook patching job
       ##
@@ -877,7 +873,6 @@ controller:
     serviceMonitor:
       enabled: false
       additionalLabels: {}
-      # -- Annotations to be added to the ServiceMonitor.
       annotations: {}
       ## The label to use to retrieve the job name from.
       ## jobLabel: "app.kubernetes.io/name"
@@ -1067,8 +1062,6 @@ defaultBackend:
   #       app.kubernetes.io/name: '{{ include "ingress-nginx.name" . }}'
   #       app.kubernetes.io/instance: '{{ .Release.Name }}'
   #       app.kubernetes.io/component: default-backend
-  #   matchLabelKeys:
-  #   - pod-template-hash
   #   topologyKey: topology.kubernetes.io/zone
   #   maxSkew: 1
   #   whenUnsatisfiable: ScheduleAnyway
@@ -1077,8 +1070,6 @@ defaultBackend:
   #       app.kubernetes.io/name: '{{ include "ingress-nginx.name" . }}'
   #       app.kubernetes.io/instance: '{{ .Release.Name }}'
   #       app.kubernetes.io/component: default-backend
-  #   matchLabelKeys:
-  #   - pod-template-hash
   #   topologyKey: kubernetes.io/hostname
   #   maxSkew: 1
   #   whenUnsatisfiable: ScheduleAnyway
@@ -1099,7 +1090,6 @@ defaultBackend:
   ##
   podAnnotations: {}
   replicaCount: 1
-  # -- Minimum available pods set in PodDisruptionBudget.
   minAvailable: 1
   resources: {}
   # limits:
diff --git a/packages/system/ingress-nginx/values.yaml b/packages/system/ingress-nginx/values.yaml
index b20d647b..5571ff37 100644
--- a/packages/system/ingress-nginx/values.yaml
+++ b/packages/system/ingress-nginx/values.yaml
@@ -5,8 +5,8 @@ ingress-nginx:
     image:
       registry: ghcr.io
       image: cozystack/ingress-nginx-with-protobuf-exporter/controller
-      tag: v1.11.5
-      digest: sha256:b78ae118129a9417d4126744cab2fc2f777b3a9ac460d74caa4b57a479b98ead
+      tag: v1.11.2
+      digest: sha256:beba8869ee370599e1f26557a9669ebdc9481c07b34059f348eb3e17b647e7e0
     allowSnippetAnnotations: true
     replicaCount: 2
     admissionWebhooks:
@@ -16,7 +16,7 @@ ingress-nginx:
       enabled: true
     extraContainers:
     - name: protobuf-exporter
-      image: ghcr.io/cozystack/ingress-nginx-with-protobuf-exporter/protobuf-exporter:v1.11.5@sha256:1e60d53324c2028d6f20136cdd5553ebf2d1288aefc5900b96cd379680fc25dc
+      image: ghcr.io/kvaps/ingress-nginx-with-protobuf-exporter/protobuf-exporter:v1.11.2@sha256:6d9235a9ee6f2be1921db4687afbdcd85d145b087dd916b5a96455bdb5cff560
       args:
       - --server.telemetry-address=0.0.0.0:9090
       - --server.exporter-address=0.0.0.0:9091
@@ -65,3 +65,4 @@ ingress-nginx:
       requests:
         cpu: 10m
         memory: 20Mi
+
diff --git a/packages/system/linstor/hack/plunger/plunger-satellite.sh b/packages/system/linstor/hack/plunger/plunger-satellite.sh
index 5ecc184d..36087243 100755
--- a/packages/system/linstor/hack/plunger/plunger-satellite.sh
+++ b/packages/system/linstor/hack/plunger/plunger-satellite.sh
@@ -22,20 +22,22 @@ while true; do
   # the `/` path could not be a backing file for a loop device, so it's a good indicator of a stuck loop device
   # TODO describe the issue in more detail
   # Using the direct /usr/sbin/losetup as the linstor-satellite image has own wrapper in /usr/local
-  stale_loopbacks=$(/usr/sbin/losetup --json | jq -r '.[][] | select(."back-file" == "/ (deleted)").name')
+  stale_loopbacks=$(/usr/sbin/losetup --json | jq -r '.[][] | select(."back-file" == "/" or ."back-file" == "/ (deleted)").name' )
   for stale_device in $stale_loopbacks; do (
     echo "Detaching stuck loop device ${stale_device}"
     set -x
-    /usr/sbin/losetup --detach "${stale_device}"
+    /usr/sbin/losetup --detach "${stale_device}" || echo "Command failed"
   ); done
 
-  # Detect secondary volumes that lost connection and can be simply reconnected
+  # Detect secondary volumes that got suspended with force-io-failure
+  # As long as this is not a primary volume, it's somewhat safe to recreate the whole DRBD device.
+  # Backing block device is not touched.
   disconnected_secondaries=$(drbdadm status 2>/dev/null | awk '/pvc-.*role:Secondary.*force-io-failures:yes/ {print $1}')
   for secondary in $disconnected_secondaries; do (
     echo "Trying to reconnect secondary volume ${secondary}"
     set -x
-    drbdadm down "${secondary}"
-    drbdadm up "${secondary}"
+    drbdadm down "${secondary}" || echo "Command failed"
+    drbdadm up "${secondary}" || echo "Command failed"
   ); done
 
 done
diff --git a/packages/system/linstor/templates/cluster.yaml b/packages/system/linstor/templates/cluster.yaml
index fffb170d..4db670f5 100644
--- a/packages/system/linstor/templates/cluster.yaml
+++ b/packages/system/linstor/templates/cluster.yaml
@@ -43,3 +43,14 @@ spec:
           configMap:
             name: linstor-plunger
             defaultMode: 0755
+  patches:
+  - target:
+      group: apps
+      version: v1
+      kind: Deployment
+    patch: |
+      apiVersion: apps/v1
+      kind: Deployment
+      metadata:
+        annotations:
+          secret.reloader.stakater.com/auto: "true"
diff --git a/packages/system/linstor/templates/plunger/configmap-scripts.yaml b/packages/system/linstor/templates/plunger/configmap-scripts.yaml
index 9b5754ba..7dc72ef5 100644
--- a/packages/system/linstor/templates/plunger/configmap-scripts.yaml
+++ b/packages/system/linstor/templates/plunger/configmap-scripts.yaml
@@ -6,8 +6,4 @@ kind: ConfigMap
 metadata:
   name: linstor-plunger
   namespace: cozy-linstor
-data:
-{{- range $path, $file := $files }}
-  {{ $path | base }}: |
-    {{- $file | toString | nindent 4 }}
-{{- end -}}
+data: {{- $files.AsConfig | nindent 2 }}
diff --git a/packages/system/linstor/templates/satellites-reloader.yaml b/packages/system/linstor/templates/satellites-reloader.yaml
new file mode 100644
index 00000000..98ee5429
--- /dev/null
+++ b/packages/system/linstor/templates/satellites-reloader.yaml
@@ -0,0 +1,18 @@
+apiVersion: piraeus.io/v1
+kind: LinstorSatelliteConfiguration
+metadata:
+  name: cozystack-reloader
+spec:
+  patches:
+  - target:
+      group: apps
+      version: v1
+      kind: DaemonSet
+      name: linstor-satellite
+    patch: |
+      apiVersion: apps/v1
+      kind: DaemonSet
+      metadata:
+        name: linstor-satellite
+        annotations:
+          secret.reloader.stakater.com/auto: "true"
diff --git a/packages/system/reloader/Chart.yaml b/packages/system/reloader/Chart.yaml
new file mode 100644
index 00000000..9c7e9afb
--- /dev/null
+++ b/packages/system/reloader/Chart.yaml
@@ -0,0 +1,3 @@
+apiVersion: v2
+name: cozy-reloader
+version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
diff --git a/packages/system/reloader/Makefile b/packages/system/reloader/Makefile
new file mode 100644
index 00000000..378dc23c
--- /dev/null
+++ b/packages/system/reloader/Makefile
@@ -0,0 +1,10 @@
+export NAME=reloader
+export NAMESPACE=cozy-$(NAME)
+
+include ../../../scripts/package.mk
+
+update:
+	rm -rf charts
+	helm repo add stakater https://stakater.github.io/stakater-charts
+	helm repo update
+	helm pull stakater/reloader --untar --untardir charts
diff --git a/packages/system/reloader/charts/reloader/.helmignore b/packages/system/reloader/charts/reloader/.helmignore
new file mode 100644
index 00000000..9e169088
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/.helmignore
@@ -0,0 +1,2 @@
+# OWNERS file for Kubernetes
+OWNERS
diff --git a/packages/system/reloader/charts/reloader/Chart.yaml b/packages/system/reloader/charts/reloader/Chart.yaml
new file mode 100644
index 00000000..84ea57f6
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/Chart.yaml
@@ -0,0 +1,19 @@
+apiVersion: v1
+appVersion: v1.3.0
+description: Reloader chart that runs on kubernetes
+home: https://github.com/stakater/Reloader
+icon: https://raw.githubusercontent.com/stakater/Reloader/master/assets/web/reloader-round-100px.png
+keywords:
+- Reloader
+- kubernetes
+maintainers:
+- email: hello@stakater.com
+  name: Stakater
+- email: rasheed@stakater.com
+  name: rasheedamir
+- email: faizan@stakater.com
+  name: faizanahmad055
+name: reloader
+sources:
+- https://github.com/stakater/Reloader
+version: 2.0.0
diff --git a/packages/system/reloader/charts/reloader/templates/NOTES.txt b/packages/system/reloader/charts/reloader/templates/NOTES.txt
new file mode 100644
index 00000000..d437a4f2
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/NOTES.txt
@@ -0,0 +1,7 @@
+- For a `Deployment` called `foo` have a `ConfigMap` called `foo-configmap`. Then add this annotation to main metadata of your `Deployment`
+  {{ .Values.reloader.custom_annotations.configmap | default "configmap.reloader.stakater.com/reload" }}: "foo-configmap"
+
+- For a `Deployment` called `foo` have a `Secret` called `foo-secret`. Then add this annotation to main metadata of your `Deployment`
+  {{ .Values.reloader.custom_annotations.secret | default "secret.reloader.stakater.com/reload" }}: "foo-secret"
+
+- After successful installation, your pods will get rolling updates when a change in data of configmap or secret will happen.
diff --git a/packages/system/reloader/charts/reloader/templates/_helpers.tpl b/packages/system/reloader/charts/reloader/templates/_helpers.tpl
new file mode 100644
index 00000000..04b3ee4d
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/_helpers.tpl
@@ -0,0 +1,65 @@
+{{/* vim: set filetype=mustache: */}}
+{{/*
+Expand the name of the chart.
+*/}}
+
+{{- define "reloader-name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" | lower -}}
+{{- end -}}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+*/}}
+{{- define "reloader-fullname" -}}
+{{- if .Values.fullnameOverride -}}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
+{{- else -}}
+{{- $name := default .Chart.Name .Values.nameOverride -}}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+{{- end -}}
+
+{{- define "reloader-labels.chart" -}}
+app: {{ template "reloader-fullname" . }}
+chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
+release: {{ .Release.Name | quote }}
+heritage: {{ .Release.Service | quote }}
+app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
+{{- end -}}
+
+{{/*
+Create pod anti affinity labels
+*/}}
+{{- define "reloader-podAntiAffinity" -}}
+podAntiAffinity:
+  preferredDuringSchedulingIgnoredDuringExecution:
+  - weight: 100
+    podAffinityTerm:
+      labelSelector:
+        matchExpressions:
+        - key: app
+          operator: In
+          values:
+          - {{ template "reloader-fullname" . }}
+      topologyKey: "kubernetes.io/hostname"
+{{- end -}}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "reloader-serviceAccountName" -}}
+{{- if .Values.reloader.serviceAccount.create -}}
+    {{ default (include "reloader-fullname" .) .Values.reloader.serviceAccount.name }}
+{{- else -}}
+    {{ default "default" .Values.reloader.serviceAccount.name }}
+{{- end -}}
+{{- end -}}
+
+{{/*
+Create the annotations to support helm3
+*/}}
+{{- define "reloader-helm3.annotations" -}}
+meta.helm.sh/release-namespace: {{ .Release.Namespace | quote }}
+meta.helm.sh/release-name: {{ .Release.Name | quote }}
+{{- end -}}
diff --git a/packages/system/reloader/charts/reloader/templates/clusterrole.yaml b/packages/system/reloader/charts/reloader/templates/clusterrole.yaml
new file mode 100644
index 00000000..5b2ad549
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/clusterrole.yaml
@@ -0,0 +1,112 @@
+{{- if and .Values.reloader.watchGlobally (.Values.reloader.rbac.enabled) }}
+{{- if  (.Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1") }}
+apiVersion: rbac.authorization.k8s.io/v1
+{{ else }}
+apiVersion: rbac.authorization.k8s.io/v1beta1
+{{- end }}
+kind: ClusterRole
+metadata:
+  annotations:
+{{ include "reloader-helm3.annotations" . | indent 4 }}
+  labels:
+{{ include "reloader-labels.chart" . | indent 4 }}
+{{- if .Values.reloader.rbac.labels }}
+{{ toYaml .Values.reloader.rbac.labels | indent 4 }}
+{{- end }}
+{{- if .Values.reloader.matchLabels }}
+{{ toYaml .Values.reloader.matchLabels | indent 4 }}
+{{- end }}
+  name: {{ template "reloader-fullname" . }}-role
+rules:
+  - apiGroups:
+      - ""
+    resources:
+{{- if .Values.reloader.ignoreSecrets }}{{- else }}
+      - secrets
+{{- end }}
+{{- if .Values.reloader.ignoreConfigMaps }}{{- else }}
+      - configmaps
+{{- end }}
+    verbs:
+      - list
+      - get
+      - watch
+{{- if .Values.reloader.namespaceSelector }}
+  - apiGroups:
+      - ""
+    resources:
+      - namespaces
+    verbs:
+      - get
+      - list
+      - watch
+{{- end }}
+{{- if and (.Capabilities.APIVersions.Has "apps.openshift.io/v1") (.Values.reloader.isOpenshift) }}
+  - apiGroups:
+      - "apps.openshift.io"
+      - ""
+    resources:
+      - deploymentconfigs
+    verbs:
+      - list
+      - get
+      - update
+      - patch
+{{- end }}
+{{- if and (.Capabilities.APIVersions.Has "argoproj.io/v1alpha1") (.Values.reloader.isArgoRollouts) }}
+  - apiGroups:
+      - "argoproj.io"
+      - ""
+    resources:
+      - rollouts
+    verbs:
+      - list
+      - get
+      - update
+      - patch
+{{- end }}
+  - apiGroups:
+      - "apps"
+    resources:
+      - deployments
+      - daemonsets
+      - statefulsets
+    verbs:
+      - list
+      - get
+      - update
+      - patch
+  - apiGroups:
+      - "batch"
+    resources:
+      - cronjobs
+    verbs:
+      - list
+      - get
+  - apiGroups:
+      - "batch"
+    resources:
+      - jobs
+    verbs:
+      - create
+      - delete
+      - list
+      - get
+{{- if .Values.reloader.enableHA }}
+  - apiGroups:
+      - "coordination.k8s.io"
+    resources:
+      - leases
+    verbs:
+      - create
+      - get
+      - update
+{{- end}}
+  - apiGroups:
+      - ""
+    resources:
+      - events
+    verbs:
+      - create
+      - patch
+{{- end }}
diff --git a/packages/system/reloader/charts/reloader/templates/clusterrolebinding.yaml b/packages/system/reloader/charts/reloader/templates/clusterrolebinding.yaml
new file mode 100644
index 00000000..0730dba9
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/clusterrolebinding.yaml
@@ -0,0 +1,28 @@
+{{- if and .Values.reloader.watchGlobally (.Values.reloader.rbac.enabled) }}
+{{- if  (.Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1") }}
+apiVersion: rbac.authorization.k8s.io/v1
+{{ else }}
+apiVersion: rbac.authorization.k8s.io/v1beta1
+{{- end }}
+kind: ClusterRoleBinding
+metadata:
+  annotations:
+{{ include "reloader-helm3.annotations" . | indent 4 }}
+  labels:
+{{ include "reloader-labels.chart" . | indent 4 }}
+{{- if .Values.reloader.rbac.labels }}
+{{ toYaml .Values.reloader.rbac.labels | indent 4 }}
+{{- end }}
+{{- if .Values.reloader.matchLabels }}
+{{ toYaml .Values.reloader.matchLabels | indent 4 }}
+{{- end }}
+  name: {{ template "reloader-fullname" . }}-role-binding
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: {{ template "reloader-fullname" . }}-role
+subjects:
+  - kind: ServiceAccount
+    name: {{ template "reloader-serviceAccountName" . }}
+    namespace: {{ .Values.namespace | default .Release.Namespace }}
+{{- end }}
diff --git a/packages/system/reloader/charts/reloader/templates/deployment.yaml b/packages/system/reloader/charts/reloader/templates/deployment.yaml
new file mode 100644
index 00000000..851230e8
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/deployment.yaml
@@ -0,0 +1,300 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  annotations:
+{{ include "reloader-helm3.annotations" . | indent 4 }}
+{{- if .Values.reloader.deployment.annotations }}
+{{ toYaml .Values.reloader.deployment.annotations | indent 4 }}
+{{- end }}
+  labels:
+{{ include "reloader-labels.chart" . | indent 4 }}
+{{- if .Values.reloader.deployment.labels }}
+{{ toYaml .Values.reloader.deployment.labels | indent 4 }}
+{{- end }}
+{{- if .Values.reloader.matchLabels }}
+{{ toYaml .Values.reloader.matchLabels | indent 4 }}
+{{- end }}
+  name: {{ template "reloader-fullname" . }}
+  namespace: {{ .Values.namespace | default .Release.Namespace }}
+spec:
+{{- if not (.Values.reloader.enableHA) }}
+  replicas: {{ min .Values.reloader.deployment.replicas 1 }}
+{{- else }}
+  replicas: {{ .Values.reloader.deployment.replicas }}
+{{- end}}
+  revisionHistoryLimit: {{ .Values.reloader.deployment.revisionHistoryLimit }}
+  selector:
+    matchLabels:
+      app: {{ template "reloader-fullname" . }}
+      release: {{ .Release.Name | quote }}
+{{- if .Values.reloader.matchLabels }}
+{{ toYaml .Values.reloader.matchLabels | indent 6 }}
+{{- end }}
+  template:
+    metadata:
+{{- if .Values.reloader.deployment.pod.annotations }}
+      annotations:
+{{ toYaml .Values.reloader.deployment.pod.annotations | indent 8 }}
+{{- end }}
+      labels:
+{{ include "reloader-labels.chart" . | indent 8 }}
+{{- if .Values.reloader.deployment.labels }}
+{{ toYaml .Values.reloader.deployment.labels | indent 8 }}
+{{- end }}
+{{- if .Values.reloader.matchLabels }}
+{{ toYaml .Values.reloader.matchLabels | indent 8 }}
+{{- end }}
+    spec:
+      {{- with .Values.global.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- if .Values.reloader.deployment.nodeSelector }}
+      nodeSelector:
+{{ toYaml .Values.reloader.deployment.nodeSelector | indent 8 }}
+      {{- end }}
+      {{- if or (.Values.reloader.deployment.affinity) (.Values.reloader.enableHA) }}
+      affinity:
+      {{- if .Values.reloader.deployment.affinity }}
+{{ toYaml .Values.reloader.deployment.affinity | indent 8 }}
+      {{- else }}
+{{ include "reloader-podAntiAffinity" . | indent 8 }}
+      {{- end }}
+      {{- end }}
+      {{- if .Values.reloader.deployment.tolerations }}
+      tolerations:
+{{ toYaml .Values.reloader.deployment.tolerations | indent 8 }}
+      {{- end }}
+      {{- if .Values.reloader.deployment.topologySpreadConstraints }}
+      topologySpreadConstraints:
+{{ toYaml .Values.reloader.deployment.topologySpreadConstraints | indent 8 }}
+      {{- end }}
+      {{- if .Values.reloader.deployment.priorityClassName }}
+      priorityClassName: {{ .Values.reloader.deployment.priorityClassName }}
+      {{- end }}
+      containers:
+      {{- if .Values.global.imageRegistry }}
+      - image: "{{ .Values.global.imageRegistry }}/{{ .Values.image.name }}:{{ .Values.image.tag }}"
+      {{- else }}
+      {{- if .Values.image.digest }}
+      - image: "{{ .Values.image.repository }}@{{ .Values.image.digest }}"
+      {{- else }}
+      - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
+      {{- end }}
+      {{- end }}
+        imagePullPolicy: {{ .Values.image.pullPolicy }}
+        name: {{ template "reloader-fullname" . }}
+        env:
+        - name: GOMAXPROCS
+        {{- if .Values.reloader.deployment.gomaxprocsOverride }}
+          value: {{ .Values.reloader.deployment.gomaxprocsOverride | quote }}
+        {{- else }}
+          valueFrom:
+            resourceFieldRef:
+              resource: limits.cpu
+              divisor: '1'
+        {{- end }}
+        - name: GOMEMLIMIT
+        {{- if .Values.reloader.deployment.gomemlimitOverride }}
+          value: {{ .Values.reloader.deployment.gomemlimitOverride | quote }}
+        {{- else }}
+          valueFrom:
+            resourceFieldRef:
+              resource: limits.memory
+              divisor: '1'
+        {{- end }}
+      {{- range $name, $value := .Values.reloader.deployment.env.open }}
+      {{- if not (empty $value) }}
+        - name: {{ $name | quote }}
+          value: {{ $value | quote }}
+      {{- end }}
+      {{- end }}
+      {{- $secret_name := include "reloader-fullname" . }}
+      {{- range $name, $value := .Values.reloader.deployment.env.secret }}
+      {{- if not ( empty $value) }}
+        - name: {{ $name | quote }}
+          valueFrom:
+            secretKeyRef:
+              name: {{ $secret_name }}
+              key: {{ $name | quote }}
+      {{- end }}
+      {{- end }}
+      {{- range $secret, $values := .Values.reloader.deployment.env.existing }}
+      {{- range $name, $key := $values }}
+      {{- if not ( empty $name) }}
+        - name: {{ $name | quote }}
+          valueFrom:
+            secretKeyRef:
+              name: {{ $secret | quote }}
+              key: {{ $key | quote }}
+      {{- end }}
+      {{- end }}
+      {{- end }}
+      {{- range $name, $value := .Values.reloader.deployment.env.field }}
+      {{- if not ( empty $value) }}
+        - name: {{ $name | quote }}
+          valueFrom:
+            fieldRef:
+              fieldPath: {{ $value | quote}}
+      {{- end }}
+      {{- end }}
+      {{- if eq .Values.reloader.watchGlobally false }}
+        - name: KUBERNETES_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+      {{- end }}
+      {{- if .Values.reloader.enableHA }}
+        - name: POD_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        - name: POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+      {{- end }}
+      {{- if .Values.reloader.enableMetricsByNamespace }}
+        - name: METRICS_COUNT_BY_NAMESPACE
+          value: enabled
+      {{- end }}
+        ports:
+        - name: http
+          containerPort: 9090
+        livenessProbe:
+          httpGet:
+            path: /live
+            port: http
+          timeoutSeconds: {{ .Values.reloader.deployment.livenessProbe.timeoutSeconds | default "5" }}
+          failureThreshold: {{ .Values.reloader.deployment.livenessProbe.failureThreshold | default "5" }}
+          periodSeconds: {{ .Values.reloader.deployment.livenessProbe.periodSeconds | default "10" }}
+          successThreshold: {{ .Values.reloader.deployment.livenessProbe.successThreshold | default "1" }}
+          initialDelaySeconds: {{ .Values.reloader.deployment.livenessProbe.initialDelaySeconds | default "10" }}
+        readinessProbe:
+          httpGet:
+            path: /metrics
+            port: http
+          timeoutSeconds: {{ .Values.reloader.deployment.readinessProbe.timeoutSeconds | default "5" }}
+          failureThreshold: {{ .Values.reloader.deployment.readinessProbe.failureThreshold | default "5" }}
+          periodSeconds: {{ .Values.reloader.deployment.readinessProbe.periodSeconds | default "10" }}
+          successThreshold: {{ .Values.reloader.deployment.readinessProbe.successThreshold | default "1" }}
+          initialDelaySeconds: {{ .Values.reloader.deployment.readinessProbe.initialDelaySeconds | default "10" }}
+
+        {{- $containerSecurityContext := .Values.reloader.deployment.containerSecurityContext | default dict }}
+        {{- if .Values.reloader.readOnlyRootFileSystem }}
+          {{- $_ := set $containerSecurityContext "readOnlyRootFilesystem" true }}
+        {{- end }}
+
+        securityContext:
+          {{- toYaml $containerSecurityContext | nindent 10 }}
+
+      {{- if (or (.Values.reloader.deployment.volumeMounts) (eq .Values.reloader.readOnlyRootFileSystem true)) }}
+        volumeMounts:
+          {{- if eq .Values.reloader.readOnlyRootFileSystem true }}
+          - mountPath: /tmp/
+            name: tmp-volume
+          {{- end }}
+          {{- with .Values.reloader.deployment.volumeMounts }}
+          {{- . | toYaml | nindent 10 }}
+          {{- end }}
+      {{- end }}
+      {{- if or (.Values.reloader.logFormat) (.Values.reloader.logLevel) (.Values.reloader.ignoreSecrets) (.Values.reloader.ignoreNamespaces) (.Values.reloader.namespaceSelector) (.Values.reloader.resourceLabelSelector) (.Values.reloader.ignoreConfigMaps) (.Values.reloader.custom_annotations) (eq .Values.reloader.isArgoRollouts true) (eq .Values.reloader.reloadOnCreate true) (eq .Values.reloader.reloadOnDelete true) (ne .Values.reloader.reloadStrategy "default") (.Values.reloader.enableHA) (.Values.reloader.autoReloadAll)}}
+        args:
+          {{- if .Values.reloader.logFormat }}
+          - "--log-format={{ .Values.reloader.logFormat }}"
+          {{- end }}
+          {{- if .Values.reloader.logLevel }}
+          - "--log-level={{ .Values.reloader.logLevel }}"
+          {{- end }}
+          {{- if .Values.reloader.ignoreSecrets }}
+          - "--resources-to-ignore=secrets"
+          {{- end }}
+          {{- if .Values.reloader.ignoreConfigMaps }}
+          - "--resources-to-ignore=configMaps"
+          {{- end }}
+          {{- if .Values.reloader.ignoreNamespaces }}
+          - "--namespaces-to-ignore={{ .Values.reloader.ignoreNamespaces }}"
+          {{- end }}
+          {{- if .Values.reloader.namespaceSelector }}
+          - "--namespace-selector={{ .Values.reloader.namespaceSelector }}"
+          {{- end }}
+          {{- if .Values.reloader.resourceLabelSelector }}
+          - "--resource-label-selector={{ .Values.reloader.resourceLabelSelector }}"
+          {{- end }}
+          {{- if .Values.reloader.custom_annotations }}
+            {{- if .Values.reloader.custom_annotations.configmap }}
+          - "--configmap-annotation"
+          - "{{ .Values.reloader.custom_annotations.configmap }}"
+            {{- end }}
+            {{- if .Values.reloader.custom_annotations.secret }}
+          - "--secret-annotation"
+          - "{{ .Values.reloader.custom_annotations.secret }}"
+            {{- end }}
+            {{- if .Values.reloader.custom_annotations.auto }}
+          - "--auto-annotation"
+          - "{{ .Values.reloader.custom_annotations.auto }}"
+            {{- end }}
+            {{- if .Values.reloader.custom_annotations.secret_auto }}
+          - "--secret-auto-annotation"
+          - "{{ .Values.reloader.custom_annotations.secret_auto }}"
+            {{- end }}
+            {{- if .Values.reloader.custom_annotations.configmap_auto }}
+          - "--configmap-auto-annotation"
+          - "{{ .Values.reloader.custom_annotations.configmap_auto }}"
+            {{- end }}
+            {{- if .Values.reloader.custom_annotations.search }}
+          - "--auto-search-annotation"
+          - "{{ .Values.reloader.custom_annotations.search }}"
+            {{- end }}
+            {{- if .Values.reloader.custom_annotations.match }}
+          - "--search-match-annotation"
+          - "{{ .Values.reloader.custom_annotations.match }}"
+            {{- end }}
+            {{- if .Values.reloader.webhookUrl }}
+          - "--webhook-url"
+          - "{{ .Values.reloader.webhookUrl }}"
+            {{- end }}
+          {{- end }}
+          {{- if eq .Values.reloader.isArgoRollouts true }}
+          - "--is-Argo-Rollouts={{ .Values.reloader.isArgoRollouts }}"
+          {{- end }}
+          {{- if eq .Values.reloader.reloadOnCreate true }}
+          - "--reload-on-create={{ .Values.reloader.reloadOnCreate }}"
+          {{- end }}
+          {{- if eq .Values.reloader.reloadOnDelete true }}
+          - "--reload-on-delete={{ .Values.reloader.reloadOnDelete }}"
+          {{- end }}
+          {{- if eq .Values.reloader.syncAfterRestart true }}
+          - "--sync-after-restart={{ .Values.reloader.syncAfterRestart }}"
+          {{- end }}
+          {{- if ne .Values.reloader.reloadStrategy "default" }}
+          - "--reload-strategy={{ .Values.reloader.reloadStrategy }}"
+          {{- end }}
+          {{- if or (gt (int .Values.reloader.deployment.replicas) 1) (.Values.reloader.enableHA) }}
+          - "--enable-ha=true"
+          {{- end}}
+          {{- if eq .Values.reloader.autoReloadAll true }}
+          - "--auto-reload-all=true"
+          {{- end -}}
+      {{- end }}
+      {{- if .Values.reloader.deployment.resources }}
+        resources:
+{{ toYaml .Values.reloader.deployment.resources | indent 10 }}
+      {{- end }}
+{{- if .Values.reloader.deployment.securityContext }}
+      securityContext: {{ toYaml .Values.reloader.deployment.securityContext | nindent 8 }}
+{{- end }}
+      serviceAccountName: {{ template "reloader-serviceAccountName" . }}
+{{- if hasKey .Values.reloader.deployment "automountServiceAccountToken" }}
+      automountServiceAccountToken: {{ .Values.reloader.deployment.automountServiceAccountToken }}
+{{- end }}
+    {{- if (or (.Values.reloader.deployment.volumes) (eq .Values.reloader.readOnlyRootFileSystem true)) }}
+      volumes:
+        {{- if eq .Values.reloader.readOnlyRootFileSystem true }}
+        - emptyDir: {}
+          name: tmp-volume
+        {{- end }}
+        {{- with .Values.reloader.deployment.volumes }}
+          {{- . | toYaml | nindent 8 }}
+        {{- end }}
+    {{- end }}
diff --git a/packages/system/reloader/charts/reloader/templates/networkpolicy.yaml b/packages/system/reloader/charts/reloader/templates/networkpolicy.yaml
new file mode 100644
index 00000000..ca5f2248
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/networkpolicy.yaml
@@ -0,0 +1,39 @@
+{{- if and ( .Values.reloader.netpol.enabled ) }}
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  annotations:
+{{ include "reloader-helm3.annotations" . | indent 4 }}
+  labels:
+{{ include "reloader-labels.chart" . | indent 4 }}
+{{- if .Values.reloader.matchLabels }}
+{{ toYaml .Values.reloader.matchLabels | indent 4 }}
+{{- end }}
+  name: {{ template "reloader-fullname" . }}
+  namespace: {{ .Values.namespace | default .Release.Namespace }}
+spec:
+  podSelector:
+    matchLabels:
+      app: {{ template "reloader-fullname" . }}
+      release: {{ .Release.Name | quote }}
+{{- if .Values.reloader.matchLabels }}
+{{ toYaml .Values.reloader.matchLabels | indent 6 }}
+{{- end }}
+  policyTypes:
+  - Ingress
+  - Egress
+  ingress:
+    - ports:
+        - port: http
+      {{- with .Values.reloader.netpol.from}}
+      from:
+        {{- toYaml .| nindent 8 }}
+      {{- end }}
+  egress:
+    - ports:
+        - port: 443
+      {{- with .Values.reloader.netpol.to}}
+      to:
+        {{- toYaml .| nindent 8 }}
+      {{- end }}
+{{- end }}
diff --git a/packages/system/reloader/charts/reloader/templates/poddisruptionbudget.yaml b/packages/system/reloader/charts/reloader/templates/poddisruptionbudget.yaml
new file mode 100644
index 00000000..e13c4783
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/poddisruptionbudget.yaml
@@ -0,0 +1,17 @@
+{{- if .Values.reloader.podDisruptionBudget.enabled }}
+apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+  name: {{ template "reloader-fullname" . }}
+  namespace: {{ .Values.namespace | default .Release.Namespace }}
+spec:
+{{- if .Values.reloader.podDisruptionBudget.maxUnavailable }}
+  maxUnavailable: {{ .Values.reloader.podDisruptionBudget.maxUnavailable }}
+{{- end }}
+{{- if and .Values.reloader.podDisruptionBudget.minAvailable (not .Values.reloader.podDisruptionBudget.maxUnavailable)}}
+  minAvailable: {{ .Values.reloader.podDisruptionBudget.minAvailable }}
+{{- end }}
+  selector:
+    matchLabels:
+      app: {{ template "reloader-fullname" . }}
+{{- end }}
diff --git a/packages/system/reloader/charts/reloader/templates/podmonitor.yaml b/packages/system/reloader/charts/reloader/templates/podmonitor.yaml
new file mode 100644
index 00000000..7afeba3b
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/podmonitor.yaml
@@ -0,0 +1,60 @@
+{{- if ( .Values.reloader.podMonitor.enabled ) }}
+apiVersion: monitoring.coreos.com/v1
+kind: PodMonitor
+metadata:
+{{- if .Values.reloader.podMonitor.annotations }}
+  annotations:
+{{ tpl (toYaml .Values.reloader.podMonitor.annotations) . | indent 4 }}
+{{- end }}
+  labels:
+{{ include "reloader-labels.chart" . | indent 4 }}
+{{- if .Values.reloader.podMonitor.labels }}
+{{ tpl (toYaml .Values.reloader.podMonitor.labels) . | indent 4 }}
+{{- end }}
+  name: {{ template "reloader-fullname" . }}
+{{- if .Values.reloader.podMonitor.namespace }}
+  namespace: {{ tpl .Values.reloader.podMonitor.namespace . }}
+{{- else }}
+  namespace: {{ .Values.namespace | default .Release.Namespace }}
+{{- end }}
+spec:
+  podMetricsEndpoints:
+  - port: http
+    path: "/metrics"
+    {{- with .Values.reloader.podMonitor.interval }}
+    interval: {{ . }}
+    {{- end }}
+    {{- with .Values.reloader.podMonitor.scheme }}
+    scheme: {{ . }}
+    {{- end }}
+    {{- with .Values.reloader.podMonitor.bearerTokenSecret }}
+    bearerTokenSecret: {{ . }}
+    {{- end }}
+    {{- with .Values.reloader.podMonitor.tlsConfig }}
+    tlsConfig:
+      {{- toYaml .| nindent 6 }}
+    {{- end }}
+    {{- with .Values.reloader.podMonitor.timeout }}
+    scrapeTimeout: {{ . }}
+    {{- end }}
+    honorLabels: {{ .Values.reloader.podMonitor.honorLabels }}
+    {{- with .Values.reloader.podMonitor.metricRelabelings }}
+    metricRelabelings:
+      {{- tpl (toYaml . | nindent 6) $ }}
+    {{- end }}
+    {{- with .Values.reloader.podMonitor.relabelings }}
+    relabelings:
+      {{- toYaml . | nindent 6 }}
+    {{- end }}
+  {{- with .Values.reloader.podMonitor.podTargetLabels }}
+  podTargetLabels:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+  jobLabel: {{ template "reloader-fullname" . }}
+  namespaceSelector:
+    matchNames:
+    - {{ .Release.Namespace }}
+  selector:
+    matchLabels:
+      {{ include "reloader-labels.chart" . | nindent 6 }}
+{{- end }}
diff --git a/packages/system/reloader/charts/reloader/templates/role.yaml b/packages/system/reloader/charts/reloader/templates/role.yaml
new file mode 100644
index 00000000..13ac4bb2
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/role.yaml
@@ -0,0 +1,103 @@
+{{- if and (not (.Values.reloader.watchGlobally)) (.Values.reloader.rbac.enabled) }}
+{{- if  (.Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1") }}
+apiVersion: rbac.authorization.k8s.io/v1
+{{ else }}
+apiVersion: rbac.authorization.k8s.io/v1beta1
+{{- end }}
+kind: Role
+metadata:
+  annotations:
+{{ include "reloader-helm3.annotations" . | indent 4 }}
+  labels:
+{{ include "reloader-labels.chart" . | indent 4 }}
+{{- if .Values.reloader.rbac.labels }}
+{{ toYaml .Values.reloader.rbac.labels | indent 4 }}
+{{- end }}
+{{- if .Values.reloader.matchLabels }}
+{{ toYaml .Values.reloader.matchLabels | indent 4 }}
+{{- end }}
+  name: {{ template "reloader-fullname" . }}-role
+  namespace: {{ .Values.namespace | default .Release.Namespace }}
+rules:
+  - apiGroups:
+      - ""
+    resources:
+{{- if .Values.reloader.ignoreSecrets }}{{- else }}
+      - secrets
+{{- end }}
+{{- if .Values.reloader.ignoreConfigMaps }}{{- else }}
+      - configmaps
+{{- end }}
+    verbs:
+      - list
+      - get
+      - watch
+{{- if and (.Capabilities.APIVersions.Has "apps.openshift.io/v1") (.Values.reloader.isOpenshift) }}
+  - apiGroups:
+      - "apps.openshift.io"
+      - ""
+    resources:
+      - deploymentconfigs
+    verbs:
+      - list
+      - get
+      - update
+      - patch
+{{- end }}
+{{- if and (.Capabilities.APIVersions.Has "argoproj.io/v1alpha1") (.Values.reloader.isArgoRollouts) }}
+  - apiGroups:
+      - "argoproj.io"
+      - ""
+    resources:
+      - rollouts
+    verbs:
+      - list
+      - get
+      - update
+      - patch
+{{- end }}
+  - apiGroups:
+      - "apps"
+    resources:
+      - deployments
+      - daemonsets
+      - statefulsets
+    verbs:
+      - list
+      - get
+      - update
+      - patch
+  - apiGroups:
+      - "batch"
+    resources:
+      - cronjobs
+    verbs:
+      - list
+      - get
+  - apiGroups:
+      - "batch"
+    resources:
+      - jobs
+    verbs:
+      - create
+      - delete
+      - list
+      - get
+{{- if .Values.reloader.enableHA }}
+  - apiGroups:
+      - "coordination.k8s.io"
+    resources:
+      - leases
+    verbs:
+      - create
+      - get
+      - update
+{{- end}}
+  - apiGroups:
+      - ""
+    resources:
+      - events
+    verbs:
+      - create
+      - patch
+{{- end }}
diff --git a/packages/system/reloader/charts/reloader/templates/rolebinding.yaml b/packages/system/reloader/charts/reloader/templates/rolebinding.yaml
new file mode 100644
index 00000000..abeb721d
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/rolebinding.yaml
@@ -0,0 +1,29 @@
+{{- if and (not (.Values.reloader.watchGlobally)) (.Values.reloader.rbac.enabled) }}
+{{- if  (.Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1") }}
+apiVersion: rbac.authorization.k8s.io/v1
+{{ else }}
+apiVersion: rbac.authorization.k8s.io/v1beta1
+{{- end }}
+kind: RoleBinding
+metadata:
+  annotations:
+{{ include "reloader-helm3.annotations" . | indent 4 }}
+  labels:
+{{ include "reloader-labels.chart" . | indent 4 }}
+{{- if .Values.reloader.rbac.labels }}
+{{ toYaml .Values.reloader.rbac.labels | indent 4 }}
+{{- end }}
+{{- if .Values.reloader.matchLabels }}
+{{ toYaml .Values.reloader.matchLabels | indent 4 }}
+{{- end }}
+  name: {{ template "reloader-fullname" . }}-role-binding
+  namespace: {{ .Values.namespace | default .Release.Namespace }}
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: {{ template "reloader-fullname" . }}-role
+subjects:
+  - kind: ServiceAccount
+    name: {{ template "reloader-serviceAccountName" . }}
+    namespace: {{ .Values.namespace | default .Release.Namespace }}
+{{- end }}
diff --git a/packages/system/reloader/charts/reloader/templates/secret.yaml b/packages/system/reloader/charts/reloader/templates/secret.yaml
new file mode 100644
index 00000000..21723092
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/secret.yaml
@@ -0,0 +1,21 @@
+{{- if .Values.reloader.deployment.env.secret -}}
+apiVersion: v1
+kind: Secret
+metadata:
+  name: {{ template "reloader-fullname" . }}
+  namespace: {{ .Values.namespace | default .Release.Namespace }}
+type: Opaque
+data:
+  {{ if .Values.reloader.deployment.env.secret.ALERT_ON_RELOAD -}}
+  ALERT_ON_RELOAD: {{ .Values.reloader.deployment.env.secret.ALERT_ON_RELOAD | b64enc | quote }}
+  {{ end }}
+  {{- if .Values.reloader.deployment.env.secret.ALERT_SINK -}}
+  ALERT_SINK: {{ .Values.reloader.deployment.env.secret.ALERT_SINK | b64enc | quote }}
+  {{ end }}
+  {{- if .Values.reloader.deployment.env.secret.ALERT_WEBHOOK_URL -}}
+  ALERT_WEBHOOK_URL: {{ .Values.reloader.deployment.env.secret.ALERT_WEBHOOK_URL | b64enc | quote }}
+  {{ end }}
+  {{- if .Values.reloader.deployment.env.secret.ALERT_ADDITIONAL_INFO -}}
+  ALERT_ADDITIONAL_INFO: {{ .Values.reloader.deployment.env.secret.ALERT_ADDITIONAL_INFO | b64enc | quote }}
+  {{ end }}
+{{ end }}
diff --git a/packages/system/reloader/charts/reloader/templates/service.yaml b/packages/system/reloader/charts/reloader/templates/service.yaml
new file mode 100644
index 00000000..95a8150d
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/service.yaml
@@ -0,0 +1,30 @@
+{{- if .Values.reloader.service }}
+apiVersion: v1
+kind: Service
+metadata:
+  annotations:
+{{ include "reloader-helm3.annotations" . | indent 4 }}
+{{- if .Values.reloader.service.annotations }}
+{{ toYaml .Values.reloader.service.annotations | indent 4 }}
+{{- end }}
+  labels:
+{{ include "reloader-labels.chart" . | indent 4 }}
+{{- if .Values.reloader.service.labels }}
+{{ toYaml .Values.reloader.service.labels | indent 4 }}
+{{- end }}
+  name: {{ template "reloader-fullname" . }}
+  namespace: {{ .Values.namespace | default .Release.Namespace }}
+spec:
+  selector:
+{{- if .Values.reloader.deployment.labels }}
+{{ toYaml .Values.reloader.deployment.labels | indent 4 }}
+{{- end }}
+{{- if .Values.reloader.matchLabels }}
+{{ toYaml .Values.reloader.matchLabels | indent 4 }}
+{{- end }}
+  ports:
+  - port: {{ .Values.reloader.service.port }}
+    name: http
+    protocol: TCP
+    targetPort: http
+{{- end }}
diff --git a/packages/system/reloader/charts/reloader/templates/serviceaccount.yaml b/packages/system/reloader/charts/reloader/templates/serviceaccount.yaml
new file mode 100644
index 00000000..27909edc
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/serviceaccount.yaml
@@ -0,0 +1,26 @@
+{{- if .Values.reloader.serviceAccount.create }}
+apiVersion: v1
+kind: ServiceAccount
+{{- if .Values.global.imagePullSecrets }}
+imagePullSecrets: {{ toYaml .Values.global.imagePullSecrets | nindent 2 }}
+{{- end }}
+{{- if hasKey .Values.reloader.serviceAccount "automountServiceAccountToken" }}
+automountServiceAccountToken: {{ .Values.reloader.serviceAccount.automountServiceAccountToken }}
+{{- end }}
+metadata:
+  annotations:
+{{ include "reloader-helm3.annotations" . | indent 4 }}
+{{- if .Values.reloader.serviceAccount.annotations }}
+{{ toYaml .Values.reloader.serviceAccount.annotations | indent 4 }}
+{{- end }}
+  labels:
+{{ include "reloader-labels.chart" . | indent 4 }}
+{{- if .Values.reloader.serviceAccount.labels }}
+{{ toYaml .Values.reloader.serviceAccount.labels | indent 4 }}
+{{- end }}
+{{- if .Values.reloader.matchLabels }}
+{{ toYaml .Values.reloader.matchLabels | indent 4 }}
+{{- end }}
+  name: {{ template "reloader-serviceAccountName" . }}
+  namespace: {{ .Values.namespace | default .Release.Namespace }}
+{{- end }}
diff --git a/packages/system/reloader/charts/reloader/templates/servicemonitor.yaml b/packages/system/reloader/charts/reloader/templates/servicemonitor.yaml
new file mode 100644
index 00000000..c4685fdb
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/servicemonitor.yaml
@@ -0,0 +1,60 @@
+{{- if and ( .Capabilities.APIVersions.Has "monitoring.coreos.com/v1" ) ( .Values.reloader.serviceMonitor.enabled ) }}
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
+metadata:
+{{- if .Values.reloader.serviceMonitor.annotations }}
+  annotations:
+{{ tpl (toYaml .Values.reloader.serviceMonitor.annotations) . | indent 4 }}
+{{- end }}
+  labels:
+{{ include "reloader-labels.chart" . | indent 4 }}
+{{- if .Values.reloader.serviceMonitor.labels }}
+{{ tpl (toYaml .Values.reloader.serviceMonitor.labels) . | indent 4 }}
+{{- end }}
+  name: {{ template "reloader-fullname" . }}
+{{- if .Values.reloader.serviceMonitor.namespace }}
+  namespace: {{ tpl .Values.reloader.serviceMonitor.namespace . }}
+{{- else }}
+  namespace: {{ .Values.namespace | default .Release.Namespace }}
+{{- end }}
+spec:
+  endpoints:
+  - targetPort: http
+    path: "/metrics"
+    {{- with .Values.reloader.serviceMonitor.interval }}
+    interval: {{ . }}
+    {{- end }}
+    {{- with .Values.reloader.serviceMonitor.scheme }}
+    scheme: {{ . }}
+    {{- end }}
+    {{- with .Values.reloader.serviceMonitor.bearerTokenFile }}
+    bearerTokenFile: {{ . }}
+    {{- end }}
+    {{- with .Values.reloader.serviceMonitor.tlsConfig }}
+    tlsConfig:
+      {{- toYaml .| nindent 6 }}
+    {{- end }}
+    {{- with .Values.reloader.serviceMonitor.timeout }}
+    scrapeTimeout: {{ . }}
+    {{- end }}
+    honorLabels: {{ .Values.reloader.serviceMonitor.honorLabels }}
+    {{- with .Values.reloader.serviceMonitor.metricRelabelings }}
+    metricRelabelings:
+      {{- tpl (toYaml . | nindent 6) $ }}
+    {{- end }}
+    {{- with .Values.reloader.serviceMonitor.relabelings }}
+    relabelings:
+      {{- toYaml . | nindent 6 }}
+    {{- end }}
+  {{- with .Values.reloader.serviceMonitor.targetLabels }}
+  targetLabels:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+  jobLabel: {{ template "reloader-fullname" . }}
+  namespaceSelector:
+    matchNames:
+    - {{ .Release.Namespace }}
+  selector:
+    matchLabels:
+      {{ include "reloader-labels.chart" . | nindent 6 }}
+{{- end }}
diff --git a/packages/system/reloader/charts/reloader/templates/verticalpodautoscaler.yaml b/packages/system/reloader/charts/reloader/templates/verticalpodautoscaler.yaml
new file mode 100644
index 00000000..9ec2c463
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/templates/verticalpodautoscaler.yaml
@@ -0,0 +1,40 @@
+{{- if and (.Capabilities.APIVersions.Has "autoscaling.k8s.io/v1") (.Values.reloader.verticalPodAutoscaler.enabled) }}
+apiVersion: autoscaling.k8s.io/v1
+kind: VerticalPodAutoscaler
+metadata:
+  name: {{ template "reloader-fullname" . }}
+  namespace: {{ .Values.namespace | default .Release.Namespace }}
+  labels:
+    {{- include "reloader-labels.chart" . | nindent 4 }}
+spec:
+  {{- with .Values.reloader.verticalPodAutoscaler.recommenders }}
+  recommenders:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+  resourcePolicy:
+    containerPolicies:
+    - containerName: {{ template "reloader-fullname" . }}
+      {{- with .Values.reloader.verticalPodAutoscaler.controlledResources }}
+      controlledResources:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- if .Values.reloader.verticalPodAutoscaler.controlledValues }}
+      controlledValues: {{ .Values.reloader.verticalPodAutoscaler.controlledValues }}
+      {{- end }}
+      {{- if .Values.reloader.verticalPodAutoscaler.maxAllowed }}
+      maxAllowed:
+        {{ toYaml .Values.reloader.verticalPodAutoscaler.maxAllowed | nindent 8 }}
+      {{- end }}
+      {{- if .Values.reloader.verticalPodAutoscaler.minAllowed }}
+      minAllowed:
+        {{ toYaml .Values.reloader.verticalPodAutoscaler.minAllowed | nindent 8 }}
+      {{- end }}
+  targetRef:
+    apiVersion: apps/v1
+    kind: Deployment
+    name: {{ template "reloader-fullname" . }}
+  {{- with .Values.reloader.verticalPodAutoscaler.updatePolicy }}
+  updatePolicy:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+{{- end }}
diff --git a/packages/system/reloader/charts/reloader/values.schema.json b/packages/system/reloader/charts/reloader/values.schema.json
new file mode 100644
index 00000000..56eb678a
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/values.schema.json
@@ -0,0 +1,19 @@
+{
+  "$schema": "http://json-schema.org/schema#",
+  "type": "object",
+  "properties": {
+    "reloader": {
+      "type": "object",
+      "properties": {
+        "reloadStrategy": {
+          "type": "string",
+          "enum": [
+            "default",
+            "env-vars",
+            "annotations"
+          ]
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/packages/system/reloader/charts/reloader/values.yaml b/packages/system/reloader/charts/reloader/values.yaml
new file mode 100644
index 00000000..d7f9bf74
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/values.yaml
@@ -0,0 +1,342 @@
+# Generated from deployments/kubernetes/templates/chart/values.yaml.tmpl
+global:
+  ## Reference to one or more secrets to be used when pulling images
+  ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
+  ##
+  imageRegistry: ""
+  imagePullSecrets: []
+  #imagePullSecrets:
+  #  - name: my-pull-secret
+
+kubernetes:
+  host: https://kubernetes.default
+
+nameOverride: ""
+fullnameOverride: ""
+
+image:
+  name: stakater/reloader
+  repository: ghcr.io/stakater/reloader
+  tag: v1.3.0
+  # digest: sha256:1234567
+  pullPolicy: IfNotPresent
+
+reloader:
+  autoReloadAll: false
+  isArgoRollouts: false
+  isOpenshift: false
+  ignoreSecrets: false
+  ignoreConfigMaps: false
+  reloadOnCreate: false
+  reloadOnDelete: false
+  syncAfterRestart: false
+  reloadStrategy: default # Set to default, env-vars or annotations
+  ignoreNamespaces: "" # Comma separated list of namespaces to ignore
+  namespaceSelector: "" # Comma separated list of k8s label selectors for namespaces selection
+  resourceLabelSelector: "" # Comma separated list of k8s label selectors for configmap/secret selection
+  logFormat: "" # json
+  logLevel: info # Log level to use (trace, debug, info, warning, error, fatal and panic)
+  watchGlobally: true
+  # Set to true to enable leadership election allowing you to run multiple replicas
+  enableHA: false
+  # Set to true if you have a pod security policy that enforces readOnlyRootFilesystem
+  readOnlyRootFileSystem: false
+  legacy:
+    rbac: false
+  matchLabels: {}
+  # Set to true to expose a prometheus counter of reloads by namespace (this metric may have high cardinality in clusters with many namespaces)
+  enableMetricsByNamespace: false
+  deployment:
+    # If you wish to run multiple replicas set reloader.enableHA = true
+    replicas: 1
+
+    revisionHistoryLimit: 2
+
+    nodeSelector:
+    # cloud.google.com/gke-nodepool: default-pool
+
+    # An affinity stanza to be applied to the Deployment.
+    # Example:
+    #   affinity:
+    #     nodeAffinity:
+    #       requiredDuringSchedulingIgnoredDuringExecution:
+    #         nodeSelectorTerms:
+    #         - matchExpressions:
+    #           - key: "node-role.kubernetes.io/infra-worker"
+    #             operator: "Exists"
+    affinity: {}
+
+    securityContext:
+      runAsNonRoot: true
+      runAsUser: 65534
+      seccompProfile:
+        type: RuntimeDefault
+
+    containerSecurityContext:
+      {}
+      # capabilities:
+      #   drop:
+      #     - ALL
+      # allowPrivilegeEscalation: false
+      # readOnlyRootFilesystem: true
+
+    # A list of tolerations to be applied to the Deployment.
+    # Example:
+    #   tolerations:
+    #   - key: "node-role.kubernetes.io/infra-worker"
+    #     operator: "Exists"
+    #     effect: "NoSchedule"
+    tolerations: []
+
+    # Topology spread constraints for pod assignment
+    # Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
+    # Example:
+    # topologySpreadConstraints:
+    #   - maxSkew: 1
+    #     topologyKey: zone
+    #     whenUnsatisfiable: DoNotSchedule
+    #     labelSelector:
+    #       matchLabels:
+    #         app: my-app
+    topologySpreadConstraints: []
+
+    annotations: {}
+    labels:
+      provider: stakater
+      group: com.stakater.platform
+      version: v1.3.0
+    # Support for extra environment variables.
+    env:
+      # Open supports Key value pair as environment variables.
+      open:
+      # secret supports Key value pair as environment variables. It gets the values based on keys from default reloader secret if any.
+      secret:
+      #  ALERT_ON_RELOAD: <"true"|"false">
+      #  ALERT_SINK: <"slack"> # By default it will be a raw text based webhook
+      #  ALERT_WEBHOOK_URL: <"webhook_url">
+      #  ALERT_ADDITIONAL_INFO: <"Additional Info like Cluster Name if needed">
+      # field supports Key value pair as environment variables. It gets the values from other fields of pod.
+      field:
+      # existing secret, you can specify multiple existing secrets, for each
+      # specify the env var name followed by the key in existing secret that
+      # will be used to populate the env var
+      existing:
+      #  existing_secret_name:
+      #    ALERT_ON_RELOAD: alert_on_reload_key
+      #    ALERT_SINK: alert_sink_key
+      #    ALERT_WEBHOOK_URL: alert_webhook_key
+      #    ALERT_ADDITIONAL_INFO: alert_additional_info_key
+
+    # Liveness and readiness probe timeout values.
+    livenessProbe: {}
+    #  timeoutSeconds: 5
+    #  failureThreshold: 5
+    #  periodSeconds: 10
+    #  successThreshold: 1
+    readinessProbe: {}
+    #  timeoutSeconds: 15
+    #  failureThreshold: 5
+    #  periodSeconds: 10
+    #  successThreshold: 1
+
+    # Specify resource requests/limits for the deployment.
+    # Example:
+    # resources:
+    #   limits:
+    #     cpu: "100m"
+    #     memory: "512Mi"
+    #   requests:
+    #     cpu: "10m"
+    #     memory: "128Mi"
+    resources: {}
+    pod:
+      annotations: {}
+    priorityClassName: ""
+    # imagePullSecrets:
+    #   - name: myregistrykey
+
+    # Put "0" in either to have go runtime ignore the set value.
+    # Otherwise, see https://pkg.go.dev/runtime#hdr-Environment_Variables for GOMAXPROCS and GOMEMLIMIT
+    gomaxprocsOverride: ""
+    gomemlimitOverride: ""
+
+  service:
+    {}
+
+    # labels: {}
+    # annotations: {}
+    # port: 9090
+
+  rbac:
+    enabled: true
+    labels: {}
+  # Service account config for the agent pods
+  serviceAccount:
+    # Specifies whether a ServiceAccount should be created
+    create: true
+    labels: {}
+    annotations: {}
+    # The name of the ServiceAccount to use.
+    # If not set and create is true, a name is generated using the fullname template
+    name:
+  # Optional flags to pass to the Reloader entrypoint
+  # Example:
+  #   custom_annotations:
+  #     configmap: "my.company.com/configmap"
+  #     secret: "my.company.com/secret"
+  custom_annotations: {}
+
+  serviceMonitor:
+    # Deprecated: Service monitor will be removed in future releases of reloader in favour of Pod monitor
+    # Enabling this requires service to be enabled as well, or no endpoints will be found
+    enabled: false
+    # Set the namespace the ServiceMonitor should be deployed
+    # namespace: monitoring
+
+    # Fallback to the prometheus default unless specified
+    # interval: 10s
+
+    ## scheme: HTTP scheme to use for scraping. Can be used with `tlsConfig` for example if using istio mTLS.
+    # scheme: ""
+
+    ## tlsConfig: TLS configuration to use when scraping the endpoint. For example if using istio mTLS.
+    ## Of type: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#tlsconfig
+    # tlsConfig: {}
+
+    # bearerTokenFile:
+    # Fallback to the prometheus default unless specified
+    # timeout: 30s
+
+    ## Used to pass Labels that are used by the Prometheus installed in your cluster to select Service Monitors to work with
+    ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec
+    labels: {}
+
+    ## Used to pass annotations that are used by the Prometheus installed in your cluster to select Service Monitors to work with
+    ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec
+    annotations: {}
+
+    # Retain the job and instance labels of the metrics pushed to the Pushgateway
+    # [Scraping Pushgateway](https://github.com/prometheus/pushgateway#configure-the-pushgateway-as-a-target-to-scrape)
+    honorLabels: true
+
+    ## Metric relabel configs to apply to samples before ingestion.
+    ## [Metric Relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs)
+    metricRelabelings: []
+    # - action: keep
+    #   regex: 'kube_(daemonset|deployment|pod|namespace|node|statefulset).+'
+    #   sourceLabels: [__name__]
+
+    ## Relabel configs to apply to samples before ingestion.
+    ## [Relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config)
+    relabelings: []
+    # - sourceLabels: [__meta_kubernetes_pod_node_name]
+    #   separator: ;
+    #   regex: ^(.*)$
+    #   targetLabel: nodename
+    #   replacement: $1
+    #   action: replace
+
+    targetLabels: []
+
+  podMonitor:
+    enabled: false
+    # Set the namespace the podMonitor should be deployed
+    # namespace: monitoring
+
+    # Fallback to the prometheus default unless specified
+    # interval: 10s
+
+    ## scheme: HTTP scheme to use for scraping. Can be used with `tlsConfig` for example if using istio mTLS.
+    # scheme: ""
+
+    ## tlsConfig: TLS configuration to use when scraping the endpoint. For example if using istio mTLS.
+    ## Of type: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#tlsconfig
+    # tlsConfig: {}
+
+    # bearerTokenSecret:
+    # Fallback to the prometheus default unless specified
+    # timeout: 30s
+
+    ## Used to pass Labels that are used by the Prometheus installed in your cluster to select Service Monitors to work with
+    ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec
+    labels: {}
+
+    ## Used to pass annotations that are used by the Prometheus installed in your cluster to select Service Monitors to work with
+    ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec
+    annotations: {}
+
+    # Retain the job and instance labels of the metrics pushed to the Pushgateway
+    # [Scraping Pushgateway](https://github.com/prometheus/pushgateway#configure-the-pushgateway-as-a-target-to-scrape)
+    honorLabels: true
+
+    ## Metric relabel configs to apply to samples before ingestion.
+    ## [Metric Relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs)
+    metricRelabelings: []
+    # - action: keep
+    #   regex: 'kube_(daemonset|deployment|pod|namespace|node|statefulset).+'
+    #   sourceLabels: [__name__]
+
+    ## Relabel configs to apply to samples before ingestion.
+    ## [Relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config)
+    relabelings: []
+    # - sourceLabels: [__meta_kubernetes_pod_node_name]
+    #   separator: ;
+    #   regex: ^(.*)$
+    #   targetLabel: nodename
+    #   replacement: $1
+    #   action: replace
+
+    podTargetLabels: []
+
+  podDisruptionBudget:
+    enabled: false
+    # Set the minimum available replicas
+    # minAvailable: 1
+    # OR Set the maximum unavailable replicas
+    # maxUnavailable: 1
+    # If both defined only maxUnavailable will be used
+
+  netpol:
+    enabled: false
+    from: []
+    # - podSelector:
+    #     matchLabels:
+    #       app.kubernetes.io/name: prometheus
+    to: []
+
+  # Enable vertical pod autoscaler
+  verticalPodAutoscaler:
+    enabled: false
+
+    # Recommender responsible for generating recommendation for the object.
+    # List should be empty (then the default recommender will generate the recommendation)
+    # or contain exactly one recommender.
+    # recommenders:
+    # - name: custom-recommender-performance
+
+    # List of resources that the vertical pod autoscaler can control. Defaults to cpu and memory
+    controlledResources: []
+    # Specifies which resource values should be controlled: RequestsOnly or RequestsAndLimits.
+    # controlledValues: RequestsAndLimits
+
+    # Define the max allowed resources for the pod
+    maxAllowed: {}
+    # cpu: 200m
+    # memory: 100Mi
+    # Define the min allowed resources for the pod
+    minAllowed: {}
+    # cpu: 200m
+    # memory: 100Mi
+
+    updatePolicy:
+      # Specifies minimal number of replicas which need to be alive for VPA Updater to attempt pod eviction
+      # minReplicas: 1
+      # Specifies whether recommended updates are applied when a Pod is started and whether recommended updates
+      # are applied during the life of a Pod. Possible values are "Off", "Initial", "Recreate", and "Auto".
+      updateMode: Auto
+
+  volumeMounts: []
+
+  volumes: []
+
+  webhookUrl: ""
diff --git a/packages/system/reloader/values.yaml b/packages/system/reloader/values.yaml
new file mode 100644
index 00000000..cbfae7ca
--- /dev/null
+++ b/packages/system/reloader/values.yaml
@@ -0,0 +1,3 @@
+reloader:
+  reloader:
+    reloadStrategy: annotations



Test patch:
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/tests/admission-webhooks/job-patch/serviceaccount_test.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/tests/admission-webhooks/job-patch/serviceaccount_test.yaml
index f72bc438..7c30d1e6 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/tests/admission-webhooks/job-patch/serviceaccount_test.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/tests/admission-webhooks/job-patch/serviceaccount_test.yaml
@@ -20,7 +20,7 @@ tests:
           of: ServiceAccount
       - equal:
           path: metadata.name
-          value: RELEASE-NAME-ingress-nginx-admission
+          value: ingress-nginx-admission
 
   - it: should create a ServiceAccount with specified name if `controller.admissionWebhooks.patch.serviceAccount.name` is set
     set:
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/tests/admission-webhooks/validating-webhook_test.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/tests/admission-webhooks/validating-webhook_test.yaml
deleted file mode 100644
index 47b6b687..00000000
--- a/packages/system/ingress-nginx/charts/ingress-nginx/tests/admission-webhooks/validating-webhook_test.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-suite: Admission Webhooks > ValidatingWebhookConfiguration
-templates:
-  - admission-webhooks/validating-webhook.yaml
-
-tests:
-  - it: should not create a ValidatingWebhookConfiguration if `controller.admissionWebhooks.enabled` is false
-    set:
-      controller.admissionWebhooks.enabled: false
-    asserts:
-      - hasDocuments:
-          count: 0
-
-  - it: should create a ValidatingWebhookConfiguration if `controller.admissionWebhooks.enabled` is true
-    set:
-      controller.admissionWebhooks.enabled: true
-    asserts:
-      - hasDocuments:
-          count: 1
-      - isKind:
-          of: ValidatingWebhookConfiguration
-      - equal:
-          path: metadata.name
-          value: RELEASE-NAME-ingress-nginx-admission
-
-  - it: should create a ValidatingWebhookConfiguration with a custom port if `controller.admissionWebhooks.service.servicePort` is set
-    set:
-      controller.admissionWebhooks.enabled: true
-      controller.admissionWebhooks.service.servicePort: 9443
-    asserts:
-      - equal:
-          path: webhooks[0].clientConfig.service.port
-          value: 9443
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-configmap_test.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-configmap_test.yaml
index 168b657d..9cfea980 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-configmap_test.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-configmap_test.yaml
@@ -16,16 +16,16 @@ tests:
   - it: should create a ConfigMap with templated values if `controller.config` contains templates
     set:
       controller.config:
-        template: "test.{{ .Release.Namespace }}.svc.kubernetes.local"
-        integer: 12345
-        boolean: true
+        global-rate-limit-memcached-host: "memcached.{{ .Release.Namespace }}.svc.kubernetes.local"
+        global-rate-limit-memcached-port: 11211
+        use-gzip: true
     asserts:
       - equal:
-          path: data.template
-          value: test.NAMESPACE.svc.kubernetes.local
+          path: data.global-rate-limit-memcached-host
+          value: memcached.NAMESPACE.svc.kubernetes.local
       - equal:
-          path: data.integer
-          value: "12345"
+          path: data.global-rate-limit-memcached-port
+          value: "11211"
       - equal:
-          path: data.boolean
+          path: data.use-gzip
           value: "true"
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-daemonset_test.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-daemonset_test.yaml
index 81d067bb..bc810a1c 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-daemonset_test.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-daemonset_test.yaml
@@ -139,34 +139,12 @@ tests:
                           - controller
                   topologyKey: kubernetes.io/hostname
 
-  - it: should create a DaemonSet with a custom registry if `controller.image.registry` is set
-    set:
-      controller.kind: DaemonSet
-      controller.image.registry: custom.registry.io
-      controller.image.tag: v1.0.0-dev
-      controller.image.digest: sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
-    asserts:
-      - equal:
-          path: spec.template.spec.containers[0].image
-          value: custom.registry.io/ingress-nginx/controller:v1.0.0-dev@sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
-
-  - it: should create a DaemonSet with a custom image if `controller.image.image` is set
-    set:
-      controller.kind: DaemonSet
-      controller.image.image: custom-repo/custom-image
-      controller.image.tag: v1.0.0-dev
-      controller.image.digest: sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
-    asserts:
-      - equal:
-          path: spec.template.spec.containers[0].image
-          value: registry.k8s.io/custom-repo/custom-image:v1.0.0-dev@sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
-
   - it: should create a DaemonSet with a custom tag if `controller.image.tag` is set
     set:
       controller.kind: DaemonSet
-      controller.image.tag: custom-tag
+      controller.image.tag: my-little-custom-tag
       controller.image.digest: sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
     asserts:
       - equal:
           path: spec.template.spec.containers[0].image
-          value: registry.k8s.io/ingress-nginx/controller:custom-tag@sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
+          value: registry.k8s.io/ingress-nginx/controller:my-little-custom-tag@sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-deployment_test.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-deployment_test.yaml
index 382aecd7..da400487 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-deployment_test.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-deployment_test.yaml
@@ -161,31 +161,11 @@ tests:
                           - controller
                   topologyKey: kubernetes.io/hostname
 
-  - it: should create a Deployment with a custom registry if `controller.image.registry` is set
-    set:
-      controller.image.registry: custom.registry.io
-      controller.image.tag: v1.0.0-dev
-      controller.image.digest: sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
-    asserts:
-      - equal:
-          path: spec.template.spec.containers[0].image
-          value: custom.registry.io/ingress-nginx/controller:v1.0.0-dev@sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
-
-  - it: should create a Deployment with a custom image if `controller.image.image` is set
-    set:
-      controller.image.image: custom-repo/custom-image
-      controller.image.tag: v1.0.0-dev
-      controller.image.digest: sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
-    asserts:
-      - equal:
-          path: spec.template.spec.containers[0].image
-          value: registry.k8s.io/custom-repo/custom-image:v1.0.0-dev@sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
-
   - it: should create a Deployment with a custom tag if `controller.image.tag` is set
     set:
-      controller.image.tag: custom-tag
+      controller.image.tag: my-little-custom-tag
       controller.image.digest: sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
     asserts:
       - equal:
           path: spec.template.spec.containers[0].image
-          value: registry.k8s.io/ingress-nginx/controller:custom-tag@sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
+          value: registry.k8s.io/ingress-nginx/controller:my-little-custom-tag@sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-poddisruptionbudget_test.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-poddisruptionbudget_test.yaml
index f215f352..48b4fafc 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-poddisruptionbudget_test.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-poddisruptionbudget_test.yaml
@@ -71,19 +71,3 @@ tests:
     asserts:
       - hasDocuments:
           count: 0
-
-  - it: should create a PodDisruptionBudget without `minAvailable` and with `maxUnavailable` if `controller.minAvailable` and `controller.maxUnavailable` are set
-    set:
-      controller.replicaCount: 2
-      controller.minAvailable: 1
-      controller.maxUnavailable: 1
-    asserts:
-      - hasDocuments:
-          count: 1
-      - isKind:
-          of: PodDisruptionBudget
-      - notExists:
-          path: spec.minAvailable
-      - equal:
-          path: spec.maxUnavailable
-          value: 1
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-prometheusrule_test.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-prometheusrule_test.yaml
deleted file mode 100644
index d60a9831..00000000
--- a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-prometheusrule_test.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-suite: Controller > PrometheusRule
-templates:
-  - controller-prometheusrule.yaml
-
-tests:
-  - it: should create a PrometheusRule if `controller.metrics.prometheusRule.enabled` is true
-    set:
-      controller.metrics.enabled: true
-      controller.metrics.prometheusRule.enabled: true
-    asserts:
-      - hasDocuments:
-          count: 1
-      - isKind:
-          of: PrometheusRule
-      - equal:
-          path: metadata.name
-          value: RELEASE-NAME-ingress-nginx-controller
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-service-webhook_test.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-service-webhook_test.yaml
deleted file mode 100644
index 1c759edb..00000000
--- a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-service-webhook_test.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-suite: Controller > Service > Webhook
-templates:
-  - controller-service-webhook.yaml
-
-tests:
-  - it: should not create a webhook Service if `controller.admissionWebhooks.enabled` is false
-    set:
-      controller.admissionWebhooks.enabled: false
-    asserts:
-      - hasDocuments:
-          count: 0
-
-  - it: should create a webhook Service if `controller.admissionWebhooks.enabled` is true
-    set:
-      controller.admissionWebhooks.enabled: true
-    asserts:
-      - hasDocuments:
-          count: 1
-      - isKind:
-          of: Service
-      - equal:
-          path: metadata.name
-          value: RELEASE-NAME-ingress-nginx-controller-admission
-
-  - it: should create a webhook Service with a custom port if `controller.admissionWebhooks.service.servicePort` is set
-    set:
-      controller.admissionWebhooks.enabled: true
-      controller.admissionWebhooks.service.servicePort: 9443
-    asserts:
-      - equal:
-          path: spec.ports[0].port
-          value: 9443
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-serviceaccount_test.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-serviceaccount_test.yaml
deleted file mode 100644
index 928e5377..00000000
--- a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-serviceaccount_test.yaml
+++ /dev/null
@@ -1,47 +0,0 @@
-suite: Controller > ServiceAccount
-templates:
-  - controller-serviceaccount.yaml
-
-tests:
-  - it: should not create a ServiceAccount if `serviceAccount.create` is false
-    set:
-      serviceAccount.create: false
-    asserts:
-      - hasDocuments:
-          count: 0
-
-  - it: should create a ServiceAccount if `serviceAccount.create` is true
-    set:
-      serviceAccount.create: true
-    asserts:
-      - hasDocuments:
-          count: 1
-      - isKind:
-          of: ServiceAccount
-      - equal:
-          path: metadata.name
-          value: RELEASE-NAME-ingress-nginx
-
-  - it: should create a ServiceAccount with specified name if `serviceAccount.name` is set
-    set:
-      serviceAccount.name: ingress-nginx-admission-test-sa
-    asserts:
-      - hasDocuments:
-          count: 1
-      - isKind:
-          of: ServiceAccount
-      - equal:
-          path: metadata.name
-          value: ingress-nginx-admission-test-sa
-
-  - it: should create a ServiceAccount with token auto-mounting disabled if `serviceAccount.automountServiceAccountToken` is false
-    set:
-      serviceAccount.automountServiceAccountToken: false
-    asserts:
-      - hasDocuments:
-          count: 1
-      - isKind:
-          of: ServiceAccount
-      - equal:
-          path: automountServiceAccountToken
-          value: false
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-servicemonitor_test.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-servicemonitor_test.yaml
deleted file mode 100644
index 310097c1..00000000
--- a/packages/system/ingress-nginx/charts/ingress-nginx/tests/controller-servicemonitor_test.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
-suite: Controller > ServiceMonitor
-templates:
-  - controller-servicemonitor.yaml
-
-tests:
-  - it: should create a ServiceMonitor if `controller.metrics.serviceMonitor.enabled` is true
-    set:
-      controller.metrics.enabled: true
-      controller.metrics.serviceMonitor.enabled: true
-    asserts:
-      - hasDocuments:
-          count: 1
-      - isKind:
-          of: ServiceMonitor
-      - equal:
-          path: metadata.name
-          value: RELEASE-NAME-ingress-nginx-controller
-
-  - it: should create a ServiceMonitor with annotations if `controller.metrics.serviceMonitor.annotations` is set
-    set:
-      controller.metrics.enabled: true
-      controller.metrics.serviceMonitor.enabled: true
-      controller.metrics.serviceMonitor.annotations:
-        my-little-annotation: test-value
-    asserts:
-      - equal:
-          path: metadata.annotations
-          value:
-            my-little-annotation: test-value
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/tests/default-backend-deployment_test.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/tests/default-backend-deployment_test.yaml
index 4ba4b03d..e237fe7e 100644
--- a/packages/system/ingress-nginx/charts/ingress-nginx/tests/default-backend-deployment_test.yaml
+++ b/packages/system/ingress-nginx/charts/ingress-nginx/tests/default-backend-deployment_test.yaml
@@ -135,35 +135,3 @@ tests:
                         values:
                           - default-backend
                   topologyKey: kubernetes.io/hostname
-
-  - it: should create a Deployment with a custom registry if `defaultBackend.image.registry` is set
-    set:
-      defaultBackend.enabled: true
-      defaultBackend.image.registry: custom.registry.io
-      defaultBackend.image.tag: v1.0.0-dev
-      defaultBackend.image.digest: sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
-    asserts:
-      - equal:
-          path: spec.template.spec.containers[0].image
-          value: custom.registry.io/defaultbackend-amd64:v1.0.0-dev@sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
-
-  - it: should create a Deployment with a custom image if `defaultBackend.image.image` is set
-    set:
-      defaultBackend.enabled: true
-      defaultBackend.image.image: custom-repo/custom-image
-      defaultBackend.image.tag: v1.0.0-dev
-      defaultBackend.image.digest: sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
-    asserts:
-      - equal:
-          path: spec.template.spec.containers[0].image
-          value: registry.k8s.io/custom-repo/custom-image:v1.0.0-dev@sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
-
-  - it: should create a Deployment with a custom tag if `defaultBackend.image.tag` is set
-    set:
-      defaultBackend.enabled: true
-      defaultBackend.image.tag: custom-tag
-      defaultBackend.image.digest: sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
-    asserts:
-      - equal:
-          path: spec.template.spec.containers[0].image
-          value: registry.k8s.io/defaultbackend-amd64:custom-tag@sha256:faa2d18687f734994b6bd9e309e7a73852a81c30e1b8f63165fcd4f0a087e3cd
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/tests/default-backend-poddisruptionbudget_test.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/tests/default-backend-poddisruptionbudget_test.yaml
deleted file mode 100644
index 09580186..00000000
--- a/packages/system/ingress-nginx/charts/ingress-nginx/tests/default-backend-poddisruptionbudget_test.yaml
+++ /dev/null
@@ -1,48 +0,0 @@
-suite: Default Backend > PodDisruptionBudget
-templates:
-  - default-backend-poddisruptionbudget.yaml
-
-tests:
-  - it: should create a PodDisruptionBudget if `defaultBackend.replicaCount` is greater than 1
-    set:
-      defaultBackend.enabled: true
-      defaultBackend.replicaCount: 2
-    asserts:
-      - hasDocuments:
-          count: 1
-      - isKind:
-          of: PodDisruptionBudget
-      - equal:
-          path: metadata.name
-          value: RELEASE-NAME-ingress-nginx-defaultbackend
-
-  - it: should not create a PodDisruptionBudget if `defaultBackend.replicaCount` is less than or equal 1
-    set:
-      defaultBackend.enabled: true
-      defaultBackend.replicaCount: 1
-    asserts:
-      - hasDocuments:
-          count: 0
-
-  - it: should create a PodDisruptionBudget if `defaultBackend.autoscaling.enabled` is true and `defaultBackend.autoscaling.minReplicas` is greater than 1
-    set:
-      defaultBackend.enabled: true
-      defaultBackend.autoscaling.enabled: true
-      defaultBackend.autoscaling.minReplicas: 2
-    asserts:
-      - hasDocuments:
-          count: 1
-      - isKind:
-          of: PodDisruptionBudget
-      - equal:
-          path: metadata.name
-          value: RELEASE-NAME-ingress-nginx-defaultbackend
-
-  - it: should not create a PodDisruptionBudget if `defaultBackend.autoscaling.enabled` is true and `defaultBackend.autoscaling.minReplicas` is less than or equal 1
-    set:
-      defaultBackend.enabled: true
-      defaultBackend.autoscaling.enabled: true
-      defaultBackend.autoscaling.minReplicas: 1
-    asserts:
-      - hasDocuments:
-          count: 0
diff --git a/packages/system/ingress-nginx/charts/ingress-nginx/tests/default-backend-serviceaccount_test.yaml b/packages/system/ingress-nginx/charts/ingress-nginx/tests/default-backend-serviceaccount_test.yaml
deleted file mode 100644
index 05a815d0..00000000
--- a/packages/system/ingress-nginx/charts/ingress-nginx/tests/default-backend-serviceaccount_test.yaml
+++ /dev/null
@@ -1,51 +0,0 @@
-suite: Default Backend > ServiceAccount
-templates:
-  - default-backend-serviceaccount.yaml
-
-tests:
-  - it: should not create a ServiceAccount if `defaultBackend.serviceAccount.create` is false
-    set:
-      defaultBackend.enabled: true
-      defaultBackend.serviceAccount.create: false
-    asserts:
-      - hasDocuments:
-          count: 0
-
-  - it: should create a ServiceAccount if `defaultBackend.serviceAccount.create` is true
-    set:
-      defaultBackend.enabled: true
-      defaultBackend.serviceAccount.create: true
-    asserts:
-      - hasDocuments:
-          count: 1
-      - isKind:
-          of: ServiceAccount
-      - equal:
-          path: metadata.name
-          value: RELEASE-NAME-ingress-nginx-backend
-
-  - it: should create a ServiceAccount with specified name if `defaultBackend.serviceAccount.name` is set
-    set:
-      defaultBackend.enabled: true
-      defaultBackend.serviceAccount.name: ingress-nginx-admission-test-sa
-    asserts:
-      - hasDocuments:
-          count: 1
-      - isKind:
-          of: ServiceAccount
-      - equal:
-          path: metadata.name
-          value: ingress-nginx-admission-test-sa
-
-  - it: should create a ServiceAccount with token auto-mounting disabled if `defaultBackend.serviceAccount.automountServiceAccountToken` is false
-    set:
-      defaultBackend.enabled: true
-      defaultBackend.serviceAccount.automountServiceAccountToken: false
-    asserts:
-      - hasDocuments:
-          count: 1
-      - isKind:
-          of: ServiceAccount
-      - equal:
-          path: automountServiceAccountToken
-          value: false
diff --git a/packages/system/reloader/charts/reloader/tests/deployment_test.yaml b/packages/system/reloader/charts/reloader/tests/deployment_test.yaml
new file mode 100644
index 00000000..aee0f9fb
--- /dev/null
+++ b/packages/system/reloader/charts/reloader/tests/deployment_test.yaml
@@ -0,0 +1,63 @@
+suite: Deployment
+
+templates:
+  - deployment.yaml
+
+tests:
+  - it: sets readOnlyRootFilesystem in container securityContext when reloader.readOnlyRootFileSystem is true
+    set:
+      reloader:
+        readOnlyRootFileSystem: true
+        deployment:
+          containerSecurityContext:
+            readOnlyRootFilesystem: false
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem
+          value: true
+
+  - it: sets readOnlyRootFilesystem in container securityContext even if reloader.deployment.containerSecurityContext is null
+    set:
+      reloader:
+        readOnlyRootFileSystem: true
+        deployment:
+          containerSecurityContext: null
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem
+          value: true
+
+  - it: does not override readOnlyRootFilesystem in container securityContext based on reloader.readOnlyRootFileSystem
+    set:
+      reloader:
+        readOnlyRootFileSystem: false
+        deployment:
+          containerSecurityContext:
+            readOnlyRootFilesystem: true
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem
+          value: true
+
+  - it: template is still valid with no defined containerSecurityContext
+    set:
+      reloader:
+        readOnlyRootFileSystem: false
+        deployment:
+          containerSecurityContext: null
+    asserts:
+      - isEmpty:
+          path: spec.template.spec.containers[0].securityContext
+
+  - it: template still sets POD_NAME and POD_NAMESPACE environment variables when enableHA is true
+    set:
+      reloader:
+        enableHA: true
+    asserts:
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content:
+            name: POD_NAME
+            valueFrom:
+              fieldRef:
+                fieldPath: metadata.name