diff --git a/data/k8s/dev/devteam/bigquerydataset.yaml b/data/k8s/dev/devteam/bigquerydataset.yaml
index 8622b7454..804f37545 100644
--- a/data/k8s/dev/devteam/bigquerydataset.yaml
+++ b/data/k8s/dev/devteam/bigquerydataset.yaml
@@ -6,7 +6,7 @@ metadata:
     - bqrator.nais.io/finalizer
   generation: 1
   labels:
-    labels.nais.io/key: value
+    key: value
     app: app-w-all-storage
     team: devteam
   name: app-w-all-storage-deleteme_bq-23424asf324
diff --git a/data/k8s/dev/devteam/cm.yaml b/data/k8s/dev/devteam/cm.yaml
index 8738da2db..ec1ef5f49 100644
--- a/data/k8s/dev/devteam/cm.yaml
+++ b/data/k8s/dev/devteam/cm.yaml
@@ -8,7 +8,7 @@ metadata:
     console.nais.io/last-modified-by: bob.ross@nais.io
     reloader.stakater.com/match: "true"
   labels:
-    labels.nais.io/key: value
+    key: value
     app.kubernetes.io/managed-by: console
     nais.io/managed-by: console
   name: asdadasd
diff --git a/data/k8s/dev/devteam/deploy-canary.yaml b/data/k8s/dev/devteam/deploy-canary.yaml
index 559d20464..4791259c2 100644
--- a/data/k8s/dev/devteam/deploy-canary.yaml
+++ b/data/k8s/dev/devteam/deploy-canary.yaml
@@ -2,7 +2,7 @@ apiVersion: nais.io/v1alpha1
 kind: Application
 metadata:
   labels:
-    labels.nais.io/key: value
+    key: value
   annotations:
     deploy.nais.io/client-version: 2023-01-23-7071cd7
     nais.io/deploymentCorrelationID: f8c04f82-6a84-4a8e-9f8b-563b5894d0cf
diff --git a/data/k8s/dev/devteam/job_running.yaml b/data/k8s/dev/devteam/job_running.yaml
index aaccefc4a..4dd427331 100644
--- a/data/k8s/dev/devteam/job_running.yaml
+++ b/data/k8s/dev/devteam/job_running.yaml
@@ -3,7 +3,7 @@ apiVersion: nais.io/v1
 kind: Naisjob
 metadata:
   labels:
-    labels.nais.io/key: value
+    key: value
   annotations:
     deploy.nais.io/client-version: 2023-12-19-bbbb39b
     deploy.nais.io/github-actor: jhrv
diff --git a/data/k8s/dev/devteam/opensearch.yaml b/data/k8s/dev/devteam/opensearch.yaml
index 901549c22..8c3fd294b 100644
--- a/data/k8s/dev/devteam/opensearch.yaml
+++ b/data/k8s/dev/devteam/opensearch.yaml
@@ -17,7 +17,7 @@ metadata:
       name: app-w-all-storage
       uid: 5d684281-5621-4c87-aee7-fca257550790
   labels:
-    labels.nais.io/key: value
+    key: value
     team: devteam
     app: app-w-all-storage
     "nais.io/managed-by": console
diff --git a/data/k8s/dev/devteam/postgres.yaml b/data/k8s/dev/devteam/postgres.yaml
index 496a448bb..20069a296 100644
--- a/data/k8s/dev/devteam/postgres.yaml
+++ b/data/k8s/dev/devteam/postgres.yaml
@@ -4,7 +4,7 @@ metadata:
   name: postgres-1
   namespace: devteam
   labels:
-    labels.nais.io/key: value
+    key: value
     team: devteam
 spec:
   cluster:
diff --git a/data/k8s/dev/devteam/secrets.yaml b/data/k8s/dev/devteam/secrets.yaml
index 2d299743e..43e7e114e 100644
--- a/data/k8s/dev/devteam/secrets.yaml
+++ b/data/k8s/dev/devteam/secrets.yaml
@@ -12,7 +12,7 @@ metadata:
     console.nais.io/last-modified-at: "2021-01-01T00:00:00Z"
     console.nais.io/last-modified-by: "dev.usersen@example.com"
   labels:
-    labels.nais.io/key: value
+    key: value
     nais.io/managed-by: console
     foo: bar
     bar: baz
diff --git a/data/k8s/dev/devteam/sqlinstance.yaml b/data/k8s/dev/devteam/sqlinstance.yaml
index 27b8c85c8..5b793ff55 100644
--- a/data/k8s/dev/devteam/sqlinstance.yaml
+++ b/data/k8s/dev/devteam/sqlinstance.yaml
@@ -10,7 +10,7 @@ metadata:
     cnrm.cloud.google.com/state-into-spec: merge
     nais.io/deploymentCorrelationID: c53778c4-f03f-41a5-ae2f-0ca6a833019e
   labels:
-    labels.nais.io/key: value
+    key: value
     app: app-w-all-storage
     app.kubernetes.io/instance: app-w-all-storage
     app.kubernetes.io/managed-by: Helm
