docs/book/src/reference/multiple-controllers.md
Kubebuilder supports multiple named controllers for the same API resource. This allows different reconciliation logic for the same resource type.
kubebuilder create api --group crew --version v1 --kind Captain \
--resource=true \
--controller=true \
--controller-name=captain
Creates:
api/v1/captain_types.gointernal/controller/captain_controller.go with struct CaptainReconcilercmd/main.gokubebuilder create api --group crew --version v1 --kind Captain \
--resource=false \
--controller=true \
--controller-name=captain-backup
Creates:
internal/controller/captain_backup_controller.go with struct CaptainBackupReconcilercmd/main.goThe API is only created once. Additional controllers reference the existing API.
resources:
- api:
crdVersion: v1
controller: true
group: crew
kind: Captain
version: v1
resources:
- api:
crdVersion: v1
controllers:
- name: captain
- name: captain-backup
group: crew
kind: Captain
version: v1
When both formats exist on the same resource (due to manual editing), the controllers: array takes precedence and controller: true is automatically cleared.
Controller names are stored in the PROJECT file exactly as provided by the user.
Names are normalized for Go code:
captain-backup → captain_backup_controller.gocaptain-backup → CaptainBackupReconcilerNamed("captain-backup")Named("crew-captain-backup")captain-backup and captainbackup)Multiple controllers for the same resource require coordination to avoid conflicts:
{controller-name}.example.com/finalizerKubebuilder scaffolds the controllers but does not manage coordination between them.
Existing projects with controller: true continue to work unchanged. When adding named controllers to a resource with controller: true, Kubebuilder automatically migrates the format:
Before migration:
resources:
- api:
crdVersion: v1
controller: true
group: crew
kind: Captain
version: v1
After adding a named controller:
kubebuilder create api --group crew --version v1 --kind Captain \
--resource=false \
--controller=true \
--controller-name=captain-backup
Result:
resources:
- api:
crdVersion: v1
controllers:
- name: captain
- name: captain-backup
group: crew
kind: Captain
version: v1
The original unnamed controller is assigned the name captain (lowercase kind) and the controller: true flag is automatically cleared. This maintains backward compatibility while enabling multiple controllers.
"duplicate controller name": Two controllers have the same name. Use unique names.
"conflicts with ... both normalize to": Different names generate the same struct name. Choose distinct names.
"controller with name ... already exists": Controller already exists for this resource. Use a different name.