In this post, I'll share the results of surveying the source code of 13 major open-source projects to answer a question that often comes up when building a Kubernetes Operator: how should you name controllers for multi-word resource types?
When you have a CRD (Custom Resource Definition) type like CertificateRequest or MachineDeployment, should the controller be called foo-bar-controller (hyphenated) or foobar-controller (concatenated lowercase)? There's no official guidance on this. So I dug into real-world, widely used OSS implementations to find out what the de facto standard actually looks like.
What you'll learn from this post
- How multi-word CRD types are actually named across the ecosystem
- The conventions 13 OSS projects use for controller names, finalizers, field managers, and more
- Cross-project trend analysis revealing the most common patterns
- The convention adopted by a Kubernetes SIG project — the closest thing to an "official" standard
Methodology and scope
For this survey, I cloned the repositories of 13 Kubernetes-related OSS projects and examined their naming conventions at the source code level. These findings are based on actual code, not documentation.
The projects surveyed are listed below.
- cert-manager
- Argo CD
- Istio
- Crossplane
- Knative
- Strimzi Kafka Operator
- Prometheus Operator
- Velero
- Tekton Pipelines
- Flux CD
- Cluster API
- KubeVirt
- Kyverno
I looked at six dimensions for each project.
- Go file and directory names
- Controller name strings
- Finalizer names (the mechanism that controls cleanup before a resource is deleted)
- Field manager names (identifiers for field ownership in Server-Side Apply)
- Logger context values
- Reconciler struct names
Master comparison table
Let's start with a side-by-side comparison of all 13 projects.
| Project | Resource Type | Go File / Dir Name | Controller Name | Finalizer | Field Manager | Logger Context | Reconciler Struct |
|---|---|---|---|---|---|---|---|
| cert-manager | CertificateRequest |
certificaterequests/ |
"certificaterequests-issuer-acme" |
"finalizer.acme.cert-manager.io" |
"cert-manager-<component>" |
"certificaterequests-issuer-acme" |
Controller |
| Argo CD | ApplicationSet |
applicationset_controller.go |
"argocd-application-controller" |
"resources-finalizer.argocd.argoproj.io" |
Generic (engine-level) | "applicationset" |
ApplicationSetReconciler |
| Istio | VirtualService |
Custom framework | "istio.io/gateway-controller" |
N/A | "pilot-discovery" |
"KubernetesGateway" |
autoServiceExportController |
| Crossplane | CompositeResourceDefinition |
definition/ |
"defined/compositeresourcedefinition..." |
"defined.apiextensions.crossplane.io" |
"apiextensions.crossplane.io/composite" |
Same as controller name | Reconciler |
| Knative | DomainMapping |
domainmapping/ |
"domainmapping-controller" |
"domainmappings.serving.knative.dev" |
Configurable (no default) | "serving.knative.dev.DomainMapping" |
Reconciler |
| Strimzi | KafkaConnect |
KafkaConnectAssemblyOperator.java |
"KafkaConnect" |
N/A (uses owner refs) | N/A (Java) | "KafkaConnect(ns/name)" |
KafkaConnectAssemblyOperator |
| Prometheus Op | PrometheusAgent |
pkg/prometheus/agent/operator.go |
"prometheusagent-controller" |
"monitoring.coreos.com/status-cleanup" |
"PrometheusOperator" |
"prometheusagent-controller" |
Operator |
| Velero | BackupStorageLocation |
backup_storage_location_controller.go |
"backup-storage-location" |
"velero.io/pod-volume-finalizer" |
"velero-cli" |
"PodVolumeBackup" (mixed) |
backupStorageLocationReconciler |
| Tekton | PipelineRun |
pipelinerun/ |
"PipelineRun" |
"chains.tekton.dev/finalizer" |
Knative defaults | "pipelinerun-reconciler" |
Reconciler |
| Flux CD | HelmRelease |
helmrelease_controller.go |
"helm-controller" |
"finalizers.fluxcd.io" |
"helm-controller" |
Auto (controller-runtime) | HelmReleaseReconciler |
| Cluster API | MachineDeployment |
machinedeployment_controller.go |
"machinedeployment" |
"cluster.x-k8s.io/machinedeployment" |
"capi-machinedeployment" |
"machinedeployment" |
Reconciler |
| KubeVirt | VirtualMachine |
vm/vm.go |
"virt-controller-vm" |
"kubevirt.io/virtualMachineControllerFinalize" |
N/A (no SSA) | N/A (custom) | Controller |
| Kyverno | ClusterCleanupPolicy |
cleanup/controller.go |
"cleanup-controller" |
N/A | "kyverno-{suffix}" |
ControllerLogger(name) |
controller |
Even at a glance, the concatenated lowercase pattern (foobar) stands out. Let's now look at each project in detail.
Project-by-project breakdown
1. cert-manager
cert-manager automates TLS certificate management within Kubernetes clusters. Its multi-word CRD types include CertificateRequest, ClusterIssuer, and CertificateSigningRequest.
| Artifact | Convention | Examples |
|---|---|---|
| Go directory name | Concatenated lowercase |
certificaterequests/, certificatesigningrequests/, clusterissuers/
|
| Controller name | Concatenated lowercase + hyphenated suffix |
"certificaterequests-issuer-acme", "certificaterequests-approver", "clusterissuers"
|
| Finalizer | Domain-based (not resource-specific) |
"finalizer.acme.cert-manager.io", "acme.cert-manager.io/finalizer"
|
| Field manager | Derived from UserAgent |
"cert-manager-<component>" (e.g., "cert-manager-test") |
| Logger context | Same as controller name | logf.FromContext(ctx, "clusterissuers") |
| Reconciler struct |
Controller per package |
Unexported controller or exported Controller
|
The key takeaway here is that the multi-word CRD Kind (CertificateRequest) is transformed into concatenated lowercase (certificaterequests). Hyphens only appear when separating functional suffixes like -issuer-acme or -approver. There is one exception: a directory named certificate-shim/ uses a hyphen.
2. Argo CD
Argo CD enables GitOps-based continuous delivery to Kubernetes. ApplicationSet and AppProject are its multi-word CRD types.
| Artifact | Convention | Examples |
|---|---|---|
| Go file name | Concatenated lowercase + _controller
|
applicationset_controller.go, appcontroller.go
|
| Controller name | Hyphenated |
"argocd-application-controller", "argocd-applicationset-controller"
|
| Finalizer | Hyphenated + domain |
"resources-finalizer.argocd.argoproj.io", "pre-delete-finalizer.argocd.argoproj.io"
|
| Logger context | Concatenated lowercase field key | log.WithField("applicationset", req.NamespacedName) |
| Reconciler struct | PascalCase |
ApplicationSetReconciler, ApplicationController
|
Deployment names use hyphenation (argocd-application-controller), but Go file names and logger keys use concatenated lowercase (applicationset).
3. Istio
Istio provides a service mesh. Its CRD types include VirtualService, DestinationRule, ServiceEntry, and WorkloadEntry.
| Artifact | Convention | Examples |
|---|---|---|
| Go file name | Concatenated lowercase |
autoserviceexportcontroller.go, deploymentcontroller.go, configcontroller.go
|
| Controller name | Domain + hyphenated |
"istio.io/gateway-controller", "istio.io/inference-pool-controller", "istio.io/mesh-controller"
|
| Field manager | Hyphenated |
"pilot-discovery", "istio-operator", "istio.io/inference-pool-controller"
|
| Schema plurals | Concatenated lowercase |
"virtualservices", "destinationrules"
|
| Log collection names | PascalCase |
"KubernetesGateway", "HTTPRoute", "GatewayClasses"
|
| Reconciler struct | camelCase | autoServiceExportController |
Istio uses its own custom controller framework. File names are concatenated lowercase, but controller identifier strings use hyphenation. Log collection names preserve PascalCase as-is, which is also notable.
4. Crossplane
Crossplane manages cloud infrastructure through Kubernetes. It has particularly long CRD type names like CompositeResourceDefinition and ManagedResourceActivationPolicy.
| Artifact | Convention | Examples |
|---|---|---|
| Go directory name | Semantic / abbreviated |
definition/, activationpolicy/, watchoperation/, cronoperation/
|
| Controller name | "prefix/lowercasedKind.group" |
"defined/compositeresourcedefinition.apiextensions.crossplane.io", "mrap/managedresourceactivationpolicy"
|
| Finalizer | Concatenated lowercase as subdomain |
"defined.apiextensions.crossplane.io", "watchoperation.ops.crossplane.io", "composite.apiextensions.crossplane.io"
|
| Field manager | Domain path |
"apiextensions.crossplane.io/composite", "apiextensions.crossplane.io/managed"
|
| Logger context | Controller name value | WithValues("controller", controllerName) |
| Reconciler struct |
Reconciler per package |
Reconciler |
Controller names use the lowercased Kind directly with no separators at all (compositeresourcedefinition, managedresourceactivationpolicy). Finalizers similarly use concatenated lowercase as subdomains.
5. Knative
Knative is a platform for running serverless workloads on Kubernetes. Its multi-word CRD types include DomainMapping, ServerlessService, PodAutoscaler, KnativeServing, and KnativeEventing.
| Artifact | Convention | Examples |
|---|---|---|
| Go directory name | Concatenated lowercase |
domainmapping/, serverlessservice/, knativeserving/, knativeeventing/
|
| Controller name | Concatenated lowercase + -controller
|
"domainmapping-controller", "serverlessservice-controller", "podautoscaler-controller", "knativeserving-controller"
|
| Finalizer | Concatenated lowercase plural + API group |
"domainmappings.serving.knative.dev", "serverlessservices.networking.internal.knative.dev", "knativeservings.operator.knative.dev"
|
| Log Kind | PascalCase | "serving.knative.dev.DomainMapping" |
| Reconciler struct |
Reconciler per package (code-generated) |
reconcilerImpl (generated), Reconciler (user-implemented) |
What makes Knative stand out is that its code generator enforces a consistent pattern. Agent names follow the lowercased-kind-controller format uniformly. This is the most programmatic and consistent approach among all 13 projects.
6. Strimzi Kafka Operator
Strimzi is an Operator for running Apache Kafka on Kubernetes. It is implemented in Java and has CRD types such as KafkaConnect, KafkaMirrorMaker2, KafkaBridge, KafkaRebalance, and KafkaNodePool.
| Artifact | Convention | Examples |
|---|---|---|
| Java class name | PascalCase + AssemblyOperator
|
KafkaConnectAssemblyOperator.java, KafkaMirrorMaker2AssemblyOperator.java
|
| Resource Kind | PascalCase |
"KafkaConnect", "KafkaMirrorMaker2", "KafkaBridge"
|
| Resource plural | Concatenated lowercase |
"kafkaconnects", "kafkamirrormaker2s", "kafkanodepools"
|
| Labels | PascalCase values | "strimzi.io/kind": "KafkaConnect" |
| Lock names | PascalCase Kind | "lock::ns::KafkaConnect::name" |
| Log markers | PascalCase | "KafkaConnect(namespace/name)" |
Strimzi uses PascalCase (the Kind itself) in most runtime contexts. Plural forms are concatenated lowercase (kafkaconnects), following the standard Kubernetes API convention.
7. Prometheus Operator
Prometheus Operator automates Prometheus operations on Kubernetes. Its CRD types include ServiceMonitor, PodMonitor, ThanosRuler, PrometheusAgent, ScrapeConfig, and AlertmanagerConfig.
| Artifact | Convention | Examples |
|---|---|---|
| Go directory / file |
operator.go per package |
pkg/alertmanager/operator.go, pkg/prometheus/agent/operator.go, pkg/thanos/operator.go
|
| Controller name | Concatenated lowercase + -controller
|
"prometheusagent-controller", "alertmanager-controller", "thanos-controller"
|
| Finalizer | Shared (not type-specific) | "monitoring.coreos.com/status-cleanup" |
| Field manager | Shared PascalCase | "PrometheusOperator" |
| managed-by label | Hyphenated | "prometheus-operator" |
| Logger context | Controller name value | "component": "prometheusagent-controller" |
| Reconciler struct |
Operator per package |
alertmanager.Operator, thanos.Operator
|
What's notable here is how multi-word CRD types are concatenated. PrometheusAgent becomes "prometheusagent-controller" — not "prometheus-agent-controller". Additionally, ThanosRuler is shortened to just "thanos-controller".
8. Velero
Velero handles backup and restore for Kubernetes clusters. Its CRD types include BackupStorageLocation, VolumeSnapshotLocation, ServerStatusRequest, PodVolumeBackup, PodVolumeRestore, DataDownload, DataUpload, and DownloadRequest.
| Artifact | Convention | Examples |
|---|---|---|
| Go file name | Snake case |
backup_storage_location_controller.go, pod_volume_backup_controller.go, server_status_request_controller.go
|
| Controller name | Hyphenated (kebab-case) |
"backup-storage-location", "pod-volume-backup", "server-status-request", "data-download"
|
| Finalizer | Hyphenated + domain |
"velero.io/pod-volume-finalizer", "velero.io/data-upload-download-finalizer"
|
| Field manager | Per-binary | "velero-cli" |
| Logger context | Mixed |
"controller": "PodVolumeBackup" (PascalCase) in constructors vs. "controller": "podvolumebackup" (concatenated) in Reconcile |
| Reconciler struct | Mixed export |
backupStorageLocationReconciler (unexported), PodVolumeBackupReconciler (exported) |
Velero is the only project among all 13 that consistently uses hyphenated word splitting for controller names. Each word from the PascalCase type is separated by a hyphen. Its use of snake case for Go file names is another unique trait not seen in any other project.
9. Tekton Pipelines
Tekton is a framework for building CI/CD pipelines on Kubernetes. Its CRD types include PipelineRun, TaskRun, CustomRun, and ResolutionRequest.
| Artifact | Convention | Examples |
|---|---|---|
| Go directory name | Concatenated lowercase |
pipelinerun/, taskrun/, customrun/, resolutionrequest/
|
| Controller name (AgentName) | PascalCase (Kind as-is) |
"PipelineRun", "TaskRun", "CustomRun"
|
| Finalizer | Domain-based |
"chains.tekton.dev/finalizer" (from Tekton Chains) |
| managed-by | Domain path | "tekton.dev/pipeline" |
| Tracer name | Concatenated lowercase + -reconciler
|
"pipelinerun-reconciler", "taskrun-reconciler"
|
| Reconciler struct |
Reconciler per package |
Reconciler (distinguished by package) |
Tekton takes the unique approach of using the PascalCase Kind directly as the controller agent name: "PipelineRun", not "pipelinerun" or "pipeline-run". Directory names and tracer names, on the other hand, use concatenated lowercase.
10. Flux CD
Flux CD is a GitOps toolkit. Its CRD types include GitRepository, HelmRelease, HelmChart, OCIRepository, HelmRepository, and ImageUpdateAutomation.
| Artifact | Convention | Examples |
|---|---|---|
| Go file name | Concatenated lowercase + _controller.go
|
helmrelease_controller.go, gitrepository_controller.go, ocirepository_controller.go, imageupdateautomation_controller.go
|
| Controller name | Per-binary (not per-CRD) |
"source-controller", "helm-controller", "image-automation-controller"
|
| Finalizer | Shared across all types | "finalizers.fluxcd.io" |
| Field manager | Binary name |
"source-controller", "helm-controller"
|
| Reconciler struct | PascalCase + Reconciler |
HelmReleaseReconciler, GitRepositoryReconciler, OCIRepositoryReconciler
|
Flux doesn't assign per-CRD controller names at all. The controller name is simply the binary name (the executable itself). File names use concatenated lowercase.
11. Cluster API (CAPI)
Cluster API is a Kubernetes SIG project for managing cluster lifecycles. It has a large number of multi-word CRD types: MachineDeployment, MachineSet, MachineHealthCheck, ClusterClass, ClusterResourceSet, ClusterResourceSetBinding, MachinePool, and more.
| Artifact | Convention | Examples |
|---|---|---|
| Go file name | Concatenated lowercase + _controller.go
|
machinedeployment_controller.go, machinehealthcheck_controller.go, clusterresourceset_controller.go
|
| Go directory name | Concatenated lowercase |
machinedeployment/, machinehealthcheck/, clusterresourceset/
|
| Controller name | Concatenated lowercase |
"machinedeployment", "machinehealthcheck", "clusterresourceset", "clusterresourcesetbinding"
|
| Event recorder | Concatenated lowercase + -controller
|
"machinedeployment-controller", "machinehealthcheck-controller"
|
| Finalizer | Concatenated lowercase in domain |
"cluster.x-k8s.io/machinedeployment", "machinedeployment.topology.cluster.x-k8s.io", "machinepool.cluster.x-k8s.io"
|
| Field manager (SSA) |
capi- + concatenated lowercase |
"capi-machinedeployment", "capi-machineset"
|
| Logger context | Concatenated lowercase |
WithValues("controller", "machinedeployment"), WithValues("controller", "clusterresourcesetbinding")
|
| SSA cache | Concatenated lowercase | ssa.NewCache("machinedeployment") |
| Reconciler struct |
Reconciler per package |
machinedeployment.Reconciler, machinehealthcheck.Reconciler
|
Cluster API has the most comprehensive and consistent naming convention of all 13 projects. Every single artifact uses concatenated lowercase for the resource type. MachineDeployment becomes machinedeployment everywhere, without exception.
As a Kubernetes SIG project, this convention can be considered the closest thing to an "official" standard.
12. KubeVirt
KubeVirt enables running virtual machines on Kubernetes. It has particularly long CRD type names: VirtualMachine, VirtualMachineInstance, VirtualMachineInstanceReplicaSet, VirtualMachinePool, and VirtualMachineInstanceMigration.
| Artifact | Convention | Examples |
|---|---|---|
| Go directory / file | Abbreviated |
vm/vm.go, vmi/vmi.go, replicaset/replicaset.go, pool/pool.go, migration/migration.go
|
| Work queue name | Abbreviated + hyphenated prefix |
"virt-controller-vm", "virt-controller-vmi", "virt-controller-replicaset"
|
| Event recorder | Concatenated lowercase + -controller
|
"virtualmachine-controller", "virtualmachinereplicaset-controller", "virtualmachinepool-controller"
|
| Finalizer | camelCase |
"kubevirt.io/foregroundDeleteVirtualMachine", "kubevirt.io/virtualMachineControllerFinalize", "kubevirt.io/migrationJobFinalize"
|
| Controller struct |
Controller per package |
vm.Controller, vmi.Controller, migration.Controller
|
KubeVirt uses abbreviations (VM, VMI) aggressively in its internal code, while event recorders use concatenated lowercase (virtualmachine). Its use of camelCase strings for finalizers is unique among all 13 projects.
13. Kyverno
Kyverno is a policy engine for Kubernetes. Its CRD types include ClusterPolicy, ClusterCleanupPolicy, PolicyReport, and GlobalContextEntry.
| Artifact | Convention | Examples |
|---|---|---|
| Go directory name | By functional concern |
cleanup/, policycache/, policystatus/, globalcontext/
|
| Controller name | Hyphenated (concern-based) |
"cleanup-controller", "policycache-controller", "global-context", "admissionpolicy-generator"
|
| Field manager | Prefixed | "kyverno-{suffix}" |
| Logger context | Controller name | logging.ControllerLogger(ControllerName) |
| Reconciler struct | Unexported generic name |
controller (per package) |
What distinguishes Kyverno is that controllers are named by functional concern rather than CRD type. When multi-word concepts appear, it mixes concatenated lowercase (policycache, admissionpolicy) with hyphenation (global-context), resulting in some inconsistency.
Cross-project analysis
Based on the individual findings above, let's now analyze overall trends by artifact type.
Go file and directory names
| Pattern | Projects Using It | Share |
|---|---|---|
Concatenated lowercase (foobar_controller.go, foobar/) |
cert-manager, Argo CD, Knative, Tekton, Flux, Cluster API, Crossplane, Prometheus Op | ~70% |
Snake case (foo_bar_controller.go) |
Velero | ~8% |
Abbreviated (vm.go) |
KubeVirt | ~8% |
By concern (controller.go in a semantic directory) |
Kyverno, Crossplane (partially) | ~15% |
Concatenated lowercase is the overwhelming favorite. FooBar becomes foobar_controller.go or foobar/controller.go.
Controller name strings
| Pattern | Projects Using It | Examples |
|---|---|---|
Concatenated lowercase (bare or + -controller) |
Cluster API, Knative, Prometheus Op, cert-manager, Crossplane |
"machinedeployment", "domainmapping-controller", "prometheusagent-controller"
|
| Hyphenated (word-split) | Velero |
"backup-storage-location", "pod-volume-backup"
|
| PascalCase (Kind as-is) | Tekton, Strimzi, Flux (logger only) |
"PipelineRun", "KafkaConnect"
|
| Binary name (not per-CRD) | Flux, Istio |
"helm-controller", "pilot-discovery"
|
| By concern | Kyverno | "cleanup-controller" |
Again, concatenated lowercase is the most common. FooBar becomes "foobar" or "foobar-controller".
Finalizer names
| Pattern | Projects Using It | Examples |
|---|---|---|
| Concatenated lowercase in domain | Cluster API, Knative, Crossplane |
"cluster.x-k8s.io/machinedeployment", "domainmappings.serving.knative.dev"
|
| Shared / generic | Flux, Prometheus Op, cert-manager |
"finalizers.fluxcd.io", "monitoring.coreos.com/status-cleanup"
|
| Hyphenated | Velero | "velero.io/pod-volume-finalizer" |
| camelCase | KubeVirt | "kubevirt.io/virtualMachineControllerFinalize" |
Field manager names
| Pattern | Projects Using It | Examples |
|---|---|---|
| Prefix + concatenated lowercase | Cluster API | "capi-machinedeployment" |
| Binary name | Flux, Istio |
"source-controller", "pilot-discovery"
|
| Shared operator name | Prometheus Op | "PrometheusOperator" |
| Domain path | Crossplane | "apiextensions.crossplane.io/composite" |
Reconciler struct names
| Pattern | Projects Using It | Share |
|---|---|---|
Generic Reconciler per package |
Cluster API, Knative, Tekton, Crossplane | ~40% |
PascalCase FooBarReconciler
|
Flux, Argo CD | ~20% |
Operator per package |
Prometheus Op | ~8% |
Controller per package |
KubeVirt, cert-manager | ~15% |
Unexported controller
|
Kyverno | ~8% |
Conclusions
The dominant convention: concatenated lowercase
The clear winner across all naming dimensions is concatenated lowercase — lowercasing the PascalCase Kind with no separator.
FooBar → foobar
Here's a breakdown of adoption rates for this pattern.
- ~70% of projects use it for file/directory names
- ~50% of projects use it for controller name strings
- ~40% of projects use it for finalizer domain components
- 100% of projects use it for Kubernetes API resource plurals (this is a Kubernetes API standard itself)
Who uses hyphenation?
Only Velero consistently word-splits CRD types with hyphens ("backup-storage-location", "pod-volume-backup") — that's 1 out of 13 projects.
Kyverno partially uses hyphens ("global-context"), but also mixes in concatenated lowercase (policycache, admissionpolicy), so it is not consistent.
The -controller suffix
When appending a -controller suffix to a name, every single project connects it with a hyphen. It's always "foobar-controller", never "foobarcontroller". This is universal.
Recommended naming convention
Based on the survey results, here is a recommended set of conventions. The Cluster API conventions (a Kubernetes SIG project) are particularly worth following, as they are the most comprehensive and consistent of any project surveyed.
| Artifact | Recommended Convention | Example for FooBar
|
|---|---|---|
| Go file name | Concatenated lowercase + underscore + controller.go
|
foobar_controller.go |
| Go directory | Concatenated lowercase | foobar/ |
| Controller name | Concatenated lowercase | "foobar" |
| Controller name (with suffix) | Concatenated lowercase + hyphen + controller
|
"foobar-controller" |
| Finalizer | Concatenated lowercase in domain |
"example.io/foobar" or "foobar.example.io"
|
| Field manager | Prefix + concatenated lowercase | "myoperator-foobar" |
| Logger context | Concatenated lowercase | "foobar" |
| Reconciler struct | PascalCase or generic per package |
FooBarReconciler or Reconciler
|
Why concatenated lowercase wins
There are several reasons why concatenated lowercase is so widely adopted.
First, it aligns with the Kubernetes API's own conventions. The standard resource plurals are already concatenated lowercase: deployments, replicasets, statefulsets, daemonsets. CRD resources naturally follow this same pattern.
Second, it fits well with Go package naming conventions. Go packages are conventionally single lowercase words, and a name like machinedeployment fits right in.
Third, it eliminates ambiguity. With hyphenation, splitting FooBar into foo-bar is straightforward, but FooBarBaz could plausibly be foo-bar-baz or foo-barbaz — the word boundaries aren't always obvious. With concatenated lowercase, you simply lowercase the Kind as-is. It's a mechanical transformation with no room for interpretation.
And perhaps the most compelling reason: Cluster API, a Kubernetes SIG project, uses this convention with complete consistency. If you want to follow the crowd in the Kubernetes ecosystem, this is the convention to pick.
Hyphenation has its merits too
That said, hyphenation has legitimate advantages.
There's no denying that backup-storage-location is easier for humans to read than backupstoragelocation. In contexts where humans read names directly — deployment names, CLI output, log messages — readability is a significant benefit.
Additionally, hyphenated names are the standard convention in Kubernetes label values, annotation keys, and resource names in manifests.
In other words, it comes down to a choice: concatenated lowercase for consistency with APIs and code, or hyphenation for human readability. Neither is wrong — but the ecosystem has spoken, and concatenated lowercase is the overwhelming favorite.
Final thoughts
In this post, I surveyed the source code of 13 Kubernetes-related OSS projects and mapped out the real-world naming conventions for multi-word CRD types. The verdict: concatenated lowercase (foobar) is the de facto standard, with Velero being the only project that consistently uses hyphenation (foo-bar).
If you're building a Kubernetes Operator and unsure which convention to follow, Cluster API's approach is a safe bet. That said, there's no absolute right answer here. The most pragmatic approach is to prioritize consistency within your own project and go with whatever your team finds readable.
Thanks for reading to the end! I tweet about tech topics that don't make it into my blog posts, so feel free to follow me if you're interested → Twitter@suin
Top comments (0)