diff --git a/data/k8s/dev/devteam/storagebucket.yaml b/data/k8s/dev/devteam/storagebucket.yaml
index 3a97dcc27..920721a15 100644
--- a/data/k8s/dev/devteam/storagebucket.yaml
+++ b/data/k8s/dev/devteam/storagebucket.yaml
@@ -15,7 +15,7 @@ metadata:
   labels:
     app: app-w-all-storage
     team: devteam
-    labels.nais.io/key: value
+    key: value
   name: uniquebucketname-bucket
   namespace: dev
 spec:
diff --git a/data/k8s/dev/devteam/topic.yaml b/data/k8s/dev/devteam/topic.yaml
index 3e85fe132..69de3f26e 100644
--- a/data/k8s/dev/devteam/topic.yaml
+++ b/data/k8s/dev/devteam/topic.yaml
@@ -19,7 +19,7 @@ metadata:
   generation: 215
   labels:
     team: devteam
-    labels.nais.io/key: value
+    key: value
   name: rapido.the.first
   namespace: devteam
   resourceVersion: "4941946441"
diff --git a/data/k8s/dev/devteam/valkey.yaml b/data/k8s/dev/devteam/valkey.yaml
index de4edb1a8..c57eec938 100644
--- a/data/k8s/dev/devteam/valkey.yaml
+++ b/data/k8s/dev/devteam/valkey.yaml
@@ -7,7 +7,7 @@ metadata:
     controllers.aiven.io/instance-is-running: "true"
     nais.io/deploymentCorrelationID: 61ad0b73-e30f-401a-949c-b8810c2b509a
   labels:
-    labels.nais.io/key: value
+    key: value
     app: app-w-all-storage
     team: devteam
   name: valkey-devteam-contests
diff --git a/integration_tests/configs.lua b/integration_tests/configs.lua
index efec73b3a..f71117af0 100644
--- a/integration_tests/configs.lua
+++ b/integration_tests/configs.lua
@@ -1513,7 +1513,7 @@ Test.gql("Update config labels", function(t)
 		},
 	}
 
