Single Group to Multi-Group
Kubebuilder scaffolds single-group projects by default to keep things simple, as most projects don’t require multiple API groups. However, you can convert an existing single-group project to use multi-group layout when needed. This reorganizes your APIs and controllers into group-specific directories.
See the design doc for the rationale behind this design decision.
Understanding the Layouts
Here’s what changes when you go from single-group to multi-group:
Single-group layout (default):
api/<version>/*_types.go All your CRD schemas in one place
internal/controller/* All your controllers together
internal/webhook/<version>/* Webhooks organized by version (if you have any)
Multi-group layout:
api/<group>/<version>/*_types.go CRD schemas organized by group
internal/controller/<group>/* Controllers organized by group
internal/webhook/<group>/<version>/* Webhooks organized by group and version (if you have any)
You can tell which layout you’re using by checking your PROJECT file for multigroup: true.
Migration Steps
The following steps migrate the CronJob example from single-group to multi-group layout.
Step 1: Enable multi-group mode
First, tell Kubebuilder you want to use multi-group layout:
kubebuilder edit --multigroup=true
This command updates your PROJECT file by adding multigroup: true. After this change:
- New APIs you create will automatically use the multi-group structure (
api/<group>/<version>/) - Existing APIs remain in their current location and must be migrated manually (steps 3-9 below)
Step 2: Identify your group name
Check api/v1/groupversion_info.go to find your group name:
// +groupName=batch.tutorial.kubebuilder.io
package v1
The group name is the first part before the dot (batch in this example).
Step 3: Move your APIs
Create a directory for your group and move your version directories:
mkdir -p api/batch
mv api/v1 api/batch/
If you have multiple versions (like v1, v2, etc.), move them all:
mv api/v2 api/batch/
Step 4: Move your controllers
Create a group directory and move all controller files:
mkdir -p internal/controller/batch
mv internal/controller/*.go internal/controller/batch/
This will move all your controller files, including suite_test.go, into the group directory. Each group needs its own test suite.
Step 5: Move your webhooks (if you have any)
If your project has webhooks (check for an internal/webhook/ directory), add the group directory:
mkdir -p internal/webhook/batch
mv internal/webhook/v1 internal/webhook/batch/
mv internal/webhook/v2 internal/webhook/batch/ # if v2 exists
If you don’t have webhooks, skip this step.
Step 6: Update import paths
Update all import statements to point to the new locations.
What used to look like this:
import (
batchv1 "tutorial.kubebuilder.io/project/api/v1"
"tutorial.kubebuilder.io/project/internal/controller"
)
Should now look like this:
import (
batchv1 "tutorial.kubebuilder.io/project/api/batch/v1"
batchcontroller "tutorial.kubebuilder.io/project/internal/controller/batch"
)
If you have webhooks, you’ll also need to update those imports:
// Before
webhookv1 "tutorial.kubebuilder.io/project/internal/webhook/v1"
// After
webhookbatchv1 "tutorial.kubebuilder.io/project/internal/webhook/batch/v1"
Files to check and update:
cmd/main.gointernal/controller/batch/*.gointernal/webhook/batch/v1/*.go(if you have webhooks)api/batch/v1/*_test.go
Tip: Use your IDE’s “Find and Replace” feature across the project.
Step 7: Update the PROJECT file
The kubebuilder edit --multigroup=true command sets multigroup: true in your PROJECT file but doesn’t update paths for existing APIs. You need to manually update the path field for each resource.
Verify your PROJECT file has these changes:
- Check that
multigroup: trueis set (at the top level):
layout:
- go.kubebuilder.io/v4
multigroup: true # Must be true
projectName: project
- Update the
pathfield for each resource:
Before:
resources:
- api:
crdVersion: v1
namespaced: true
controller: true
group: batch
kind: CronJob
path: tutorial.kubebuilder.io/project/api/v1 # Old path
version: v1
After:
resources:
- api:
crdVersion: v1
namespaced: true
controller: true
group: batch
kind: CronJob
path: tutorial.kubebuilder.io/project/api/batch/v1 # New path with group
version: v1
Repeat this for all resources in your PROJECT file.
Step 8: Update test suite CRD paths
Update the CRD directory path in test suites. Since files moved one level deeper, add one more ".." to the path.
In internal/controller/batch/suite_test.go:
Before (was at internal/controller/suite_test.go):
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
}
After (now at internal/controller/batch/suite_test.go):
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")},
}
If you have webhooks, update internal/webhook/batch/v1/webhook_suite_test.go:
Before (was at internal/webhook/v1/webhook_suite_test.go):
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")},
}
After (now at internal/webhook/batch/v1/webhook_suite_test.go):
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "config", "crd", "bases")},
}
Step 9: Verify the migration
Run the following commands to verify everything works:
make manifests # Regenerate CRDs and RBAC
make generate # Regenerate code
make test # Run tests
make build # Build the project
AI-Assisted Migration
If you’re using an AI coding assistant (Cursor, GitHub Copilot, etc.), you can automate most of the migration steps.