Organising Kubernetes resources with Helm-driven metadata
api can coexist in different namespaceskubectl get namespaces
# NAME STATUS AGE
# default Active 42d
# kube-system Active 42d
# kube-public Active 42d
# kube-node-lease Active 42d
Helm sets the namespace at install time — every resource rendered receives it automatically.
# Install into a specific namespace
helm install my-app ./mychart -n staging --create-namespace
Reference the release namespace inside templates:
# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-config
namespace: {{ .Release.Namespace }}
data:
env: {{ .Release.Namespace }}
namespace: in templates — Helm injects it. Use {{ .Release.Namespace }} only when a resource must reference its own namespace as data.helm install app ./chart \
-n prod --create-namespace
Simple. Works for most cases.
# templates/namespace.yaml
{{- if .Values.namespace.create }}
apiVersion: v1
kind: Namespace
metadata:
name: {{ .Release.Namespace }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
{{- end }}
Useful when the namespace needs labels, annotations, or quotas managed by the chart.
--create-namespace, both attempts succeed idempotently — but consider documenting the intended approach.Labels are key/value pairs attached to objects — they power selectors used by Services, Deployments, and tooling.
metadata:
labels:
app.kubernetes.io/name: myapp
app.kubernetes.io/instance: myapp-staging
app.kubernetes.io/version: "1.4.0"
app.kubernetes.io/component: frontend
app.kubernetes.io/part-of: ecommerce
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/* common label schema. It is understood by kubectl, dashboards, and service meshes.metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
Services route traffic by matching pod labels. Deployments manage pods via matchLabels.
# templates/deployment.yaml
spec:
selector:
matchLabels:
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
---
# templates/service.yaml
spec:
selector:
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
selector.matchLabels is immutable after creation. Changing it requires deleting and recreating the Deployment.Centralise labels in _helpers.tpl so every template stays consistent:
# templates/_helpers.tpl
{{- define "mychart.labels" -}}
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ include "mychart.chart" . }}
{{- end }}
Pair namespaces with ResourceQuotas to enforce limits:
# templates/resourcequota.yaml
{{- if .Values.resourceQuota.enabled }}
apiVersion: v1
kind: ResourceQuota
metadata:
name: {{ .Release.Name }}-quota
namespace: {{ .Release.Namespace }}
spec:
hard:
requests.cpu: {{ .Values.resourceQuota.cpu | quote }}
requests.memory: {{ .Values.resourceQuota.memory | quote }}
pods: {{ .Values.resourceQuota.pods | quote }}
{{- end }}