-	-- Update labels successfully (fully qualified)
+	-- Update labels successfully with a custom label (non-reserved)
 	t.query [[
 		mutation {
 			updateConfig(input: {
@@ -1521,7 +1521,7 @@ Test.gql("Update config labels", function(t)
 				environmentName: "dev"
 				teamSlug: "myteam"
 				labels: [
-					{ key: "labels.nais.io/tag", value: "testing" }
+					{ key: "my-custom-key", value: "testing" }
 				]
 			}) {
 				config { name labels { key value } }
@@ -1535,14 +1535,14 @@ Test.gql("Update config labels", function(t)
 				config = {
 					name = "labels-test-config",
 					labels = {
-						{ key = "labels.nais.io/tag", value = "testing" },
+						{ key = "my-custom-key", value = "testing" },
 					},
 				},
 			},
 		},
 	}
 
-	-- Try updating with an invalid key (no prefix) -> should fail validation
+	-- Try updating with a reserved key -> should fail validation
 	t.query [[
 		mutation {
 			updateConfig(input: {
@@ -1550,7 +1550,7 @@ Test.gql("Update config labels", function(t)
 				environmentName: "dev"
 				teamSlug: "myteam"
 				labels: [
-					{ key: "tag", value: "invalid" }
+					{ key: "app", value: "invalid" }
 				]
 			}) {
 				config { name }
@@ -1562,7 +1562,7 @@ Test.gql("Update config labels", function(t)
 		errors = {
 			{
 				locations = NotNull(),
-				message = Contains("label key \"tag\" must be prefixed with \"labels.nais.io/\""),
+				message = Contains("is reserved"),
 				path = {
 					"updateConfig",
 				},
@@ -1570,4 +1570,78 @@ Test.gql("Update config labels", function(t)
 		},
 		data = Null,
 	}
+
+	-- Update labels and explicitly specify app.kubernetes.io/managed-by: Helm (value is not console, so it's a valid user label and gets set)
+	t.query [[
+		mutation {
+			updateConfig(input: {
+				name: "labels-test-config"
+				environmentName: "dev"
+				teamSlug: "myteam"
+				labels: [
+					{ key: "app.kubernetes.io/managed-by", value: "Helm" }
+				]
+			}) {
+				config { name labels { key value } }
+			}
+		}
+	]]
+
+	t.check {
+		data = {
+			updateConfig = {
+				config = {
+					name = "labels-test-config",
+					labels = {
+						{ key = "app.kubernetes.io/managed-by", value = "Helm" },
+					},
+				},
+			},
+		},
+	}
+
+	-- Update labels again with another custom label -> app.kubernetes.io/managed-by: Helm must be removed because value is not console!
+	t.query [[
+		mutation {
+			updateConfig(input: {
+				name: "labels-test-config"
+				environmentName: "dev"
+				teamSlug: "myteam"
+				labels: [
+					{ key: "my-custom-key", value: "second-test" }
+				]
+			}) {
+				config { name labels { key value } }
+			}
+		}
+	]]
+
+	t.check {
+		data = {
+			updateConfig = {
+				config = {
+					name = "labels-test-config",
+					labels = {
+						{ key = "my-custom-key", value = "second-test" },
+					},
+				},
+			},
+		},
+	}
+end)
+
+Test.k8s("Validate config labels after update", function(t)
+	t.check("v1", "configmaps", "dev", "myteam", "labels-test-config", {
+		apiVersion = Ignore(),
+		kind = Ignore(),
+		metadata = {
+			name = "labels-test-config",
+			namespace = "myteam",
+			annotations = Ignore(),
+			labels = {
+				["nais.io/managed-by"] = "console",
+				["my-custom-key"] = "second-test",
+			},
+		},
+	})
 end)
diff --git a/integration_tests/k8s_resources/label_selectors/dev/labelteam/applications.yaml b/integration_tests/k8s_resources/label_selectors/dev/labelteam/applications.yaml
index 64744f3e5..542f5a359 100644
--- a/integration_tests/k8s_resources/label_selectors/dev/labelteam/applications.yaml
+++ b/integration_tests/k8s_resources/label_selectors/dev/labelteam/applications.yaml
@@ -5,8 +5,8 @@ metadata:
   name: app-one
   namespace: labelteam
   labels:
-    labels.nais.io/tag: target
-    labels.nais.io/priority: high
+    tag: target
+    priority: high
 spec:
   image: navikt/app-one:latest
 ---
@@ -16,7 +16,7 @@ metadata:
   name: app-two
   namespace: labelteam
   labels:
-    labels.nais.io/tag: target
+    tag: target
 spec:
   image: navikt/app-two:latest
 ---
@@ -26,6 +26,6 @@ metadata:
   name: app-three
   namespace: labelteam
   labels:
-    labels.nais.io/tag: other
+    tag: other
 spec:
   image: navikt/app-three:latest
diff --git a/integration_tests/k8s_resources/label_selectors/dev/labelteam/jobs.yaml b/integration_tests/k8s_resources/label_selectors/dev/labelteam/jobs.yaml
index 67328c2c5..cf70cd13f 100644
--- a/integration_tests/k8s_resources/label_selectors/dev/labelteam/jobs.yaml
+++ b/integration_tests/k8s_resources/label_selectors/dev/labelteam/jobs.yaml
@@ -5,8 +5,8 @@ metadata:
   name: job-one
   namespace: labelteam
   labels:
-    labels.nais.io/tag: target
-    labels.nais.io/priority: high
+    tag: target
+    priority: high
 spec:
   image: navikt/job-one:latest
 ---
@@ -16,7 +16,7 @@ metadata:
   name: job-two
   namespace: labelteam
   labels:
-    labels.nais.io/tag: target
+    tag: target
 spec:
   image: navikt/job-two:latest
 ---
@@ -26,6 +26,6 @@ metadata:
   name: job-three
   namespace: labelteam
   labels:
-    labels.nais.io/tag: other
+    tag: other
 spec:
   image: navikt/job-three:latest
diff --git a/integration_tests/k8s_resources/label_selectors/dev/labelteam/postgres.yaml b/integration_tests/k8s_resources/label_selectors/dev/labelteam/postgres.yaml
index 5d26c3442..7b92807cd 100644
--- a/integration_tests/k8s_resources/label_selectors/dev/labelteam/postgres.yaml
+++ b/integration_tests/k8s_resources/label_selectors/dev/labelteam/postgres.yaml
@@ -5,8 +5,8 @@ metadata:
   name: postgres-one
   namespace: labelteam
   labels:
-    labels.nais.io/tag: target
-    labels.nais.io/priority: high
+    tag: target
+    priority: high
 spec:
   cluster:
     majorVersion: "17"
@@ -23,7 +23,7 @@ metadata:
   name: postgres-two
   namespace: labelteam
   labels:
-    labels.nais.io/tag: target
+    tag: target
 spec:
   cluster:
     majorVersion: "17"
@@ -40,7 +40,7 @@ metadata:
   name: postgres-three
   namespace: labelteam
   labels:
-    labels.nais.io/tag: other
+    tag: other
 spec:
   cluster:
     majorVersion: "17"
diff --git a/integration_tests/k8s_resources/label_selectors/dev/labelteam/valkeys.yaml b/integration_tests/k8s_resources/label_selectors/dev/labelteam/valkeys.yaml
index cd461edf5..8b4ed23d3 100644
--- a/integration_tests/k8s_resources/label_selectors/dev/labelteam/valkeys.yaml
+++ b/integration_tests/k8s_resources/label_selectors/dev/labelteam/valkeys.yaml
@@ -5,8 +5,8 @@ metadata:
   name: valkey-one
   namespace: labelteam
   labels:
-    labels.nais.io/tag: target
-    labels.nais.io/priority: high
+    tag: target
+    priority: high
 spec:
   plan: startup-4
 ---
@@ -16,7 +16,7 @@ metadata:
   name: valkey-two
   namespace: labelteam
   labels:
-    labels.nais.io/tag: target
+    tag: target
 spec:
   plan: startup-4
 ---
@@ -26,6 +26,6 @@ metadata:
   name: valkey-three
   namespace: labelteam
   labels:
-    labels.nais.io/tag: other
+    tag: other
 spec:
   plan: startup-4
diff --git a/integration_tests/label_selectors.lua b/integration_tests/label_selectors.lua
index fa8a39336..20a087769 100644
--- a/integration_tests/label_selectors.lua
+++ b/integration_tests/label_selectors.lua
@@ -38,20 +38,20 @@ Test.gql("Check all Valkey instances (no filter)", function(t)
 						{
 							name = "valkey-one",
 							labels = {
-								{ key = "labels.nais.io/priority", value = "high" },
-								{ key = "labels.nais.io/tag",      value = "target" },
+								{ key = "priority", value = "high" },
+								{ key = "tag",      value = "target" },
 							},
 						},
 						{
 							name = "valkey-three",
 							labels = {
-								{ key = "labels.nais.io/tag", value = "other" },
+								{ key = "tag", value = "other" },
 							},
 						},
 						{
 							name = "valkey-two",
 							labels = {
-								{ key = "labels.nais.io/tag", value = "target" },
+								{ key = "tag", value = "target" },
 							},
 						},
 					},
@@ -66,7 +66,7 @@ Test.gql("Valkey filter by tag=target", function(t)
 	t.query [[
 		{
 			team(slug: "labelteam") {
-				valkeys(filter: { labels: [{ key: "labels.nais.io/tag", value: "target" }] }) {
+				valkeys(filter: { labels: [{ key: "tag", value: "target" }] }) {
 					pageInfo {
 						totalCount
 					}
@@ -102,8 +102,8 @@ Test.gql("Valkey filter by tag=target and priority=high", function(t)
 			team(slug: "labelteam") {
 				valkeys(filter: {
 					labels: [
-						{ key: "labels.nais.io/tag", value: "target" },
-						{ key: "labels.nais.io/priority", value: "high" }
+						{ key: "tag", value: "target" },
+						{ key: "priority", value: "high" }
 					]
 				}) {
 					pageInfo {
@@ -167,20 +167,20 @@ Test.gql("Check all Postgres instances (no filter)", function(t)
 						{
 							name = "postgres-one",
 							labels = {
-								{ key = "labels.nais.io/priority", value = "high" },
-								{ key = "labels.nais.io/tag",      value = "target" },
+								{ key = "priority", value = "high" },
+								{ key = "tag",      value = "target" },
 							},
 						},
 						{
 							name = "postgres-three",
 							labels = {
-								{ key = "labels.nais.io/tag", value = "other" },
+								{ key = "tag", value = "other" },
 							},
 						},
 						{
 							name = "postgres-two",
 							labels = {
-								{ key = "labels.nais.io/tag", value = "target" },
+								{ key = "tag", value = "target" },
 							},
 						},
 					},
@@ -195,7 +195,7 @@ Test.gql("Postgres filter by tag=target", function(t)
 	t.query [[
 		{
 			team(slug: "labelteam") {
-				postgresInstances(filter: { labels: [{ key: "labels.nais.io/tag", value: "target" }] }) {
+				postgresInstances(filter: { labels: [{ key: "tag", value: "target" }] }) {
 					pageInfo {
 						totalCount
 					}
@@ -231,8 +231,8 @@ Test.gql("Postgres filter by tag=target and priority=high", function(t)
 			team(slug: "labelteam") {
 				postgresInstances(filter: {
 					labels: [
-						{ key: "labels.nais.io/tag", value: "target" },
-						{ key: "labels.nais.io/priority", value: "high" }
+						{ key: "tag", value: "target" },
+						{ key: "priority", value: "high" }
 					]
 				}) {
 					pageInfo {
@@ -296,20 +296,20 @@ Test.gql("Check all applications (no filter)", function(t)
 						{
 							name = "app-one",
 							labels = {
-								{ key = "labels.nais.io/priority", value = "high" },
-								{ key = "labels.nais.io/tag",      value = "target" },
+								{ key = "priority", value = "high" },
+								{ key = "tag",      value = "target" },
 							},
 						},
 						{
 							name = "app-three",
 							labels = {
-								{ key = "labels.nais.io/tag", value = "other" },
+								{ key = "tag", value = "other" },
 							},
 						},
 						{
 							name = "app-two",
 							labels = {
-								{ key = "labels.nais.io/tag", value = "target" },
+								{ key = "tag", value = "target" },
 							},
 						},
 					},
@@ -324,7 +324,7 @@ Test.gql("Application filter by tag=target", function(t)
 	t.query [[
 		{
 			team(slug: "labelteam") {
-				applications(filter: { labels: [{ key: "labels.nais.io/tag", value: "target" }] }) {
+				applications(filter: { labels: [{ key: "tag", value: "target" }] }) {
 					pageInfo {
 						totalCount
 					}
@@ -360,8 +360,8 @@ Test.gql("Application filter by tag=target and priority=high", function(t)
 			team(slug: "labelteam") {
 				applications(filter: {
 					labels: [
-						{ key: "labels.nais.io/tag", value: "target" },
-						{ key: "labels.nais.io/priority", value: "high" }
+						{ key: "tag", value: "target" },
+						{ key: "priority", value: "high" }
 					]
 				}) {
 					pageInfo {
@@ -425,20 +425,20 @@ Test.gql("Check all jobs (no filter)", function(t)
 						{
 							name = "job-one",
 							labels = {
-								{ key = "labels.nais.io/priority", value = "high" },
-								{ key = "labels.nais.io/tag",      value = "target" },
+								{ key = "priority", value = "high" },
+								{ key = "tag",      value = "target" },
 							},
 						},
 						{
 							name = "job-three",
 							labels = {
-								{ key = "labels.nais.io/tag", value = "other" },
+								{ key = "tag", value = "other" },
 							},
 						},
 						{
 							name = "job-two",
 							labels = {
-								{ key = "labels.nais.io/tag", value = "target" },
+								{ key = "tag", value = "target" },
 							},
 						},
 					},
@@ -453,7 +453,7 @@ Test.gql("Job filter by tag=target", function(t)
 	t.query [[
 		{
 			team(slug: "labelteam") {
-				jobs(filter: { name: "", labels: [{ key: "labels.nais.io/tag", value: "target" }] }) {
+				jobs(filter: { name: "", labels: [{ key: "tag", value: "target" }] }) {
 					pageInfo {
 						totalCount
 					}
@@ -490,8 +490,8 @@ Test.gql("Job filter by tag=target and priority=high", function(t)
 				jobs(filter: {
 					name: ""
 					labels: [
-						{ key: "labels.nais.io/tag", value: "target" },
-						{ key: "labels.nais.io/priority", value: "high" }
+						{ key: "tag", value: "target" },
+						{ key: "priority", value: "high" }
 					]
 				}) {
 					pageInfo {
@@ -521,12 +521,12 @@ Test.gql("Job filter by tag=target and priority=high", function(t)
 	}
 end)
 
-Test.gql("Valkey filter with invalid label prefix", function(t)
+Test.gql("Valkey filter with reserved label key", function(t)
 	t.addHeader("x-user-email", user:email())
 	t.query [[
 		{
 			team(slug: "labelteam") {
-				valkeys(filter: { labels: [{ key: "tag", value: "target" }] }) {
+				valkeys(filter: { labels: [{ key: "app", value: "target" }] }) {
 					pageInfo {
 						totalCount
 					}
@@ -538,7 +538,7 @@ Test.gql("Valkey filter with invalid label prefix", function(t)
 	t.check {
 		errors = {
 			{
-				message = Contains("label key \"tag\" must be prefixed with \"labels.nais.io/\""),
+				message = Contains("Label key \"app\" is reserved and cannot be used"),
 				path = {
 					"team",
 					"valkeys",
@@ -575,9 +575,9 @@ Test.gql("Check labels facets on Valkey connection", function(t)
 				valkeys = {
 					facets = {
 						labels = {
-							{ key = "labels.nais.io/priority", value = "high",   count = 1 },
-							{ key = "labels.nais.io/tag",      value = "other",  count = 1 },
-							{ key = "labels.nais.io/tag",      value = "target", count = 2 },
+							{ key = "priority", value = "high",   count = 1 },
+							{ key = "tag",      value = "other",  count = 1 },
+							{ key = "tag",      value = "target", count = 2 },
 						},
 					},
 				},
diff --git a/integration_tests/secrets.lua b/integration_tests/secrets.lua
index e7e776ef0..6ea9a351d 100644
--- a/integration_tests/secrets.lua
+++ b/integration_tests/secrets.lua
@@ -1008,7 +1008,7 @@ Test.gql("Update secret labels", function(t)
 				environmentName: "dev"
 				teamSlug: "myteam"
 				labels: [
-					{ key: "labels.nais.io/tag", value: "testing" }
+					{ key: "tag", value: "testing" }
 				]
 			}) {
 				secret { name labels { key value } }
@@ -1022,14 +1022,14 @@ Test.gql("Update secret labels", function(t)
 				secret = {
 					name = "labels-test-secret",
 					labels = {
-						{ key = "labels.nais.io/tag", value = "testing" },
+						{ key = "tag", value = "testing" },
 					},
 				},
 			},
 		},
 	}
 
-	-- Try updating with an invalid key (no prefix) -> should fail validation
+	-- Try updating with a reserved key -> should fail validation
 	t.query [[
 		mutation {
 			updateSecret(input: {
@@ -1037,7 +1037,7 @@ Test.gql("Update secret labels", function(t)
 				environmentName: "dev"
 				teamSlug: "myteam"
 				labels: [
-					{ key: "tag", value: "invalid" }
+					{ key: "app", value: "invalid" }
 				]
 			}) {
 				secret { name }
@@ -1049,7 +1049,7 @@ Test.gql("Update secret labels", function(t)
 		errors = {
 			{
 				locations = NotNull(),
-				message = Contains("label key \"tag\" must be prefixed with \"labels.nais.io/\""),
+				message = Contains("is reserved"),
 				path = {
 					"updateSecret",
 				},
diff --git a/integration_tests/valkey_crud.lua b/integration_tests/valkey_crud.lua
index 4f9aabc80..312706067 100644
--- a/integration_tests/valkey_crud.lua
+++ b/integration_tests/valkey_crud.lua
@@ -1071,3 +1071,247 @@ Test.gql("Verify otherTeam activity log is isolated from mainTeam", function(t)
 		},
 	}
 end)
+
+Test.gql("Create Valkey to test labels", function(t)
+	t.addHeader("x-user-email", user:email())
+	t.query [[
+		mutation CreateValkey {
+		  createValkey(
+		    input: {
+		      name: "labels-valkey"
+		      environmentName: "dev"
+		      teamSlug: "someteamname"
+		      tier: SINGLE_NODE
+		      memory: GB_14
+		    }
+		  ) {
+		    valkey {
+		      name
+		      labels {
+		        key
+		        value
+		      }
+		    }
+		  }
+		}
+	]]
+
+	t.check {
+		data = {
+			createValkey = {
+				valkey = {
+					name = "labels-valkey",
+					labels = {},
+				},
+			},
+		},
+	}
+end)
+
+Test.gql("Update Valkey labels successfully", function(t)
+	t.addHeader("x-user-email", user:email())
+	t.query [[
+		mutation UpdateValkey {
+		  updateValkey(
+		    input: {
+		      name: "labels-valkey"
+		      environmentName: "dev"
+		      teamSlug: "someteamname"
+		      tier: SINGLE_NODE
+		      memory: GB_14
+		      labels: [
+		        { key: "my-custom-key", value: "testing" }
+		      ]
+		    }
+		  ) {
+		    valkey {
+		      name
+		      labels {
+		        key
+		        value
+		      }
+		    }
+		  }
+		}
+	]]
+
+	t.check {
+		data = {
+			updateValkey = {
+				valkey = {
+					name = "labels-valkey",
+					labels = {
+						{ key = "my-custom-key", value = "testing" },
+					},
+				},
+			},
+		},
+	}
+end)
+
+Test.gql("Update Valkey labels with reserved key -> should fail validation", function(t)
+	t.addHeader("x-user-email", user:email())
+	t.query [[
+		mutation UpdateValkey {
+		  updateValkey(
+		    input: {
+		      name: "labels-valkey"
+		      environmentName: "dev"
+		      teamSlug: "someteamname"
+		      tier: SINGLE_NODE
+		      memory: GB_14
+		      labels: [
+		        { key: "app", value: "invalid" }
+		      ]
+		    }
+		  ) {
+		    valkey {
+		      name
+		    }
+		  }
+		}
+	]]
+
+	t.check {
+		errors = {
+			{
+				message = Contains("is reserved"),
+				path = {
+					"updateValkey",
+				},
+				extensions = {
+					field = "labels",
+				},
+			},
+		},
+		data = Null,
+	}
+end)
+
+Test.gql("Update Valkey labels to specify app.kubernetes.io/managed-by: Helm", function(t)
+	t.addHeader("x-user-email", user:email())
+	t.query [[
+		mutation UpdateValkey {
+		  updateValkey(
+		    input: {
+		      name: "labels-valkey"
+		      environmentName: "dev"
+		      teamSlug: "someteamname"
+		      tier: SINGLE_NODE
+		      memory: GB_14
+		      labels: [
+		        { key: "app.kubernetes.io/managed-by", value: "Helm" }
+		      ]
+		    }
+		  ) {
+		    valkey {
+		      name
+		      labels {
+		        key
+		        value
+		      }
+		    }
+		  }
+		}
+	]]
+
+	t.check {
+		data = {
+			updateValkey = {
+				valkey = {
+					name = "labels-valkey",
+					labels = {
+						{ key = "app.kubernetes.io/managed-by", value = "Helm" },
+					},
+				},
+			},
+		},
+	}
+end)
+
+Test.gql(
+	"Update Valkey labels again -> app.kubernetes.io/managed-by: Helm must be removed because value is not console!",
+	function(t)
+		t.addHeader("x-user-email", user:email())
+		t.query [[
+		mutation UpdateValkey {
+		  updateValkey(
+		    input: {
+		      name: "labels-valkey"
+		      environmentName: "dev"
+		      teamSlug: "someteamname"
+		      tier: SINGLE_NODE
+		      memory: GB_14
+		      labels: [
+		        { key: "my-custom-key", value: "second-test" }
+		      ]
+		    }
+		  ) {
+		    valkey {
+		      name
+		      labels {
+		        key
+		        value
+		      }
+		    }
+		  }
+		}
+	]]
+
+		t.check {
+			data = {
+				updateValkey = {
+					valkey = {
+						name = "labels-valkey",
+						labels = {
+							{ key = "my-custom-key", value = "second-test" },
+						},
+					},
+				},
+			},
+		}
+	end)
+
+Test.k8s("Validate Valkey labels after update", function(t)
+	local resourceName = string.format("valkey-%s-labels-valkey", mainTeam:slug())
+
+	t.check("aiven.io/v1alpha1", "valkeys", "dev", mainTeam:slug(), resourceName, {
+		apiVersion = "aiven.io/v1alpha1",
+		kind = "Valkey",
+		metadata = {
+			name = resourceName,
+			namespace = mainTeam:slug(),
+			annotations = Ignore(),
+			labels = {
+				["nais.io/managed-by"] = "console",
+				["my-custom-key"] = "second-test",
+			},
+		},
+		spec = Ignore(),
+	})
+end)
+
+Test.gql("Clean up labels Valkey", function(t)
+	t.addHeader("x-user-email", user:email())
+	t.query [[
+		mutation DeleteValkey {
+		  deleteValkey(
+		    input: {
+		      name: "labels-valkey"
+		      environmentName: "dev"
+		      teamSlug: "someteamname"
+		    }
+		  ) {
+		    valkeyDeleted
+		  }
+		}
+	]]
+
+	t.check {
+		data = {
+			deleteValkey = {
+				valkeyDeleted = true,
+			},
+		},
+	}
+end)
diff --git a/internal/graph/gengql/root_.generated.go b/internal/graph/gengql/root_.generated.go
index e3a77e5c9..3261a9c2a 100644
--- a/internal/graph/gengql/root_.generated.go
+++ b/internal/graph/gengql/root_.generated.go
@@ -24343,8 +24343,6 @@ A user-defined label attached to a resource.
 Labels are key-value pairs that teams can use to organize and filter their
 resources. Both the key and the value may contain letters, numbers, hyphens,
 underscores and dots, and may be at most 63 characters long.
-
-Keys must be prefixed with labels.nais.io/.
 """
 type ResourceLabel {
 	"The label key."
diff --git a/internal/graph/model/labels.go b/internal/graph/model/labels.go
index 9394234ed..1fc5a7325 100644
--- a/internal/graph/model/labels.go
+++ b/internal/graph/model/labels.go
@@ -12,11 +12,6 @@ import (
 	"k8s.io/apimachinery/pkg/util/validation"
 )
 
-// UserLabelPrefix is the namespace under which user-defined labels are stored on
-// Nais resources. Only labels with this prefix are user-editable and exposed
-// through the API; all other labels are considered platform-internal.
-const UserLabelPrefix = "labels.nais.io/"
-
 type ResourceLabel struct {
 	Key   string `json:"key"`
 	Value string `json:"value"`
@@ -74,8 +69,8 @@ func ValidateUserLabels(labels []*ResourceLabel) error {
 		}
 		seen[l.Key] = struct{}{}
 
-		if !strings.HasPrefix(l.Key, UserLabelPrefix) {
-			return LabelValidationError{Message: fmt.Sprintf("label key %q must be prefixed with %q", l.Key, UserLabelPrefix)}
+		if HiddenLabelKey(l.Key, l.Value) {
+			return LabelValidationError{Message: fmt.Sprintf("Label key %q is reserved", l.Key)}
 		}
 
 		for _, msg := range validation.IsQualifiedName(l.Key) {
@@ -103,14 +98,16 @@ func (l *LabelFilters) UnmarshalGQL(v any) error {
 
 		key, _ := itemMap["key"].(string)
 
-		if !strings.HasPrefix(key, UserLabelPrefix) {
-			return LabelValidationError{Message: fmt.Sprintf("label key %q must be prefixed with %q", key, UserLabelPrefix)}
-		}
-
+		var value string
 		var valuePtr *string
 		if val, ok := itemMap["value"]; ok && val != nil {
 			valStr, _ := val.(string)
 			valuePtr = &valStr
+			value = valStr
+		}
+
+		if HiddenLabelKey(key, value) {
+			return LabelValidationError{Message: fmt.Sprintf("Label key %q is reserved and cannot be used", key)}
 		}
 
 		filters = append(filters, &LabelFilter{
@@ -144,7 +141,7 @@ func (l LabelFilters) MarshalGQL(w io.Writer) {
 func UserLabels(labels map[string]string) []*ResourceLabel {
 	out := make([]*ResourceLabel, 0, len(labels))
 	for k, v := range labels {
-		if !strings.HasPrefix(k, UserLabelPrefix) && k != UserLabelPrefix {
+		if HiddenLabelKey(k, v) {
 			continue
 		}
 		out = append(out, &ResourceLabel{Key: k, Value: v})
@@ -208,7 +205,7 @@ func MatchesLabelFilters(labels []*ResourceLabel, filters []*LabelFilter) bool {
 func MergeUserLabels(existing map[string]string, desired []*ResourceLabel) map[string]string {
 	out := make(map[string]string, len(existing)+len(desired))
 	for k, v := range existing {
-		if strings.HasPrefix(k, UserLabelPrefix) {
+		if !HiddenLabelKey(k, v) {
 			continue
 		}
 		out[k] = v
@@ -223,3 +220,15 @@ func MergeUserLabels(existing map[string]string, desired []*ResourceLabel) map[s
 
 	return out
 }
+
+// HiddenLabelKey return true if the label key is considered managed by the platform
+func HiddenLabelKey(key, value string) bool {
+	switch key {
+	case "app", "team", "euthanaisa.nais.io/kill-after":
+		return true
+	case "app.kubernetes.io/managed-by":
+		return value == "console"
+	}
+
+	return strings.Contains(key, "nais.io/")
+}
diff --git a/internal/graph/model/labels_test.go b/internal/graph/model/labels_test.go
new file mode 100644
index 000000000..4cd566e2a
--- /dev/null
+++ b/internal/graph/model/labels_test.go
@@ -0,0 +1,89 @@
+package model_test
+
+import (
+	"testing"
+
+	"github.com/nais/api/internal/graph/model"
+)
+
+func TestHiddenLabelKey(t *testing.T) {
+	tests := []struct {
+		key  string
+		val  string
+		want bool
+	}{
+		{"app", "foo", true},
+		{"team", "bar", true},
+		{"euthanaisa.nais.io/kill-after", "12345", true},
+		{"app.kubernetes.io/managed-by", "console", true},
+		{"app.kubernetes.io/managed-by", "Helm", false},
+		{"my-custom-label", "whatever", false},
+		{"nais.io/something", "any", true},
+	}
+
+	for _, tt := range tests {
+		got := model.HiddenLabelKey(tt.key, tt.val)
+		if got != tt.want {
+			t.Errorf("HiddenLabelKey(%q, %q) = %v; want %v", tt.key, tt.val, got, tt.want)
+		}
+	}
+}
+
+func TestMergeUserLabels(t *testing.T) {
+	existing := map[string]string{
+		"app.kubernetes.io/managed-by": "Helm",
+		"nais.io/managed-by":           "console",
+		"my-custom-key":                "testing",
+	}
+
+	desired := []*model.ResourceLabel{
+		{Key: "another-key", Value: "val"},
+	}
+
+	got := model.MergeUserLabels(existing, desired)
+
+	// Since app.kubernetes.io/managed-by was "Helm" (not "console"), it should be removed!
+	if _, ok := got["app.kubernetes.io/managed-by"]; ok {
+		t.Errorf("expected app.kubernetes.io/managed-by: Helm to be removed, but it was kept")
+	}
+
+	// nais.io/managed-by is reserved/hidden, so it must be kept
+	if val := got["nais.io/managed-by"]; val != "console" {
+		t.Errorf("expected nais.io/managed-by: console to be kept, but got %q", val)
+	}
+
+	// my-custom-key is a user label and not in desired, so it should be removed
+	if _, ok := got["my-custom-key"]; ok {
+		t.Errorf("expected my-custom-key to be removed, but it was kept")
+	}
+
+	// another-key was in desired, so it must be added
+	if val := got["another-key"]; val != "val" {
+		t.Errorf("expected another-key: val, but got %q", val)
+	}
+}
+
+func TestUserLabels(t *testing.T) {
+	labels := map[string]string{
+		"app.kubernetes.io/managed-by": "Helm",
+		"nais.io/managed-by":           "console",
+		"my-custom-key":                "testing",
+	}
+
+	got := model.UserLabels(labels)
+
+	if len(got) != 2 {
+		t.Errorf("expected 2 user labels, got %d", len(got))
+	}
+
+	// app.kubernetes.io/managed-by: Helm should be exposed to user
+	foundHelm := false
+	for _, l := range got {
+		if l.Key == "app.kubernetes.io/managed-by" && l.Value == "Helm" {
+			foundHelm = true
+		}
+	}
+	if !foundHelm {
+		t.Errorf("expected app.kubernetes.io/managed-by: Helm to be returned as user label")
+	}
+}
diff --git a/internal/graph/schema/labels.graphqls b/internal/graph/schema/labels.graphqls
index a837d679d..1efc192c0 100644
--- a/internal/graph/schema/labels.graphqls
+++ b/internal/graph/schema/labels.graphqls
@@ -4,8 +4,6 @@ A user-defined label attached to a resource.
 Labels are key-value pairs that teams can use to organize and filter their
 resources. Both the key and the value may contain letters, numbers, hyphens,
 underscores and dots, and may be at most 63 characters long.
-
-Keys must be prefixed with labels.nais.io/.
 """
 type ResourceLabel {
 	"The label key."
