Repository: cloudnative-pg/cloudnative-pg

Issue:
[Feature]: Add standard label to all CNPG-managed pods for policy targeting
### Is there an existing issue already for this feature request/idea?

- [x] I have searched for an existing issue, and could not find anything. I believe this is a new feature request to be evaluated.

### What problem is this feature going to solve? Why should it be added?

In environments where PostgreSQL clusters are designed to be self-service, labels become essential to apply cluster-wide policies such as:

* Kubernetes `NetworkPolicy`
* Istio `PeerAuthentication`
* Kyverno or OPA-based policy enforcement
* Admission control via validating/mutating webhooks

Today there are no shared labels across all provisioned cnpg manged pods, meaning that a matchLabels statement cannot target all cnpg managed pods at once. this makes it quite difficult to apply these policies consistently across all provisioned objects.

### Describe the solution you'd like

I'd like to request that all pods managed by CloudNativePG be labeled with a consistent, well-known label such as:

```yaml
app.kubernetes.io/managed-by: cloudnative-pg
```

Adding a well-known label (such as `app.kubernetes.io/managed-by: cloudnative-pg`) to all pods created by CNPG-managed workloads (e.g. Postgres instances, init pods, wal-archive pods, etc) aligns with Kubernetes label best practices and makes it easier for platform operators to apply consistent policies.

you can find a list of well known recommended labels here: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/#labels

### Describe alternatives you've considered

Today, it's possible to work around this by using a `matchExpressions` clause and checking for the existence of the `cnpg.io/cluster` label, for example:

```yaml
matchExpressions:
  - key: cnpg.io/cluster
    operator: Exists
```

However, some systems (such as Istio's `PeerAuthentication`) do not support `matchExpressions` and require explicit `matchLabels`. Without a consistent label, it becomes difficult to safely target only CNPG-managed pods with such policies. Today I am forced to rely on a consumer to add the label, or resort to creating a mutating policy to add the label, while these work, it certainly adds complexity to the process.

### Additional context

_No response_

### Backport?

Yes

### Are you willing to actively contribute to this feature?

Yes

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

Discussion:
@gbartolini: Thanks @JefeDavis for making the time to open this issue and provide detailed information. It makes sense to me. Let me circle that with the other maintainers. Are you willing to submit a PR?@gbartolini: Tentatively adding to 1.28 for now.@JefeDavis: I do not mind adding it, I'll get a branch going today@JefeDavis: @gbartolini is there a preferred label? or is the `app.kubernetes.io/managed-by: cloudnative-pg` acceptable?@gbartolini: > [@gbartolini](https://github.com/gbartolini) is there a preferred label? or is the `app.kubernetes.io/managed-by: cloudnative-pg` acceptable?

Yep, for now I would start with this. We can always decide for a different one before merging. Thanks!@JefeDavis: @gbartolini I've got the PR up, i added the label to Pods, Deployments, Jobs, Services, Secrets, and PodMonitors. I did not touch selector labels or rbac objects@github-actions: This issue is stale because it has been open for 60 days with no activity.@github-actions: This issue was closed because it has been inactive for 14 days since being marked as stale.@JefeDavis: Please re open@mnencia: With #8087, all resources managed by CloudNativePG will now include the standard Kubernetes recommended labels. This makes it much easier to apply policies and integrate with tools across the ecosystem.

Specifically, to the Pods we're adding:

* `app.kubernetes.io/name`: `postgresql`
* `app.kubernetes.io/instance`: The cluster's name
* `app.kubernetes.io/version`: The major PostgreSQL version
* `app.kubernetes.io/component`: `database`
* `app.kubernetes.io/managed-by`: `cloudnative-pg`


Solution patch:
diff --git a/.wordlist-en-custom.txt b/.wordlist-en-custom.txt
index 3b139509..98c4c5f6 100644
--- a/.wordlist-en-custom.txt
+++ b/.wordlist-en-custom.txt
@@ -1128,6 +1128,7 @@ passwordStatus
 pc
 pdf
 periodSeconds
+persistentVolumeClaims
 persistentvolumeclaim
 persistentvolumeclaims
 pgAdmin
@@ -1166,9 +1167,11 @@ podAffinityTerm
 podAntiAffinity
 podAntiAffinityType
 podCount
+podDisruptionBudgets
 podMetricsEndpoints
 podMonitorMetricRelabelings
 podMonitorRelabelings
+podMonitors
 podName
 podStatuses
 podmonitor
diff --git a/docs/src/labels_annotations.md b/docs/src/labels_annotations.md
index 596ffbb3..726fff5f 100644
--- a/docs/src/labels_annotations.md
+++ b/docs/src/labels_annotations.md
@@ -104,6 +104,27 @@ This label is available only on `VolumeSnapshot` resources.
 `cnpg.io/instanceRole`
 : Whether the instance running in a pod is a `primary` or a `replica`.
 
+`app.kubernetes.io/managed-by`
+: Name of the manager. It will always be `cloudnative-pg`.
+  Available across all CloudNativePG managed resources.
+
+`app.kubernetes.io/name`
+: Name of the application. It will always be `postgresql`.
+  Available on pods, jobs, deployments, services, persistentVolumeClaims, volumeSnapshots,
+  podDisruptionBudgets, podMonitors.
+
+`app.kubernetes.io/component`
+: Name of the component (`database`, `pooler`, ...).
+  Available on pods, jobs, deployments, services, persistentVolumeClaims, volumeSnapshots,
+  podDisruptionBudgets, podMonitors.
+
+`app.kubernetes.io/instance`
+: Name of the owning `Cluster` resource.
+  Available on pods, jobs, deployments, services, volumeSnapshots, podDisruptionBudgets, podMonitors.
+
+`app.kubernetes.io/version`
+: Major version of PostgreSQL.
+  Available on pods, jobs, services, volumeSnapshots, podDisruptionBudgets, podMonitors.
 
 ## Predefined annotations
 
diff --git a/internal/controller/cluster_create.go b/internal/controller/cluster_create.go
index 0c2c9c6d..41be0686 100644
--- a/internal/controller/cluster_create.go
+++ b/internal/controller/cluster_create.go
@@ -618,6 +618,9 @@ func (r *ClusterReconciler) createServiceAccount(ctx context.Context, cluster *a
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: cluster.Namespace,
 			Name:      cluster.Name,
+			Labels: map[string]string{
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			},
 		},
 	}
 	err = specs.UpdateServiceAccount(generatedPullSecretNames, serviceAccount)
@@ -792,7 +795,8 @@ func (r *ClusterReconciler) createOrPatchDefaultMetricsConfigmap(ctx context.Con
 				Name:      apiv1.DefaultMonitoringConfigMapName,
 				Namespace: cluster.Namespace,
 				Labels: map[string]string{
-					utils.WatchedLabelName: "true",
+					utils.WatchedLabelName:                "true",
+					utils.KubernetesAppManagedByLabelName: utils.ManagerName,
 				},
 			},
 			Data: map[string]string{
@@ -816,7 +820,7 @@ func (r *ClusterReconciler) createOrPatchDefaultMetricsConfigmap(ctx context.Con
 		return nil
 	}
 
-	// The configuration changed, and we need the patch the secret we have
+	// The configuration changed, and we need the patch the configMap we have
 	patchedConfigMap := targetConfigMap.DeepCopy()
 	utils.SetOperatorVersion(&patchedConfigMap.ObjectMeta, versions.Version)
 	patchedConfigMap.Data = sourceConfigmap.Data
@@ -874,20 +878,21 @@ func (r *ClusterReconciler) createOrPatchDefaultMetricsSecret(ctx context.Contex
 			return err
 		}
 		// If the secret does not exist we create it
-		newConfigMap := corev1.Secret{
+		newSecret := corev1.Secret{
 			ObjectMeta: metav1.ObjectMeta{
 				Name:      apiv1.DefaultMonitoringSecretName,
 				Namespace: cluster.Namespace,
 				Labels: map[string]string{
-					utils.WatchedLabelName: "true",
+					utils.WatchedLabelName:                "true",
+					utils.KubernetesAppManagedByLabelName: utils.ManagerName,
 				},
 			},
 			Data: map[string][]byte{
 				apiv1.DefaultMonitoringKey: sourceSecret.Data[apiv1.DefaultMonitoringKey],
 			},
 		}
-		utils.SetOperatorVersion(&newConfigMap.ObjectMeta, versions.Version)
-		return r.Create(ctx, &newConfigMap)
+		utils.SetOperatorVersion(&newSecret.ObjectMeta, versions.Version)
+		return r.Create(ctx, &newSecret)
 	}
 
 	// We check that we own the existing configmap
diff --git a/pkg/certs/certs.go b/pkg/certs/certs.go
index 1f7d6499..57ae9156 100644
--- a/pkg/certs/certs.go
+++ b/pkg/certs/certs.go
@@ -40,6 +40,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	"github.com/cloudnative-pg/cloudnative-pg/internal/configuration"
+	"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
 )
 
 const (
@@ -237,6 +238,9 @@ func (pair KeyPair) GenerateCASecret(namespace, name string) *corev1.Secret {
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      name,
 			Namespace: namespace,
+			Labels: map[string]string{
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			},
 		},
 		Data: map[string][]byte{
 			CAPrivateKeyKey: pair.Private,
@@ -252,6 +256,9 @@ func (pair KeyPair) GenerateCertificateSecret(namespace, name string) *corev1.Se
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      name,
 			Namespace: namespace,
+			Labels: map[string]string{
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			},
 		},
 		Data: map[string][]byte{
 			TLSPrivateKeyKey: pair.Private,
diff --git a/pkg/reconciler/backup/volumesnapshot/reconciler.go b/pkg/reconciler/backup/volumesnapshot/reconciler.go
index 75246ea1..875197aa 100644
--- a/pkg/reconciler/backup/volumesnapshot/reconciler.go
+++ b/pkg/reconciler/backup/volumesnapshot/reconciler.go
@@ -88,6 +88,13 @@ func (se *Reconciler) enrichSnapshot(
 	vs.Labels[utils.BackupNameLabelName] = backup.Name
 	vs.Labels[utils.MajorVersionLabelName] = strconv.Itoa(backup.Status.MajorVersion)
 
+	// Common labels
+	vs.Labels[utils.KubernetesAppManagedByLabelName] = utils.ManagerName
+	vs.Labels[utils.KubernetesAppLabelName] = utils.AppName
+	vs.Labels[utils.KubernetesAppInstanceLabelName] = cluster.Name
+	vs.Labels[utils.KubernetesAppVersionLabelName] = fmt.Sprint(backup.Status.MajorVersion)
+	vs.Labels[utils.KubernetesAppComponentLabelName] = utils.DatabaseComponentName
+
 	switch snapshotConfig.SnapshotOwnerReference {
 	case apiv1.SnapshotOwnerReferenceCluster:
 		cluster.SetInheritedDataAndOwnership(&vs.ObjectMeta)
diff --git a/pkg/reconciler/persistentvolumeclaim/calculator.go b/pkg/reconciler/persistentvolumeclaim/calculator.go
index e731f9ff..ee1f472d 100644
--- a/pkg/reconciler/persistentvolumeclaim/calculator.go
+++ b/pkg/reconciler/persistentvolumeclaim/calculator.go
@@ -124,6 +124,10 @@ func (r pgDataCalculator) GetLabels(instanceName string) map[string]string {
 	labels := map[string]string{
 		utils.InstanceNameLabelName: instanceName,
 		utils.PvcRoleLabelName:      string(utils.PVCRolePgData),
+		// Common Labels
+		utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+		utils.KubernetesAppLabelName:          utils.AppName,
+		utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
 	}
 	return labels
 }
@@ -191,6 +195,10 @@ func (r pgWalCalculator) GetLabels(instanceName string) map[string]string {
 	labels := map[string]string{
 		utils.InstanceNameLabelName: instanceName,
 		utils.PvcRoleLabelName:      string(utils.PVCRolePgWal),
+		// Common Labels
+		utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+		utils.KubernetesAppLabelName:          utils.AppName,
+		utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
 	}
 	return labels
 }
@@ -250,6 +258,10 @@ func (r pgTablespaceCalculator) GetLabels(instanceName string) map[string]string
 	labels := map[string]string{
 		utils.InstanceNameLabelName: instanceName,
 		utils.PvcRoleLabelName:      string(utils.PVCRolePgTablespace),
+		// Common Labels
+		utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+		utils.KubernetesAppLabelName:          utils.AppName,
+		utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
 	}
 	// we need empty check here as we don't want to impact the label filter with empty value
 	if r.tablespaceName != "" {
diff --git a/pkg/reconciler/persistentvolumeclaim/metadata.go b/pkg/reconciler/persistentvolumeclaim/metadata.go
index 52f8fe12..e1a858df 100644
--- a/pkg/reconciler/persistentvolumeclaim/metadata.go
+++ b/pkg/reconciler/persistentvolumeclaim/metadata.go
@@ -204,6 +204,18 @@ func newLabelReconciler(cluster *apiv1.Cluster) metadataReconciler { //nolint: g
 				return false
 			}
 
+			// Check common labels
+			commonLabels := []string{
+				utils.KubernetesAppManagedByLabelName,
+				utils.KubernetesAppLabelName,
+				utils.KubernetesAppComponentLabelName,
+			}
+			for _, label := range commonLabels {
+				if _, found := pvc.Labels[label]; !found {
+					return false
+				}
+			}
+
 			pvcRole := pvc.Labels[utils.PvcRoleLabelName]
 			for _, instanceName := range cluster.Status.InstanceNames {
 				var found bool
@@ -244,6 +256,11 @@ func newLabelReconciler(cluster *apiv1.Cluster) metadataReconciler { //nolint: g
 		update: func(pvc *corev1.PersistentVolumeClaim) {
 			utils.InheritLabels(&pvc.ObjectMeta, cluster.Labels, cluster.GetFixedInheritedLabels(), configuration.Current)
 
+			// Set common labels
+			pvc.Labels[utils.KubernetesAppManagedByLabelName] = utils.ManagerName
+			pvc.Labels[utils.KubernetesAppLabelName] = utils.AppName
+			pvc.Labels[utils.KubernetesAppComponentLabelName] = utils.DatabaseComponentName
+
 			pvcRole := pvc.Labels[utils.PvcRoleLabelName]
 			for _, instanceName := range cluster.Status.InstanceNames {
 				var found bool
diff --git a/pkg/specs/jobs.go b/pkg/specs/jobs.go
index 30f4a3e4..6fd98d6b 100644
--- a/pkg/specs/jobs.go
+++ b/pkg/specs/jobs.go
@@ -320,6 +320,7 @@ func (role jobRole) getJobName(instanceName string) string {
 func CreatePrimaryJob(cluster apiv1.Cluster, nodeSerial int, role jobRole, initCommand []string) *batchv1.Job {
 	instanceName := GetInstanceName(cluster.Name, nodeSerial)
 	jobName := role.getJobName(instanceName)
+	version, _ := cluster.GetPostgresqlMajorVersion()
 
 	envConfig := CreatePodEnvConfig(cluster, jobName)
 
@@ -328,18 +329,28 @@ func CreatePrimaryJob(cluster apiv1.Cluster, nodeSerial int, role jobRole, initC
 			Name:      jobName,
 			Namespace: cluster.Namespace,
 			Labels: map[string]string{
-				utils.InstanceNameLabelName: instanceName,
-				utils.ClusterLabelName:      cluster.Name,
-				utils.JobRoleLabelName:      string(role),
+				utils.InstanceNameLabelName:           instanceName,
+				utils.ClusterLabelName:                cluster.Name,
+				utils.JobRoleLabelName:                string(role),
+				utils.KubernetesAppLabelName:          utils.AppName,
+				utils.KubernetesAppInstanceLabelName:  cluster.Name,
+				utils.KubernetesAppVersionLabelName:   fmt.Sprint(version),
+				utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
 			},
 		},
 		Spec: batchv1.JobSpec{
 			Template: corev1.PodTemplateSpec{
 				ObjectMeta: metav1.ObjectMeta{
 					Labels: map[string]string{
-						utils.InstanceNameLabelName: instanceName,
-						utils.ClusterLabelName:      cluster.Name,
-						utils.JobRoleLabelName:      string(role),
+						utils.InstanceNameLabelName:           instanceName,
+						utils.ClusterLabelName:                cluster.Name,
+						utils.JobRoleLabelName:                string(role),
+						utils.KubernetesAppLabelName:          utils.AppName,
+						utils.KubernetesAppInstanceLabelName:  cluster.Name,
+						utils.KubernetesAppVersionLabelName:   fmt.Sprint(version),
+						utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+						utils.KubernetesAppManagedByLabelName: utils.ManagerName,
 					},
 				},
 				Spec: corev1.PodSpec{
diff --git a/pkg/specs/pgbouncer/deployments.go b/pkg/specs/pgbouncer/deployments.go
index 612f3e42..390736c5 100644
--- a/pkg/specs/pgbouncer/deployments.go
+++ b/pkg/specs/pgbouncer/deployments.go
@@ -59,6 +59,10 @@ func Deployment(pooler *apiv1.Pooler, cluster *apiv1.Cluster) (*appsv1.Deploymen
 		WithLabel(utils.PgbouncerNameLabel, pooler.Name).
 		WithLabel(utils.ClusterLabelName, cluster.Name).
 		WithLabel(utils.PodRoleLabelName, string(utils.PodRolePooler)).
+		WithLabel(utils.KubernetesAppLabelName, utils.AppName).
+		WithLabel(utils.KubernetesAppInstanceLabelName, cluster.Name).
+		WithLabel(utils.KubernetesAppComponentLabelName, utils.PoolerComponentName).
+		WithLabel(utils.KubernetesAppManagedByLabelName, utils.ManagerName).
 		WithVolume(&corev1.Volume{
 			Name: "ca",
 			VolumeSource: corev1.VolumeSource{
@@ -138,9 +142,13 @@ func Deployment(pooler *apiv1.Pooler, cluster *apiv1.Cluster) (*appsv1.Deploymen
 			Name:      pooler.Name,
 			Namespace: pooler.Namespace,
 			Labels: map[string]string{
-				utils.ClusterLabelName:   cluster.Name,
-				utils.PgbouncerNameLabel: pooler.Name,
-				utils.PodRoleLabelName:   string(utils.PodRolePooler),
+				utils.ClusterLabelName:                cluster.Name,
+				utils.PgbouncerNameLabel:              pooler.Name,
+				utils.PodRoleLabelName:                string(utils.PodRolePooler),
+				utils.KubernetesAppLabelName:          utils.AppName,
+				utils.KubernetesAppInstanceLabelName:  cluster.Name,
+				utils.KubernetesAppComponentLabelName: utils.PoolerComponentName,
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
 			},
 			Annotations: map[string]string{
 				utils.PoolerSpecHashAnnotationName: poolerHash,
diff --git a/pkg/specs/pgbouncer/podmonitor.go b/pkg/specs/pgbouncer/podmonitor.go
index 973024b5..4f77c7a4 100644
--- a/pkg/specs/pgbouncer/podmonitor.go
+++ b/pkg/specs/pgbouncer/podmonitor.go
@@ -49,7 +49,12 @@ func (c PoolerPodMonitorManager) BuildPodMonitor() *monitoringv1.PodMonitor {
 		Namespace: c.pooler.Namespace,
 		Name:      c.pooler.Name,
 		Labels: map[string]string{
-			utils.PgbouncerNameLabel: c.pooler.Name,
+			utils.PgbouncerNameLabel:              c.pooler.Name,
+			utils.PodRoleLabelName:                string(utils.PodRolePooler),
+			utils.KubernetesAppLabelName:          utils.AppName,
+			utils.KubernetesAppInstanceLabelName:  c.pooler.Name,
+			utils.KubernetesAppComponentLabelName: utils.PoolerComponentName,
+			utils.KubernetesAppManagedByLabelName: utils.ManagerName,
 		},
 	}
 
diff --git a/pkg/specs/pgbouncer/services.go b/pkg/specs/pgbouncer/services.go
index 0b865353..1c9e44c1 100644
--- a/pkg/specs/pgbouncer/services.go
+++ b/pkg/specs/pgbouncer/services.go
@@ -43,6 +43,10 @@ func Service(pooler *apiv1.Pooler, cluster *apiv1.Cluster) (*corev1.Service, err
 		WithLabel(utils.PgbouncerNameLabel, pooler.Name).
 		WithLabel(utils.ClusterLabelName, cluster.Name).
 		WithLabel(utils.PodRoleLabelName, string(utils.PodRolePooler)).
+		WithLabel(utils.KubernetesAppLabelName, utils.AppName).
+		WithLabel(utils.KubernetesAppInstanceLabelName, cluster.Name).
+		WithLabel(utils.KubernetesAppComponentLabelName, utils.PoolerComponentName).
+		WithLabel(utils.KubernetesAppManagedByLabelName, utils.ManagerName).
 		WithAnnotation(utils.PoolerSpecHashAnnotationName, poolerHash).
 		WithServiceType(corev1.ServiceTypeClusterIP, false).
 		WithServicePortNoOverwrite(&corev1.ServicePort{
diff --git a/pkg/specs/poddisruptionbudget.go b/pkg/specs/poddisruptionbudget.go
index b44180c7..22509000 100644
--- a/pkg/specs/poddisruptionbudget.go
+++ b/pkg/specs/poddisruptionbudget.go
@@ -20,6 +20,8 @@ SPDX-License-Identifier: Apache-2.0
 package specs
 
 import (
+	"fmt"
+
 	policyv1 "k8s.io/api/policy/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
@@ -38,11 +40,19 @@ func BuildReplicasPodDisruptionBudget(cluster *apiv1.Cluster) *policyv1.PodDisru
 	}
 	minAvailableReplicas := int32(cluster.Spec.Instances - 2) //nolint:gosec
 	allReplicasButOne := intstr.FromInt32(minAvailableReplicas)
+	version, _ := cluster.GetPostgresqlMajorVersion()
 
 	pdb := &policyv1.PodDisruptionBudget{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      cluster.Name,
 			Namespace: cluster.Namespace,
+			Labels: map[string]string{
+				utils.KubernetesAppLabelName:          utils.AppName,
+				utils.KubernetesAppInstanceLabelName:  cluster.Name,
+				utils.KubernetesAppVersionLabelName:   fmt.Sprint(version),
+				utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			},
 		},
 		Spec: policyv1.PodDisruptionBudgetSpec{
 			Selector: &metav1.LabelSelector{
@@ -68,10 +78,19 @@ func BuildPrimaryPodDisruptionBudget(cluster *apiv1.Cluster) *policyv1.PodDisrup
 	}
 	one := intstr.FromInt32(1)
 
+	version, _ := cluster.GetPostgresqlMajorVersion()
+
 	pdb := &policyv1.PodDisruptionBudget{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      cluster.Name + apiv1.PrimaryPodDisruptionBudgetSuffix,
 			Namespace: cluster.Namespace,
+			Labels: map[string]string{
+				utils.KubernetesAppLabelName:          utils.AppName,
+				utils.KubernetesAppInstanceLabelName:  cluster.Name,
+				utils.KubernetesAppVersionLabelName:   fmt.Sprint(version),
+				utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			},
 		},
 		Spec: policyv1.PodDisruptionBudgetSpec{
 			Selector: &metav1.LabelSelector{
diff --git a/pkg/specs/podmonitor.go b/pkg/specs/podmonitor.go
index 128381cb..f440d2a4 100644
--- a/pkg/specs/podmonitor.go
+++ b/pkg/specs/podmonitor.go
@@ -20,6 +20,8 @@ SPDX-License-Identifier: Apache-2.0
 package specs
 
 import (
+	"fmt"
+
 	monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -42,9 +44,18 @@ func (c ClusterPodMonitorManager) IsPodMonitorEnabled() bool {
 
 // BuildPodMonitor builds a new PodMonitor object
 func (c ClusterPodMonitorManager) BuildPodMonitor() *monitoringv1.PodMonitor {
+	version, _ := c.cluster.GetPostgresqlMajorVersion()
+
 	meta := metav1.ObjectMeta{
 		Namespace: c.cluster.Namespace,
 		Name:      c.cluster.Name,
+		Labels: map[string]string{
+			utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			utils.KubernetesAppLabelName:          utils.AppName,
+			utils.KubernetesAppInstanceLabelName:  c.cluster.Name,
+			utils.KubernetesAppVersionLabelName:   fmt.Sprint(version),
+			utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+		},
 	}
 	c.cluster.SetInheritedDataAndOwnership(&meta)
 
diff --git a/pkg/specs/pods.go b/pkg/specs/pods.go
index dfa58a83..4f6d756a 100644
--- a/pkg/specs/pods.go
+++ b/pkg/specs/pods.go
@@ -513,6 +513,7 @@ func buildInstance(
 ) (*corev1.Pod, error) {
 	podName := GetInstanceName(cluster.Name, nodeSerial)
 	gracePeriod := int64(cluster.GetMaxStopDelay())
+	version, _ := cluster.GetPostgresqlMajorVersion()
 
 	envConfig := CreatePodEnvConfig(cluster, podName)
 
@@ -521,9 +522,14 @@ func buildInstance(
 	pod := &corev1.Pod{
 		ObjectMeta: metav1.ObjectMeta{
 			Labels: map[string]string{
-				utils.ClusterLabelName:      cluster.Name,
-				utils.InstanceNameLabelName: podName,
-				utils.PodRoleLabelName:      string(utils.PodRoleInstance),
+				utils.ClusterLabelName:                cluster.Name,
+				utils.InstanceNameLabelName:           podName,
+				utils.PodRoleLabelName:                string(utils.PodRoleInstance),
+				utils.KubernetesAppLabelName:          utils.AppName,
+				utils.KubernetesAppInstanceLabelName:  cluster.Name,
+				utils.KubernetesAppVersionLabelName:   fmt.Sprint(version),
+				utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
 			},
 			Annotations: map[string]string{
 				utils.ClusterSerialAnnotationName: strconv.Itoa(nodeSerial),
diff --git a/pkg/specs/rolebinding.go b/pkg/specs/rolebinding.go
index d5968cec..e7299eff 100644
--- a/pkg/specs/rolebinding.go
+++ b/pkg/specs/rolebinding.go
@@ -22,6 +22,8 @@ package specs
 import (
 	rbacv1 "k8s.io/api/rbac/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
 )
 
 // CreateRoleBinding is the binding between the permissions that the instance manager can use
@@ -31,6 +33,9 @@ func CreateRoleBinding(objectMeta metav1.ObjectMeta) rbacv1.RoleBinding {
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: objectMeta.Namespace,
 			Name:      objectMeta.Name,
+			Labels: map[string]string{
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			},
 		},
 		Subjects: []rbacv1.Subject{
 			{
diff --git a/pkg/specs/roles.go b/pkg/specs/roles.go
index 4567307d..2d1d4dee 100644
--- a/pkg/specs/roles.go
+++ b/pkg/specs/roles.go
@@ -27,6 +27,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	apiv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
+	"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
 )
 
 // CreateRole create a role with the permissions needed by the instance manager
@@ -252,6 +253,9 @@ func CreateRole(cluster apiv1.Cluster, backupOrigin *apiv1.Backup) rbacv1.Role {
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: cluster.Namespace,
 			Name:      cluster.Name,
+			Labels: map[string]string{
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			},
 		},
 		Rules: rules,
 	}
diff --git a/pkg/specs/secrets.go b/pkg/specs/secrets.go
index 52888fc6..c1f4a8aa 100644
--- a/pkg/specs/secrets.go
+++ b/pkg/specs/secrets.go
@@ -69,8 +69,9 @@ func CreateSecret(
 			Name:      name,
 			Namespace: namespace,
 			Labels: map[string]string{
-				utils.UserTypeLabelName: string(usertype),
-				utils.WatchedLabelName:  "true",
+				utils.UserTypeLabelName:               string(usertype),
+				utils.WatchedLabelName:                "true",
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
 			},
 		},
 		Type: corev1.SecretTypeBasicAuth,
diff --git a/pkg/specs/services.go b/pkg/specs/services.go
index 28b58e3a..f6d2692c 100644
--- a/pkg/specs/services.go
+++ b/pkg/specs/services.go
@@ -45,10 +45,20 @@ func buildInstanceServicePorts() []corev1.ServicePort {
 
 // CreateClusterAnyService create a service insisting on all the pods
 func CreateClusterAnyService(cluster apiv1.Cluster) *corev1.Service {
+	version, _ := cluster.GetPostgresqlMajorVersion()
+
 	return &corev1.Service{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      cluster.GetServiceAnyName(),
 			Namespace: cluster.Namespace,
+			Labels: map[string]string{
+				utils.ClusterLabelName:                cluster.Name,
+				utils.KubernetesAppLabelName:          utils.AppName,
+				utils.KubernetesAppInstanceLabelName:  cluster.Name,
+				utils.KubernetesAppVersionLabelName:   fmt.Sprint(version),
+				utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			},
 		},
 		Spec: corev1.ServiceSpec{
 			Type:                     corev1.ServiceTypeClusterIP,
@@ -64,10 +74,20 @@ func CreateClusterAnyService(cluster apiv1.Cluster) *corev1.Service {
 
 // CreateClusterReadService create a service insisting on all the ready pods
 func CreateClusterReadService(cluster apiv1.Cluster) *corev1.Service {
+	version, _ := cluster.GetPostgresqlMajorVersion()
+
 	return &corev1.Service{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      cluster.GetServiceReadName(),
 			Namespace: cluster.Namespace,
+			Labels: map[string]string{
+				utils.ClusterLabelName:                cluster.Name,
+				utils.KubernetesAppLabelName:          utils.AppName,
+				utils.KubernetesAppInstanceLabelName:  cluster.Name,
+				utils.KubernetesAppVersionLabelName:   fmt.Sprint(version),
+				utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			},
 		},
 		Spec: corev1.ServiceSpec{
 			Type:  corev1.ServiceTypeClusterIP,
@@ -82,10 +102,20 @@ func CreateClusterReadService(cluster apiv1.Cluster) *corev1.Service {
 
 // CreateClusterReadOnlyService create a service insisting on all the ready pods
 func CreateClusterReadOnlyService(cluster apiv1.Cluster) *corev1.Service {
+	version, _ := cluster.GetPostgresqlMajorVersion()
+
 	return &corev1.Service{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      cluster.GetServiceReadOnlyName(),
 			Namespace: cluster.Namespace,
+			Labels: map[string]string{
+				utils.ClusterLabelName:                cluster.Name,
+				utils.KubernetesAppLabelName:          utils.AppName,
+				utils.KubernetesAppInstanceLabelName:  cluster.Name,
+				utils.KubernetesAppVersionLabelName:   fmt.Sprint(version),
+				utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			},
 		},
 		Spec: corev1.ServiceSpec{
 			Type:  corev1.ServiceTypeClusterIP,
@@ -100,10 +130,20 @@ func CreateClusterReadOnlyService(cluster apiv1.Cluster) *corev1.Service {
 
 // CreateClusterReadWriteService create a service insisting on the primary pod
 func CreateClusterReadWriteService(cluster apiv1.Cluster) *corev1.Service {
+	version, _ := cluster.GetPostgresqlMajorVersion()
+
 	return &corev1.Service{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      cluster.GetServiceReadWriteName(),
 			Namespace: cluster.Namespace,
+			Labels: map[string]string{
+				utils.ClusterLabelName:                cluster.Name,
+				utils.KubernetesAppLabelName:          utils.AppName,
+				utils.KubernetesAppInstanceLabelName:  cluster.Name,
+				utils.KubernetesAppVersionLabelName:   fmt.Sprint(version),
+				utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			},
 		},
 		Spec: corev1.ServiceSpec{
 			Type:  corev1.ServiceTypeClusterIP,
diff --git a/pkg/utils/labels_annotations.go b/pkg/utils/labels_annotations.go
index dbacbeff..04e218f4 100644
--- a/pkg/utils/labels_annotations.go
+++ b/pkg/utils/labels_annotations.go
@@ -38,6 +38,40 @@ const AlphaMetadataNamespace = "alpha.cnpg.io"
 // MetadataNamespace is the annotation and label namespace used by the operator
 const MetadataNamespace = "cnpg.io"
 
+// KubernetesAppNamespaceDomain is the domain used for the official kubernetes app labels
+const KubernetesAppNamespaceDomain = "app.kubernetes.io"
+
+const (
+	// ManagerName is the name of the manager for cnpg controlled objects
+	ManagerName = "cloudnative-pg"
+
+	// AppName is the name of the application
+	AppName = "postgresql"
+
+	// DatabaseComponentName is the name of the component for the database.
+	DatabaseComponentName = "database"
+
+	// PoolerComponentName is the name of the component for the pooler.
+	PoolerComponentName = "pooler"
+)
+
+const (
+	// KubernetesAppManagedByLabelName is the name of the label applied to all managed objects
+	KubernetesAppManagedByLabelName = KubernetesAppNamespaceDomain + "/managed-by"
+
+	// KubernetesAppLabelName is the name of the label used to indicate the name of the application
+	KubernetesAppLabelName = KubernetesAppNamespaceDomain + "/name"
+
+	// KubernetesAppInstanceLabelName is the name of the label used to indicate the unique instance of this application
+	KubernetesAppInstanceLabelName = KubernetesAppNamespaceDomain + "/instance"
+
+	// KubernetesAppVersionLabelName is the name of the label used to indicate the version postgres
+	KubernetesAppVersionLabelName = KubernetesAppNamespaceDomain + "/version"
+
+	// KubernetesAppComponentLabelName is the name of the label used to indicate the component within the architecture
+	KubernetesAppComponentLabelName = KubernetesAppNamespaceDomain + "/component"
+)
+
 // When you add a new label or annotation, please make sure that you also update the
 // publicly visible documentation, namely the `docs/src/labels_annotations.md` file
 const (



Test patch:
diff --git a/pkg/reconciler/backup/volumesnapshot/reconciler_test.go b/pkg/reconciler/backup/volumesnapshot/reconciler_test.go
index 78fd1338..6e2fe530 100644
--- a/pkg/reconciler/backup/volumesnapshot/reconciler_test.go
+++ b/pkg/reconciler/backup/volumesnapshot/reconciler_test.go
@@ -23,6 +23,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"strconv"
 	"time"
 
 	volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1"
@@ -77,6 +78,7 @@ var _ = Describe("Volumesnapshot reconciler", func() {
 				Annotations: map[string]string{},
 			},
 			Spec: apiv1.ClusterSpec{
+				ImageName: "postgres:18.0",
 				Backup: &apiv1.BackupConfiguration{
 					VolumeSnapshot: &apiv1.VolumeSnapshotConfiguration{
 						ClassName: "csi-hostpath-snapclass",
@@ -120,8 +122,9 @@ var _ = Describe("Volumesnapshot reconciler", func() {
 				Name:      backupName,
 			},
 			Status: apiv1.BackupStatus{
-				StartedAt: ptr.To(startedAt),
-				StoppedAt: ptr.To(stoppedAt),
+				StartedAt:    ptr.To(startedAt),
+				StoppedAt:    ptr.To(stoppedAt),
+				MajorVersion: 18,
 			},
 		}
 	})
@@ -353,6 +356,41 @@ var _ = Describe("Volumesnapshot reconciler", func() {
 		Expect(err).ToNot(HaveOccurred())
 		Expect(data.Len()).To(Equal(0))
 	})
+
+	It("should properly enrich the backup with labels", func(ctx SpecContext) {
+		mockClient := fake.NewClientBuilder().
+			WithScheme(scheme.BuildWithAllKnownScheme()).
+			WithObjects(backup, cluster, targetPod).
+			Build()
+
+		fakeRecorder := record.NewFakeRecorder(3)
+
+		executor := NewReconcilerBuilder(mockClient, fakeRecorder).
+			Build()
+
+		result, err := executor.Reconcile(ctx, cluster, backup, targetPod, pvcs)
+		Expect(err).ToNot(HaveOccurred())
+		Expect(result).ToNot(BeNil())
+
+		var snapshotList volumesnapshotv1.VolumeSnapshotList
+		err = mockClient.List(ctx, &snapshotList)
+		Expect(err).ToNot(HaveOccurred())
+
+		for _, snapshot := range snapshotList.Items {
+			// Expected common labels
+			Expect(snapshot.Labels).To(HaveKeyWithValue(utils.KubernetesAppManagedByLabelName, utils.ManagerName))
+			Expect(snapshot.Labels).To(HaveKeyWithValue(utils.KubernetesAppLabelName, utils.AppName))
+			Expect(snapshot.Labels).To(HaveKeyWithValue(utils.KubernetesAppInstanceLabelName, cluster.Name))
+			Expect(snapshot.Labels).To(HaveKeyWithValue(utils.KubernetesAppVersionLabelName, "18"))
+			Expect(snapshot.Labels).To(HaveKeyWithValue(utils.KubernetesAppComponentLabelName, utils.DatabaseComponentName))
+			// Backup specific labels
+			Expect(snapshot.Labels).To(HaveKeyWithValue(utils.BackupNameLabelName, backup.Name))
+			Expect(snapshot.Labels).To(HaveKeyWithValue(utils.BackupDateLabelName, time.Now().Format("20060102")))
+			Expect(snapshot.Labels).To(HaveKeyWithValue(utils.BackupMonthLabelName, time.Now().Format("200601")))
+			Expect(snapshot.Labels).To(HaveKeyWithValue(utils.BackupYearLabelName, strconv.Itoa(time.Now().Year())))
+			Expect(snapshot.Labels).To(HaveKeyWithValue(utils.MajorVersionLabelName, "18"))
+		}
+	})
 })
 
 var _ = Describe("transferLabelsToAnnotations", func() {
diff --git a/pkg/reconciler/persistentvolumeclaim/calculator_test.go b/pkg/reconciler/persistentvolumeclaim/calculator_test.go
index 68bd4945..87c2b65c 100644
--- a/pkg/reconciler/persistentvolumeclaim/calculator_test.go
+++ b/pkg/reconciler/persistentvolumeclaim/calculator_test.go
@@ -36,6 +36,10 @@ var _ = Describe("pvc role test", func() {
 		expectedLabel := map[string]string{
 			utils.PvcRoleLabelName:      string(utils.PVCRolePgData),
 			utils.InstanceNameLabelName: instanceName,
+			// Common Labels
+			utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			utils.KubernetesAppLabelName:          utils.AppName,
+			utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
 		}
 		cluster := apiv1.Cluster{
 			Spec: apiv1.ClusterSpec{
@@ -73,6 +77,10 @@ var _ = Describe("pvc role test", func() {
 		expectedLabel := map[string]string{
 			utils.PvcRoleLabelName:      string(utils.PVCRolePgWal),
 			utils.InstanceNameLabelName: instanceName,
+			// Common Labels
+			utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			utils.KubernetesAppLabelName:          utils.AppName,
+			utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
 		}
 		cluster := apiv1.Cluster{
 			Spec: apiv1.ClusterSpec{
@@ -110,8 +118,13 @@ var _ = Describe("pvc role test", func() {
 		backupName := "backup1"
 		tbsName := "tbs1"
 		expectedLabel := map[string]string{
-			utils.PvcRoleLabelName:        string(utils.PVCRolePgTablespace),
-			utils.InstanceNameLabelName:   instanceName,
+			utils.PvcRoleLabelName:      string(utils.PVCRolePgTablespace),
+			utils.InstanceNameLabelName: instanceName,
+			// Common Labels
+			utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			utils.KubernetesAppLabelName:          utils.AppName,
+			utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+			// Tablespace specific labels
 			utils.TablespaceNameLabelName: tbsName,
 		}
 		cluster := apiv1.Cluster{
diff --git a/pkg/reconciler/persistentvolumeclaim/metadata_test.go b/pkg/reconciler/persistentvolumeclaim/metadata_test.go
index d57f1072..910ca5ab 100644
--- a/pkg/reconciler/persistentvolumeclaim/metadata_test.go
+++ b/pkg/reconciler/persistentvolumeclaim/metadata_test.go
@@ -35,6 +35,9 @@ var _ = Describe("metadataReconciler", func() {
 		Context("when a PVC is not up-to-date", func() {
 			It("should update the PVC with the correct labels", func() {
 				cluster := &apiv1.Cluster{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-cluster",
+					},
 					Spec: apiv1.ClusterSpec{
 						InheritedMetadata: &apiv1.EmbeddedObjectMetadata{Labels: map[string]string{
 							"label1": "value1",
@@ -62,16 +65,23 @@ var _ = Describe("metadataReconciler", func() {
 				reconciler.update(pvc)
 
 				// Assert that the PVC is up-to-date with the correct labels
-				Expect(pvc.Labels).To(HaveLen(3))
+				Expect(pvc.Labels).To(HaveLen(6))
 				Expect(pvc.Labels).To(HaveKeyWithValue("label1", "value1"))
 				Expect(pvc.Labels).To(HaveKeyWithValue("label2", "value2"))
 				Expect(pvc.Labels).To(HaveKeyWithValue(utils.PvcRoleLabelName, string(utils.PVCRolePgData)))
+				// Expected common labels
+				Expect(pvc.Labels).To(HaveKeyWithValue(utils.KubernetesAppManagedByLabelName, utils.ManagerName))
+				Expect(pvc.Labels).To(HaveKeyWithValue(utils.KubernetesAppLabelName, utils.AppName))
+				Expect(pvc.Labels).To(HaveKeyWithValue(utils.KubernetesAppComponentLabelName, utils.DatabaseComponentName))
 			})
 		})
 
 		Context("when a PVC is up-to-date", func() {
 			It("should not update the PVC labels", func() {
 				cluster := &apiv1.Cluster{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "test-cluster",
+					},
 					Spec: apiv1.ClusterSpec{
 						InheritedMetadata: &apiv1.EmbeddedObjectMetadata{Labels: map[string]string{
 							"label1": "value1",
@@ -90,6 +100,10 @@ var _ = Describe("metadataReconciler", func() {
 							utils.InstanceNameLabelName: "instance1",
 							"label1":                    "value1",
 							"label2":                    "value2",
+							// Common labels
+							utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+							utils.KubernetesAppLabelName:          utils.AppName,
+							utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
 						},
 						Annotations: map[string]string{},
 					},
@@ -103,9 +117,15 @@ var _ = Describe("metadataReconciler", func() {
 				reconciler.update(pvc)
 
 				// Assert that the PVC labels are unchanged
-				Expect(pvc.Labels).To(HaveLen(4))
+				Expect(pvc.Labels).To(HaveLen(7))
 				Expect(pvc.Labels).To(HaveKeyWithValue(utils.PvcRoleLabelName, string(utils.PVCRolePgData)))
 				Expect(pvc.Labels).To(HaveKeyWithValue(utils.InstanceNameLabelName, "instance1"))
+				Expect(pvc.Labels).To(HaveKeyWithValue("label1", "value1"))
+				Expect(pvc.Labels).To(HaveKeyWithValue("label2", "value2"))
+				// Expected common labels
+				Expect(pvc.Labels).To(HaveKeyWithValue(utils.KubernetesAppManagedByLabelName, utils.ManagerName))
+				Expect(pvc.Labels).To(HaveKeyWithValue(utils.KubernetesAppLabelName, utils.AppName))
+				Expect(pvc.Labels).To(HaveKeyWithValue(utils.KubernetesAppComponentLabelName, utils.DatabaseComponentName))
 			})
 		})
 	})
diff --git a/pkg/reconciler/persistentvolumeclaim/reconciler_test.go b/pkg/reconciler/persistentvolumeclaim/reconciler_test.go
index 3e00c5bf..e5d31400 100644
--- a/pkg/reconciler/persistentvolumeclaim/reconciler_test.go
+++ b/pkg/reconciler/persistentvolumeclaim/reconciler_test.go
@@ -22,6 +22,7 @@ package persistentvolumeclaim
 import (
 	"context"
 	"fmt"
+	"strings"
 
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
@@ -203,6 +204,12 @@ var _ = Describe("PVC reconciliation", Ordered, func() {
 		return pvc
 	}
 
+	assertCommonLabels := func(pvcLabels map[string]string) {
+		Expect(pvcLabels).To(HaveKeyWithValue(utils.KubernetesAppManagedByLabelName, utils.ManagerName))
+		Expect(pvcLabels).To(HaveKeyWithValue(utils.KubernetesAppLabelName, utils.AppName))
+		Expect(pvcLabels).To(HaveKeyWithValue(utils.KubernetesAppComponentLabelName, utils.DatabaseComponentName))
+	}
+
 	It("Will reconcile each PVC's with the correct labels", func() {
 		pvcs := corev1.PersistentVolumeClaimList{
 			Items: []corev1.PersistentVolumeClaim{
@@ -243,12 +250,11 @@ var _ = Describe("PVC reconciliation", Ordered, func() {
 			pvcs.Items,
 		)
 		Expect(err).ToNot(HaveOccurred())
-		Expect(pvcs.Items[2].Labels).To(BeEquivalentTo(map[string]string{
-			utils.InstanceNameLabelName: clusterName + "-3-wal",
-			utils.PvcRoleLabelName:      "PG_DATA",
-			"label1":                    "value",
-			"label2":                    "value",
-		}))
+		Expect(pvcs.Items[2].Labels).To(HaveKeyWithValue("label1", "value"))
+		Expect(pvcs.Items[2].Labels).To(HaveKeyWithValue("label2", "value"))
+		Expect(pvcs.Items[2].Labels).To(HaveKeyWithValue(utils.PvcRoleLabelName, string(utils.PVCRolePgData)))
+		Expect(pvcs.Items[2].Labels).To(HaveKeyWithValue(utils.InstanceNameLabelName, clusterName+"-3-wal"))
+		assertCommonLabels(pvcs.Items[2].Labels)
 
 		configuration.Current.InheritedAnnotations = []string{"annotation1"}
 		pvcs.Items[1].Annotations = map[string]string{
@@ -316,29 +322,21 @@ var _ = Describe("PVC reconciliation", Ordered, func() {
 		Expect(err).NotTo(HaveOccurred())
 
 		patchedPvc2 := fetchPVC(cl, pvc2)
-
-		Expect(patchedPvc2.Labels).To(Equal(map[string]string{
-			utils.InstanceNameLabelName: "cluster-pvc-reconciliation-2",
-			utils.PvcRoleLabelName:      "PG_DATA",
-			"label1":                    "value",
-			"label2":                    "value",
-		}))
-
 		patchedPvc3Wal := fetchPVC(cl, pvc3Wal)
-		Expect(patchedPvc3Wal.Labels).To(Equal(map[string]string{
-			utils.InstanceNameLabelName: "cluster-pvc-reconciliation-3",
-			utils.PvcRoleLabelName:      "PG_WAL",
-			"label1":                    "value",
-			"label2":                    "value",
-		}))
-
 		patchedPvc3Data := fetchPVC(cl, pvc3Data)
-		Expect(patchedPvc3Data.Labels).To(Equal(map[string]string{
-			utils.InstanceNameLabelName: "cluster-pvc-reconciliation-3",
-			utils.PvcRoleLabelName:      "PG_DATA",
-			"label1":                    "value",
-			"label2":                    "value",
-		}))
+
+		for _, pvc := range []corev1.PersistentVolumeClaim{patchedPvc2, patchedPvc3Wal, patchedPvc3Data} {
+			role := string(utils.PVCRolePgData)
+			if strings.Contains(pvc.Name, "-wal") {
+				role = string(utils.PVCRolePgWal)
+			}
+			Expect(pvc.Labels).To(HaveKeyWithValue("label1", "value"))
+			Expect(pvc.Labels).To(HaveKeyWithValue("label2", "value"))
+			Expect(pvc.Labels).To(HaveKeyWithValue(utils.PvcRoleLabelName, role))
+			Expect(pvc.Labels).To(HaveKeyWithValue(utils.InstanceNameLabelName,
+				fmt.Sprintf("%s-%s", clusterName, pvc.Annotations[utils.ClusterSerialAnnotationName])))
+			assertCommonLabels(pvc.Labels)
+		}
 	})
 
 	It("will reconcile each PVC's instance-relative labels by invoking the instance metadata reconciler", func() {
@@ -388,56 +386,43 @@ var _ = Describe("PVC reconciliation", Ordered, func() {
 		Expect(err).NotTo(HaveOccurred())
 
 		patchedPvc := fetchPVC(cl, pvc)
-		Expect(patchedPvc.Labels).To(Equal(map[string]string{
-			utils.InstanceNameLabelName:        clusterName + "-1",
-			utils.PvcRoleLabelName:             "PG_DATA",
-			utils.ClusterRoleLabelName:         "primary", //nolint:staticcheck
-			utils.ClusterInstanceRoleLabelName: "primary",
-		}))
-		Expect(patchedPvc.Annotations).To(Equal(map[string]string{
-			utils.ClusterSerialAnnotationName:   "1",
-			utils.PVCStatusAnnotationName:       "ready",
-			utils.OperatorVersionAnnotationName: versions.Version,
-		}))
-
 		patchedPvc2 := fetchPVC(cl, pvc2)
-		Expect(patchedPvc2.Labels).To(Equal(map[string]string{
-			utils.InstanceNameLabelName:        clusterName + "-2",
-			utils.PvcRoleLabelName:             "PG_DATA",
-			utils.ClusterRoleLabelName:         "replica", //nolint:staticcheck
-			utils.ClusterInstanceRoleLabelName: "replica",
-		}))
-		Expect(patchedPvc2.Annotations).To(Equal(map[string]string{
-			utils.OperatorVersionAnnotationName: versions.Version,
-			utils.ClusterSerialAnnotationName:   "2",
-			utils.PVCStatusAnnotationName:       "ready",
-		}))
-
 		patchedPvc3Wal := fetchPVC(cl, pvc3Wal)
-		Expect(patchedPvc3Wal.Labels).To(Equal(map[string]string{
-			utils.InstanceNameLabelName:        clusterName + "-3",
-			utils.PvcRoleLabelName:             "PG_WAL",
-			utils.ClusterRoleLabelName:         "replica", //nolint:staticcheck
-			utils.ClusterInstanceRoleLabelName: "replica",
-		}))
-		Expect(patchedPvc3Wal.Annotations).To(Equal(map[string]string{
-			utils.OperatorVersionAnnotationName: versions.Version,
-			utils.ClusterSerialAnnotationName:   "3",
-			utils.PVCStatusAnnotationName:       "ready",
-		}))
-
 		patchedPvc3Data := fetchPVC(cl, pvc3Data)
-		Expect(patchedPvc3Data.Labels).To(Equal(map[string]string{
-			utils.InstanceNameLabelName:        clusterName + "-3",
-			utils.PvcRoleLabelName:             "PG_DATA",
-			utils.ClusterRoleLabelName:         "replica", //nolint:staticcheck
-			utils.ClusterInstanceRoleLabelName: "replica",
-		}))
-		Expect(patchedPvc3Data.Annotations).To(Equal(map[string]string{
-			utils.OperatorVersionAnnotationName: versions.Version,
-			utils.ClusterSerialAnnotationName:   "3",
-			utils.PVCStatusAnnotationName:       "ready",
-		}))
+
+		// Serial annotation
+		Expect(patchedPvc.Annotations).To(HaveKeyWithValue(utils.ClusterSerialAnnotationName, "1"))
+		Expect(patchedPvc2.Annotations).To(HaveKeyWithValue(utils.ClusterSerialAnnotationName, "2"))
+		Expect(patchedPvc3Wal.Annotations).To(HaveKeyWithValue(utils.ClusterSerialAnnotationName, "3"))
+		Expect(patchedPvc3Data.Annotations).To(HaveKeyWithValue(utils.ClusterSerialAnnotationName, "3"))
+
+		patchedPvcs := []corev1.PersistentVolumeClaim{
+			patchedPvc,
+			patchedPvc2,
+			patchedPvc3Wal,
+			patchedPvc3Data,
+		}
+		for _, pvc := range patchedPvcs {
+			role := string(utils.PVCRolePgData)
+			if strings.Contains(pvc.Name, "-wal") {
+				role = string(utils.PVCRolePgWal)
+			}
+			clusterRole := specs.ClusterRoleLabelPrimary
+			if pvc.Annotations[utils.ClusterSerialAnnotationName] != "1" {
+				clusterRole = specs.ClusterRoleLabelReplica
+			}
+			// Labels
+			Expect(pvc.Labels).To(HaveKeyWithValue(utils.ClusterRoleLabelName, clusterRole)) //nolint:staticcheck
+			Expect(pvc.Labels).To(HaveKeyWithValue(utils.ClusterInstanceRoleLabelName, clusterRole))
+			Expect(pvc.Labels).To(HaveKeyWithValue(utils.PvcRoleLabelName, role))
+			Expect(pvc.Labels).To(HaveKeyWithValue(utils.InstanceNameLabelName,
+				fmt.Sprintf("%s-%s", clusterName, pvc.Annotations[utils.ClusterSerialAnnotationName])))
+			assertCommonLabels(pvc.Labels)
+
+			// Annotations
+			Expect(pvc.Annotations).To(HaveKeyWithValue(utils.PVCStatusAnnotationName, "ready"))
+			Expect(pvc.Annotations).To(HaveKeyWithValue(utils.OperatorVersionAnnotationName, versions.Version))
+		}
 	})
 })
 
diff --git a/pkg/specs/jobs_test.go b/pkg/specs/jobs_test.go
index e4e7ea1d..aa9e4cb1 100644
--- a/pkg/specs/jobs_test.go
+++ b/pkg/specs/jobs_test.go
@@ -27,6 +27,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	apiv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
+	"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
 
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
@@ -195,4 +196,53 @@ var _ = Describe("Job created via InitDB", func() {
 		Expect(initdbFlags).ShouldNot(ContainSubstring("--locale="))
 		Expect(initdbFlags).Should(ContainSubstring("'--icu-rules=&A < z <<< Z'"))
 	})
+
+	It("contains correct labels", func() {
+		cluster := apiv1.Cluster{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: "cluster",
+			},
+			Spec: apiv1.ClusterSpec{
+				ImageName: "postgres:18.0",
+				Bootstrap: &apiv1.BootstrapConfiguration{
+					InitDB: &apiv1.BootstrapInitDB{
+						PostInitSQL:            []string{"testPostInitSql"},
+						PostInitTemplateSQL:    []string{"testPostInitTemplateSql"},
+						PostInitApplicationSQL: []string{"testPostInitApplicationSql"},
+						PostInitApplicationSQLRefs: &apiv1.SQLRefs{
+							SecretRefs: []apiv1.SecretKeySelector{
+								{
+									Key: "secretKey1",
+									LocalObjectReference: apiv1.LocalObjectReference{
+										Name: "secretName1",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		}
+		job := CreatePrimaryJobViaInitdb(cluster, 0)
+		Expect(job.Labels).To(BeEquivalentTo(map[string]string{
+			utils.ClusterLabelName:                cluster.Name,
+			utils.JobRoleLabelName:                "initdb",
+			utils.InstanceNameLabelName:           "cluster-0",
+			utils.KubernetesAppLabelName:          utils.AppName,
+			utils.KubernetesAppInstanceLabelName:  cluster.Name,
+			utils.KubernetesAppVersionLabelName:   "18",
+			utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+			utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+		}))
+		Expect(job.Spec.Template.Labels).To(BeEquivalentTo(map[string]string{
+			utils.ClusterLabelName:                cluster.Name,
+			utils.JobRoleLabelName:                "initdb",
+			utils.InstanceNameLabelName:           "cluster-0",
+			utils.KubernetesAppLabelName:          utils.AppName,
+			utils.KubernetesAppInstanceLabelName:  cluster.Name,
+			utils.KubernetesAppVersionLabelName:   "18",
+			utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+			utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+		}))
+	})
 })
diff --git a/pkg/specs/pgbouncer/deployments_test.go b/pkg/specs/pgbouncer/deployments_test.go
index 6baadb2d..ac98913f 100644
--- a/pkg/specs/pgbouncer/deployments_test.go
+++ b/pkg/specs/pgbouncer/deployments_test.go
@@ -91,10 +91,15 @@ var _ = Describe("Deployment", func() {
 		// Check the metadata
 		Expect(deployment.ObjectMeta.Name).To(Equal(pooler.Name))
 		Expect(deployment.ObjectMeta.Namespace).To(Equal(pooler.Namespace))
-		Expect(deployment.Labels[utils.ClusterLabelName]).To(Equal(cluster.Name))
-		Expect(deployment.Labels[utils.PgbouncerNameLabel]).To(Equal(pooler.Name))
-		Expect(deployment.Labels[utils.PodRoleLabelName]).To(BeEquivalentTo(utils.PodRolePooler))
-
+		Expect(deployment.Labels).To(BeEquivalentTo(map[string]string{
+			utils.ClusterLabelName:                cluster.Name,
+			utils.PgbouncerNameLabel:              pooler.Name,
+			utils.PodRoleLabelName:                string(utils.PodRolePooler),
+			utils.KubernetesAppLabelName:          utils.AppName,
+			utils.KubernetesAppInstanceLabelName:  cluster.Name,
+			utils.KubernetesAppComponentLabelName: utils.PoolerComponentName,
+			utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+		}))
 		// Check the DeploymentSpec
 		Expect(deployment.Spec.Replicas).To(Equal(pooler.Spec.Instances))
 		Expect(deployment.Spec.Selector.MatchLabels[utils.PgbouncerNameLabel]).To(Equal(pooler.Name))
@@ -102,8 +107,15 @@ var _ = Describe("Deployment", func() {
 		// Check the PodTemplateSpec
 		podTemplate := deployment.Spec.Template
 		Expect(podTemplate.ObjectMeta.Annotations).To(Equal(pooler.Spec.Template.ObjectMeta.Annotations))
-		Expect(podTemplate.ObjectMeta.Labels[utils.PgbouncerNameLabel]).To(Equal(pooler.Name))
-		Expect(podTemplate.ObjectMeta.Labels[utils.PodRoleLabelName]).To(BeEquivalentTo(utils.PodRolePooler))
+		Expect(podTemplate.Labels).To(BeEquivalentTo(map[string]string{
+			utils.ClusterLabelName:                cluster.Name,
+			utils.PgbouncerNameLabel:              pooler.Name,
+			utils.PodRoleLabelName:                string(utils.PodRolePooler),
+			utils.KubernetesAppLabelName:          utils.AppName,
+			utils.KubernetesAppInstanceLabelName:  cluster.Name,
+			utils.KubernetesAppComponentLabelName: utils.PoolerComponentName,
+			utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+		}))
 
 		// Check the containers
 		Expect(podTemplate.Spec.Containers).ToNot(BeEmpty())
diff --git a/pkg/specs/pgbouncer/podmonitor_test.go b/pkg/specs/pgbouncer/podmonitor_test.go
index 82feb1f1..b2b1e937 100644
--- a/pkg/specs/pgbouncer/podmonitor_test.go
+++ b/pkg/specs/pgbouncer/podmonitor_test.go
@@ -77,7 +77,12 @@ var _ = Describe("PoolerPodMonitorManager", func() {
 			Expect(podMonitor.Namespace).To(Equal(pooler.Namespace))
 			Expect(podMonitor.Name).To(Equal(pooler.Name))
 			Expect(podMonitor.Labels).To(Equal(map[string]string{
-				utils.PgbouncerNameLabel: pooler.Name,
+				utils.PgbouncerNameLabel:              pooler.Name,
+				utils.PodRoleLabelName:                string(utils.PodRolePooler),
+				utils.KubernetesAppLabelName:          utils.AppName,
+				utils.KubernetesAppInstanceLabelName:  pooler.Name,
+				utils.KubernetesAppComponentLabelName: utils.PoolerComponentName,
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
 			}))
 
 			Expect(podMonitor.Spec.Selector.MatchLabels).To(Equal(map[string]string{
diff --git a/pkg/specs/pgbouncer/services_test.go b/pkg/specs/pgbouncer/services_test.go
index 0163399b..8e38fd3c 100644
--- a/pkg/specs/pgbouncer/services_test.go
+++ b/pkg/specs/pgbouncer/services_test.go
@@ -77,8 +77,15 @@ var _ = Describe("Pooler Service", func() {
 			// Check the metadata
 			Expect(service.Name).To(Equal(pooler.Name))
 			Expect(service.Namespace).To(Equal(pooler.Namespace))
-			Expect(service.Labels[utils.ClusterLabelName]).To(Equal(cluster.Name))
-			Expect(service.Labels[utils.PgbouncerNameLabel]).To(Equal(pooler.Name))
+			Expect(service.Labels).To(BeEquivalentTo(map[string]string{
+				utils.ClusterLabelName:                cluster.Name,
+				utils.PgbouncerNameLabel:              pooler.Name,
+				utils.PodRoleLabelName:                string(utils.PodRolePooler),
+				utils.KubernetesAppLabelName:          utils.AppName,
+				utils.KubernetesAppInstanceLabelName:  cluster.Name,
+				utils.KubernetesAppComponentLabelName: utils.PoolerComponentName,
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			}))
 			Expect(service.Spec.Type).To(Equal(corev1.ServiceTypeClusterIP))
 			Expect(service.Spec.Ports).To(ConsistOf(corev1.ServicePort{
 				Name:       "pgbouncer",
diff --git a/pkg/specs/poddisruptionbudget_test.go b/pkg/specs/poddisruptionbudget_test.go
index a58132a7..a909c007 100644
--- a/pkg/specs/poddisruptionbudget_test.go
+++ b/pkg/specs/poddisruptionbudget_test.go
@@ -23,6 +23,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	apiv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
+	"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
 
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
@@ -38,12 +39,23 @@ var _ = Describe("POD Disruption Budget specifications", func() {
 			Name:      "thistest",
 			Namespace: "default",
 		},
-		Spec: apiv1.ClusterSpec{Instances: instancesNum},
+		Spec: apiv1.ClusterSpec{
+			ImageName: "postgres:18.0",
+			Instances: instancesNum,
+		},
 	}
 
 	It("have the same name as the PostgreSQL cluster", func() {
 		result := BuildReplicasPodDisruptionBudget(cluster)
 		Expect(result.Name).To(Equal(cluster.Name))
+		Expect(result.Labels).To(BeEquivalentTo(map[string]string{
+			utils.ClusterLabelName:                cluster.Name,
+			utils.KubernetesAppLabelName:          utils.AppName,
+			utils.KubernetesAppInstanceLabelName:  cluster.Name,
+			utils.KubernetesAppVersionLabelName:   "18",
+			utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+			utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+		}))
 		Expect(result.Namespace).To(Equal(cluster.Namespace))
 	})
 
diff --git a/pkg/specs/podmonitor_test.go b/pkg/specs/podmonitor_test.go
index 5018b3ed..dd03a659 100644
--- a/pkg/specs/podmonitor_test.go
+++ b/pkg/specs/podmonitor_test.go
@@ -66,7 +66,12 @@ var _ = Describe("PodMonitor test", func() {
 			mgr := NewClusterPodMonitorManager(cluster.DeepCopy())
 			monitor := mgr.BuildPodMonitor()
 			Expect(monitor.Labels).To(BeEquivalentTo(map[string]string{
-				utils.ClusterLabelName: cluster.Name,
+				utils.ClusterLabelName:                cluster.Name,
+				utils.KubernetesAppLabelName:          utils.AppName,
+				utils.KubernetesAppInstanceLabelName:  cluster.Name,
+				utils.KubernetesAppVersionLabelName:   "18",
+				utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+				utils.KubernetesAppManagedByLabelName: utils.ManagerName,
 			}))
 			Expect(monitor.Spec.Selector.MatchLabels).To(BeEquivalentTo(map[string]string{
 				utils.ClusterLabelName: cluster.Name,
@@ -129,6 +134,7 @@ var _ = Describe("PodMonitor test", func() {
 				Name:      clusterName,
 			},
 			Spec: apiv1.ClusterSpec{
+				ImageName: "postgres:18.0",
 				Monitoring: &apiv1.MonitoringConfiguration{
 					EnablePodMonitor: true,
 				},
@@ -146,6 +152,7 @@ var _ = Describe("PodMonitor test", func() {
 				Name:      clusterName,
 			},
 			Spec: apiv1.ClusterSpec{
+				ImageName: "postgres:18.0",
 				Monitoring: &apiv1.MonitoringConfiguration{
 					EnablePodMonitor: true,
 					TLSConfig: &apiv1.ClusterMonitoringTLSConfiguration{
diff --git a/pkg/specs/pods_test.go b/pkg/specs/pods_test.go
index de6fa59b..4c80bd08 100644
--- a/pkg/specs/pods_test.go
+++ b/pkg/specs/pods_test.go
@@ -932,6 +932,32 @@ var _ = Describe("Compute startup probe failure threshold", func() {
 })
 
 var _ = Describe("NewInstance", func() {
+	It("applies correct labels", func(ctx SpecContext) {
+		cluster := apiv1.Cluster{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "test-cluster",
+				Namespace: "default",
+			},
+			Spec: apiv1.ClusterSpec{
+				ImageName: "postgres:18.0",
+			},
+		}
+
+		pod, err := NewInstance(ctx, cluster, 1, true)
+		Expect(err).NotTo(HaveOccurred())
+		Expect(pod).NotTo(BeNil())
+		Expect(pod.Labels).To(BeEquivalentTo(map[string]string{
+			utils.ClusterLabelName:                "test-cluster",
+			utils.InstanceNameLabelName:           "test-cluster-1",
+			utils.PodRoleLabelName:                string(utils.PodRoleInstance),
+			utils.KubernetesAppLabelName:          utils.AppName,
+			utils.KubernetesAppInstanceLabelName:  "test-cluster",
+			utils.KubernetesAppVersionLabelName:   "18",
+			utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+			utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+		}))
+	})
+
 	It("applies JSON patch from annotation", func(ctx SpecContext) {
 		cluster := apiv1.Cluster{
 			ObjectMeta: metav1.ObjectMeta{
diff --git a/pkg/specs/secrets_test.go b/pkg/specs/secrets_test.go
index 8693fadb..5eca6ed6 100644
--- a/pkg/specs/secrets_test.go
+++ b/pkg/specs/secrets_test.go
@@ -32,6 +32,11 @@ var _ = Describe("Secret creation", func() {
 			"thishost", "thisdb", "thisuser", "thispassword", utils.UserTypeApp)
 		Expect(secret.Name).To(Equal("name"))
 		Expect(secret.Namespace).To(Equal("namespace"))
+		Expect(secret.Labels).To(BeEquivalentTo(map[string]string{
+			utils.WatchedLabelName:                "true",
+			utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+			utils.UserTypeLabelName:               string(utils.UserTypeApp),
+		}))
 		Expect(secret.StringData["username"]).To(Equal("thisuser"))
 		Expect(secret.StringData["user"]).To(Equal("thisuser"))
 		Expect(secret.StringData["password"]).To(Equal("thispassword"))
diff --git a/pkg/specs/services_test.go b/pkg/specs/services_test.go
index 2fc0ff0c..1d1e4a86 100644
--- a/pkg/specs/services_test.go
+++ b/pkg/specs/services_test.go
@@ -33,9 +33,12 @@ import (
 )
 
 var _ = Describe("Services specification", func() {
-	postgresql := apiv1.Cluster{
+	cluster := apiv1.Cluster{
 		ObjectMeta: metav1.ObjectMeta{
-			Name: "clustername",
+			Name: "clusterName",
+		},
+		Spec: apiv1.ClusterSpec{
+			ImageName: "postgres:18.0",
 		},
 	}
 	expectedPort := corev1.ServicePort{
@@ -45,44 +48,50 @@ var _ = Describe("Services specification", func() {
 		Port:       postgres.ServerPort,
 	}
 
-	It("create a configured -any service", func() {
-		service := CreateClusterAnyService(postgresql)
-		Expect(service.Name).To(Equal("clustername-any"))
-		Expect(service.Spec.PublishNotReadyAddresses).To(BeTrue())
-		Expect(service.Spec.Selector[utils.ClusterLabelName]).To(Equal("clustername"))
-		Expect(service.Spec.Selector[utils.PodRoleLabelName]).To(Equal(string(utils.PodRoleInstance)))
+	// shared expected labels
+	expectedLabels := map[string]string{
+		utils.ClusterLabelName:                cluster.Name,
+		utils.KubernetesAppLabelName:          utils.AppName,
+		utils.KubernetesAppInstanceLabelName:  cluster.Name,
+		utils.KubernetesAppVersionLabelName:   "18",
+		utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
+		utils.KubernetesAppManagedByLabelName: utils.ManagerName,
+	}
+
+	// helper to assert common service properties
+	assertService := func(
+		service *corev1.Service,
+		expectedName string,
+		publishNotReady bool,
+		selectorKey, selectorValue string,
+	) {
+		Expect(service.Name).To(Equal(expectedName))
+		Expect(service.Labels).To(BeEquivalentTo(expectedLabels))
+		Expect(service.Spec.PublishNotReadyAddresses).To(Equal(publishNotReady))
+		Expect(service.Spec.Selector[utils.ClusterLabelName]).To(Equal(cluster.Name))
+		Expect(service.Spec.Selector[selectorKey]).To(Equal(selectorValue))
 		Expect(service.Spec.Ports).To(HaveLen(1))
 		Expect(service.Spec.Ports).To(ContainElement(expectedPort))
+	}
+
+	It("create a configured -any service", func() {
+		service := CreateClusterAnyService(cluster)
+		assertService(service, cluster.Name+"-any", true, utils.PodRoleLabelName, string(utils.PodRoleInstance))
 	})
 
 	It("create a configured -r service", func() {
-		service := CreateClusterReadService(postgresql)
-		Expect(service.Name).To(Equal("clustername-r"))
-		Expect(service.Spec.PublishNotReadyAddresses).To(BeFalse())
-		Expect(service.Spec.Selector[utils.ClusterLabelName]).To(Equal("clustername"))
-		Expect(service.Spec.Selector[utils.PodRoleLabelName]).To(Equal(string(utils.PodRoleInstance)))
-		Expect(service.Spec.Ports).To(HaveLen(1))
-		Expect(service.Spec.Ports).To(ContainElement(expectedPort))
+		service := CreateClusterReadService(cluster)
+		assertService(service, cluster.Name+"-r", false, utils.PodRoleLabelName, string(utils.PodRoleInstance))
 	})
 
 	It("create a configured -ro service", func() {
-		service := CreateClusterReadOnlyService(postgresql)
-		Expect(service.Name).To(Equal("clustername-ro"))
-		Expect(service.Spec.PublishNotReadyAddresses).To(BeFalse())
-		Expect(service.Spec.Selector[utils.ClusterLabelName]).To(Equal("clustername"))
-		Expect(service.Spec.Selector[utils.ClusterInstanceRoleLabelName]).To(Equal(ClusterRoleLabelReplica))
-		Expect(service.Spec.Ports).To(HaveLen(1))
-		Expect(service.Spec.Ports).To(ContainElement(expectedPort))
+		service := CreateClusterReadOnlyService(cluster)
+		assertService(service, cluster.Name+"-ro", false, utils.ClusterInstanceRoleLabelName, ClusterRoleLabelReplica)
 	})
 
 	It("create a configured -rw service", func() {
-		service := CreateClusterReadWriteService(postgresql)
-		Expect(service.Name).To(Equal("clustername-rw"))
-		Expect(service.Spec.PublishNotReadyAddresses).To(BeFalse())
-		Expect(service.Spec.Selector[utils.ClusterLabelName]).To(Equal("clustername"))
-		Expect(service.Spec.Selector[utils.ClusterInstanceRoleLabelName]).To(Equal(ClusterRoleLabelPrimary))
-		Expect(service.Spec.Ports).To(HaveLen(1))
-		Expect(service.Spec.Ports).To(ContainElement(expectedPort))
+		service := CreateClusterReadWriteService(cluster)
+		assertService(service, cluster.Name+"-rw", false, utils.ClusterInstanceRoleLabelName, ClusterRoleLabelPrimary)
 	})
 })
 
@@ -152,6 +161,7 @@ var _ = Describe("BuildManagedServices", func() {
 			Expect(services).NotTo(BeNil())
 			Expect(services).To(HaveLen(1))
 			Expect(services[0].ObjectMeta.Name).To(Equal("test-service"))
+			Expect(services[0].ObjectMeta.Labels).To(HaveKeyWithValue(utils.KubernetesAppManagedByLabelName, utils.ManagerName))
 			Expect(services[0].ObjectMeta.Labels).To(HaveKeyWithValue(utils.IsManagedLabelName, "true"))
 			Expect(services[0].ObjectMeta.Labels).To(HaveKeyWithValue("test-label", "test-value"))
 			Expect(services[0].ObjectMeta.Annotations).To(HaveKeyWithValue("test-annotation", "test-value"))