Repository: furiko-io/furiko
Pull request #37 (2022-04-08 19:43:21)

chore: Update development docs for make deploy
Adds more documentation in `DEVELOPMENT.md` to explain `make deploy`.

Also updates the `make deploy` step to support custom `IMAGE_TAG`.


Patch:
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index 2ea255a..d8b9837 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -126,13 +126,42 @@ We employ `go fmt` and `goimports` applied to all Go code, including generated c
 
 ## Testing
 
+### Unit Tests
+
 As much as possible, write unit tests to verify the correctness of the changes that you make. We use [Codecov](https://codecov.io/) to track and visualize code coverage over time.
 
-We currently don't have a testing/staging enviroment for validating changes and performing regression/end-to-end tests. As such, please perform integration tests manually for now.
+### Testing Locally
+
+Some complex changes or addition of new features may require testing that goes beyond unit tests that we can write. In such a case, please run your code locally and demonstrate that it works with a Kubernetes cluster, such as via a screenshot, video capture, or logs in your pull request.
+
+See [Running the Code Locally](#running-the-code-locally) for more information.
+
+### Testing Against a Real Cluster
+
+If you want to test with your code actually running in the Kubernetes cluster (e.g. for testing interaction with webhooks), you will need to be able to package your local changes as a container image, and be able to pull them in your testing cluster.
+
+If you are using `kind`, you can refer to their guide on [how to set up a local registry](https://kind.sigs.k8s.io/docs/user/local-registry/).
+
+The `Makefile` supports deploying all components to your configured Kubernetes cluster for convenience. Assuming you have set up a local registry, and you have built your images using the `dev` image tag, you can deploy all components with one line:
+
+```sh
+IMAGE_TAG=dev make deploy
+```
+
+This will generate YAMLs and immediately apply them with `kubectl apply`, using the `dev` Docker image tag for all components.
+
+Some possible values for `IMAGE_TAG`:
 
-If you are experienced or wish to explore setting up of testing infra, we more than welcome your contributions :)
+- `latest` (default): Points to the current latest stable release of Furiko.
+- `snapshot`: Points to the latest `HEAD` of Furiko (i.e. HEAD of `main`).
 
-## Release Flow
+### Staging Environment
+
+We currently don't have a staging enviroment for validating changes and performing regression/end-to-end tests. If you are experienced or wish to explore setting up of testing infra, we more than welcome your contributions :)
+
+## Releases
+
+### Official Releases
 
 Releases are determined by Git tags, and will be published primarily via the [Releases page](https://github.com/furiko-io/furiko/releases).
 
@@ -148,3 +177,17 @@ Finally, releases are minimally expected to contain the following artifacts, dow
 2. Combined YAML manifest, for installation [as described on the website](https://furiko.io/guide/setup/install/#from-yaml)
 
 When we introduce additional components (i.e. Federation and Telemetry), release workflows (and this guide) may be further revised.
+
+### Snapshot Releases
+
+We also publish a snapshot of the current `HEAD` of the `main` branch, which will help facilitate testing and allow users to install the latest cutting-edge version if desired.
+
+Docker images are pushed with the `snapshot` tags to denote these snapshot releases, and are built from the `main` branch. Additional tags like `v0.1.2-next` also will be pushed, which denotes the next planned version's snapshot.
+
+To install the current snapshot release via Kustomize, you can run the following:
+
+```sh
+IMAGE_TAG=snapshot make deploy
+```
+
+For more information, see [Testing Against a Real Cluster](#testing-against-a-real-cluster)
diff --git a/Makefile b/Makefile
index 1359f94..8000735 100644
--- a/Makefile
+++ b/Makefile
@@ -140,8 +140,8 @@ uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster. Call with
 	$(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
 
 .PHONY: deploy
-deploy: manifests kustomize $(KUSTOMIZE_DEST) ## Deploys snapshot version of all components to the K8s cluster. Useful for testing current HEAD.
-	DEST_DIR=$(KUSTOMIZE_DEST) ./hack/generate-kustomization.sh "$(IMAGE_NAME_PREFIX)" "snapshot"
+deploy: manifests kustomize $(KUSTOMIZE_DEST) ## Deploys snapshot version of all components to the K8s cluster. Use the IMAGE_TAG environment variable to override the image to be deployed.
+	DEST_DIR=$(KUSTOMIZE_DEST) ./hack/generate-kustomization.sh "$(IMAGE_NAME_PREFIX)" "$(IMAGE_TAG)"
 	$(KUSTOMIZE) build $(KUSTOMIZE_DEST) | kubectl apply -f -
 
 .PHONY: undeploy
diff --git a/apis/config/v1alpha1/configurationname_types.go b/apis/config/v1/configurationname_types.go
similarity index 67%
rename from apis/config/v1alpha1/configurationname_types.go
rename to apis/config/v1/configurationname_types.go
index b030597..608b264 100644
--- a/apis/config/v1alpha1/configurationname_types.go
+++ b/apis/config/v1/configurationname_types.go
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-package v1alpha1
+package v1
 
-// ConfigName refers to the name of a dynamic configuration.
+// ConfigName denotes a named configuration, and refers to the name in the
+// ConfigMap configuration.
 type ConfigName string
 
 const (
-	// JobExecutionConfigName refers to JobExecutionConfig.
-	JobExecutionConfigName ConfigName = "execution-job"
-
-	// CronExecutionConfigName refers to CronExecutionConfig.
-	CronExecutionConfigName ConfigName = "execution-cron"
+	ConfigNameJobController  ConfigName = "job-controller"
+	ConfigNameCronController ConfigName = "cron-controller"
 )
diff --git a/apis/config/v1alpha1/doc.go b/apis/config/v1/doc.go
similarity index 97%
rename from apis/config/v1alpha1/doc.go
rename to apis/config/v1/doc.go
index 16bd464..3427825 100644
--- a/apis/config/v1alpha1/doc.go
+++ b/apis/config/v1/doc.go
@@ -17,4 +17,4 @@
 // Package v1 contains API Schema definitions for the config v1 API group
 // +kubebuilder:object:generate=true
 // +groupName=config.furiko.io
-package v1alpha1
+package v1
diff --git a/apis/config/v1alpha1/dynamicconfig_types.go b/apis/config/v1/dynamicconfig_types.go
similarity index 94%
rename from apis/config/v1alpha1/dynamicconfig_types.go
rename to apis/config/v1/dynamicconfig_types.go
index e9dc1e1..48b2f71 100644
--- a/apis/config/v1alpha1/dynamicconfig_types.go
+++ b/apis/config/v1/dynamicconfig_types.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package v1alpha1
+package v1
 
 import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -23,8 +23,8 @@ import (
 // +kubebuilder:object:root=true
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
-// JobExecutionConfig defines runtime global configuration for Jobs in the cluster.
-type JobExecutionConfig struct {
+// JobControllerConfig defines runtime configuration for the Job controller.
+type JobControllerConfig struct {
 	metav1.TypeMeta `json:",inline"`
 
 	// DefaultTTLSecondsAfterFinished is the default time-to-live (TTL) for a Job
@@ -64,8 +64,8 @@ type JobExecutionConfig struct {
 // +kubebuilder:object:root=true
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
-// CronExecutionConfig defines runtime global configuration for Cron in the cluster.
-type CronExecutionConfig struct {
+// CronControllerConfig defines runtime configuration for the Cron controller.
+type CronControllerConfig struct {
 	metav1.TypeMeta `json:",inline"`
 
 	// CronFormat specifies the format used to parse cron expressions. Select
@@ -147,5 +147,5 @@ type CronExecutionConfig struct {
 }
 
 func init() {
-	SchemeBuilder.Register(&JobExecutionConfig{}, &CronExecutionConfig{})
+	SchemeBuilder.Register(&JobControllerConfig{}, &CronControllerConfig{})
 }
diff --git a/apis/config/v1alpha1/groupversion_info.go b/apis/config/v1/groupversion_info.go
similarity index 98%
rename from apis/config/v1alpha1/groupversion_info.go
rename to apis/config/v1/groupversion_info.go
index 71227d6..0828e7f 100644
--- a/apis/config/v1alpha1/groupversion_info.go
+++ b/apis/config/v1/groupversion_info.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package v1alpha1
+package v1
 
 import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
diff --git a/apis/config/v1alpha1/managerconfig_types.go b/apis/config/v1/managerconfig_types.go
similarity index 99%
rename from apis/config/v1alpha1/managerconfig_types.go
rename to apis/config/v1/managerconfig_types.go
index da3bccd..a710668 100644
--- a/apis/config/v1alpha1/managerconfig_types.go
+++ b/apis/config/v1/managerconfig_types.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package v1alpha1
+package v1
 
 import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/apis/config/v1alpha1/webhookconfig_types.go b/apis/config/v1/webhookconfig_types.go
similarity index 99%
rename from apis/config/v1alpha1/webhookconfig_types.go
rename to apis/config/v1/webhookconfig_types.go
index d33b308..261b9e2 100644
--- a/apis/config/v1alpha1/webhookconfig_types.go
+++ b/apis/config/v1/webhookconfig_types.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package v1alpha1
+package v1
 
 import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/apis/config/v1alpha1/zz_generated.deepcopy.go b/apis/config/v1/zz_generated.deepcopy.go
similarity index 95%
rename from apis/config/v1alpha1/zz_generated.deepcopy.go
rename to apis/config/v1/zz_generated.deepcopy.go
index 19eb58d..dc253ff 100644
--- a/apis/config/v1alpha1/zz_generated.deepcopy.go
+++ b/apis/config/v1/zz_generated.deepcopy.go
@@ -19,7 +19,7 @@
 
 // Code generated by controller-gen. DO NOT EDIT.
 
-package v1alpha1
+package v1
 
 import (
 	runtime "k8s.io/apimachinery/pkg/runtime"
@@ -87,7 +87,7 @@ func (in *ControllerManagerConfigSpec) DeepCopy() *ControllerManagerConfigSpec {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *CronExecutionConfig) DeepCopyInto(out *CronExecutionConfig) {
+func (in *CronControllerConfig) DeepCopyInto(out *CronControllerConfig) {
 	*out = *in
 	out.TypeMeta = in.TypeMeta
 	if in.CronHashNames != nil {
@@ -117,18 +117,18 @@ func (in *CronExecutionConfig) DeepCopyInto(out *CronExecutionConfig) {
 	}
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronExecutionConfig.
-func (in *CronExecutionConfig) DeepCopy() *CronExecutionConfig {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronControllerConfig.
+func (in *CronControllerConfig) DeepCopy() *CronControllerConfig {
 	if in == nil {
 		return nil
 	}
-	out := new(CronExecutionConfig)
+	out := new(CronControllerConfig)
 	in.DeepCopyInto(out)
 	return out
 }
 
 // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *CronExecutionConfig) DeepCopyObject() runtime.Object {
+func (in *CronControllerConfig) DeepCopyObject() runtime.Object {
 	if c := in.DeepCopy(); c != nil {
 		return c
 	}
@@ -302,7 +302,7 @@ func (in *HealthSpec) DeepCopy() *HealthSpec {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *JobExecutionConfig) DeepCopyInto(out *JobExecutionConfig) {
+func (in *JobControllerConfig) DeepCopyInto(out *JobControllerConfig) {
 	*out = *in
 	out.TypeMeta = in.TypeMeta
 	if in.DefaultTTLSecondsAfterFinished != nil {
@@ -327,18 +327,18 @@ func (in *JobExecutionConfig) DeepCopyInto(out *JobExecutionConfig) {
 	}
 }
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobExecutionConfig.
-func (in *JobExecutionConfig) DeepCopy() *JobExecutionConfig {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobControllerConfig.
+func (in *JobControllerConfig) DeepCopy() *JobControllerConfig {
 	if in == nil {
 		return nil
 	}
-	out := new(JobExecutionConfig)
+	out := new(JobControllerConfig)
 	in.DeepCopyInto(out)
 	return out
 }
 
 // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *JobExecutionConfig) DeepCopyObject() runtime.Object {
+func (in *JobControllerConfig) DeepCopyObject() runtime.Object {
 	if c := in.DeepCopy(); c != nil {
 		return c
 	}
diff --git a/cmd/execution-controller/main.go b/cmd/execution-controller/main.go
index 4b17bbb..09e6e1e 100644
--- a/cmd/execution-controller/main.go
+++ b/cmd/execution-controller/main.go
@@ -25,7 +25,7 @@ import (
 	"k8s.io/klog/v2"
 	ctrl "sigs.k8s.io/controller-runtime"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/bootstrap"
 	"github.com/furiko-io/furiko/pkg/execution/controllers/croncontroller"
 	"github.com/furiko-io/furiko/pkg/execution/controllers/jobconfigcontroller"
@@ -69,7 +69,7 @@ func main() {
 	// ConfigMap or some dynamic configuration source.
 	//
 	// Changes will not take effect until the server is restarted.
-	var options configv1alpha1.ExecutionControllerConfig
+	var options configv1.ExecutionControllerConfig
 	if configFile != "" {
 		klog.Infof("loading configuration from %v", configFile)
 		if err := bootstrap.UnmarshalFromFile(configFile, &options); err != nil {
@@ -122,7 +122,7 @@ func main() {
 	for _, factory := range GetControllerFactories() {
 		concurrencySpec := options.ControllerConcurrency
 		if concurrencySpec == nil {
-			concurrencySpec = &configv1alpha1.ExecutionControllerConcurrencySpec{}
+			concurrencySpec = &configv1.ExecutionControllerConcurrencySpec{}
 		}
 		klog.Infof("setting up controller %v", factory.Name())
 		controller, err := factory.New(ctrlContext, concurrencySpec)
@@ -163,7 +163,7 @@ func main() {
 type ControllerFactory interface {
 	Name() string
 	New(ctx controllercontext.ContextInterface,
-		concurrency *configv1alpha1.ExecutionControllerConcurrencySpec) (controllermanager.Controller, error)
+		concurrency *configv1.ExecutionControllerConcurrencySpec) (controllermanager.Controller, error)
 }
 
 // GetControllerFactories returns a list of ControllerFactory implementations
diff --git a/cmd/execution-webhook/main.go b/cmd/execution-webhook/main.go
index cb60433..976cc78 100644
--- a/cmd/execution-webhook/main.go
+++ b/cmd/execution-webhook/main.go
@@ -25,7 +25,7 @@ import (
 	"k8s.io/klog/v2"
 	ctrl "sigs.k8s.io/controller-runtime"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/bootstrap"
 	"github.com/furiko-io/furiko/pkg/execution/webhooks/jobconfigmutatingwebhook"
 	"github.com/furiko-io/furiko/pkg/execution/webhooks/jobconfigvalidatingwebhook"
@@ -65,7 +65,7 @@ func main() {
 	// ConfigMap or some dynamic configuration source.
 	//
 	// Changes will not take effect until the server is restarted.
-	var options configv1alpha1.ExecutionWebhookConfig
+	var options configv1.ExecutionWebhookConfig
 	if configFile != "" {
 		klog.Infof("loading configuration from %v", configFile)
 		if err := bootstrap.UnmarshalFromFile(configFile, &options); err != nil {
diff --git a/config/execution/controller/config.yaml b/config/execution/controller/config.yaml
index 8af8f2f..67b3caa 100644
--- a/config/execution/controller/config.yaml
+++ b/config/execution/controller/config.yaml
@@ -2,7 +2,7 @@
 # These values are used for bootstrapping of the controller manager at startup,
 # so changing any values here require a restart of execution-controller in order
 # to take effect.
-apiVersion: config.furiko.io/v1alpha1
+apiVersion: config.furiko.io/v1
 kind: ExecutionControllerConfig
 
 # defaultResync controls the default resync duration.
diff --git a/config/execution/dynamic/config.yaml b/config/execution/dynamic/config.yaml
index 5c87f4f..fd06c57 100644
--- a/config/execution/dynamic/config.yaml
+++ b/config/execution/dynamic/config.yaml
@@ -1,41 +1,36 @@
+# Here we define the dynamic config for execution-controller.
+# We can tune several knobs in execution-controller without requiring a restart.
+# Each file in the ConfigMap defines a group of configuration knobs available for
+# the user to tweak as desired. As a start, we have populated a set of sane default
+# values for you.
 apiVersion: v1
 kind: ConfigMap
 metadata:
   name: dynamic-config
 data:
-  execution-job: |
-    # Here we define the dynamic config for execution-controller without requiring
-    # a restart. This config file configures dynamic configuration for Jobs.
-    apiVersion: config.furiko.io/v1alpha1
-    kind: JobExecutionConfig
-
+  job-controller: |
     # defaultTTLSecondsAfterFinished is the default time-to-live (TTL) for a Job
     # after it has finished. Lower this value to reduce the strain on the
-    # cluster/kubelet. Set to 0 to delete immediately after the Job is finished.
+    # cluster/kubelet. This field can be set separately on each Job as well.
     defaultTTLSecondsAfterFinished: 3600
 
     # defaultPendingTimeoutSeconds is default timeout to use if job does not
     # specify the pending timeout. By default, this is a non-zero value to prevent
-    # permanently stuck jobs. To disable default pending timeout, set this to 0.
+    # permanently stuck jobs. To disable default pending timeout, set this to 0 or
+    # a negative number.
     defaultPendingTimeoutSeconds: 900
 
     # deleteKillingTasksTimeoutSeconds is the duration we delete the task to kill
     # it instead of using active deadline, if previous efforts were ineffective.
-    # Set this value to 0 to immediately use deletion.
     deleteKillingTasksTimeoutSeconds: 180
 
     # forceDeleteKillingTasksTimeoutSeconds is the duration before we use force
     # deletion instead of normal deletion. This timeout is computed from the
     # deletionTimestamp of the object, which may also include an additional delay
-    # of deletionGracePeriodSeconds. Set this value to 0 to disable force deletion.
+    # of deletionGracePeriodSeconds.
     forceDeleteKillingTasksTimeoutSeconds: 120
 
-  execution-cron: |
-    # Here we define the dynamic config for execution-controller without requiring
-    # a restart. This config file configures dynamic configuration for Cron.
-    apiVersion: config.furiko.io/v1alpha1
-    kind: CronExecutionConfig
-
+  cron-controller: |
     # cronFormat specifies the format used to parse cron expressions. Select
     # between "standard" (default) or "quartz".
     cronFormat: "standard"
@@ -81,8 +76,7 @@ data:
     # maxMissedSchedules defines a maximum number of jobs that the controller
     # should back-schedule, or attempt to create after coming back up from
     # downtime. Having a sane value here would prevent a thundering herd of jobs
-    # being scheduled that would exhaust resources in the cluster. Set this to 0 to
-    # disable back-scheduling.
+    # being scheduled that would exhaust resources in the cluster.
     maxMissedSchedules: 5
 
     # maxDowntimeThresholdSeconds defines the maximum downtime that the controller
diff --git a/config/execution/webhook/config.yaml b/config/execution/webhook/config.yaml
index d926271..18a9327 100644
--- a/config/execution/webhook/config.yaml
+++ b/config/execution/webhook/config.yaml
@@ -2,7 +2,7 @@
 # These values are used for bootstrapping of the webhook server at startup,
 # so changing any values here require a restart of execution-webhook in order
 # to take effect.
-apiVersion: config.furiko.io/v1alpha1
+apiVersion: config.furiko.io/v1
 kind: ExecutionWebhookConfig
 
 # defaultResync controls the default resync duration.
diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go
index 34d875b..03db3ce 100644
--- a/pkg/config/defaults.go
+++ b/pkg/config/defaults.go
@@ -19,18 +19,18 @@ package config
 import (
 	"k8s.io/utils/pointer"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 )
 
 var (
-	DefaultJobControllerConfig = &configv1alpha1.JobExecutionConfig{
+	DefaultJobControllerConfig = &configv1.JobControllerConfig{
 		DefaultTTLSecondsAfterFinished:        pointer.Int64(3600),
 		DefaultPendingTimeoutSeconds:          pointer.Int64(900),
 		DeleteKillingTasksTimeoutSeconds:      pointer.Int64(180),
 		ForceDeleteKillingTasksTimeoutSeconds: pointer.Int64(120),
 	}
 
-	DefaultCronControllerConfig = &configv1alpha1.CronExecutionConfig{
+	DefaultCronControllerConfig = &configv1.CronControllerConfig{
 		CronFormat:                  "standard",
 		CronHashNames:               pointer.Bool(true),
 		CronHashSecondsByDefault:    pointer.Bool(false),
diff --git a/pkg/execution/controllers/croncontroller/controller.go b/pkg/execution/controllers/croncontroller/controller.go
index 53b3b9c..7c8b39f 100644
--- a/pkg/execution/controllers/croncontroller/controller.go
+++ b/pkg/execution/controllers/croncontroller/controller.go
@@ -30,7 +30,7 @@ import (
 	"k8s.io/client-go/util/workqueue"
 	"k8s.io/klog/v2"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	execution "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	"github.com/furiko-io/furiko/pkg/generated/clientset/versioned/scheme"
 	executioninformers "github.com/furiko-io/furiko/pkg/generated/informers/externalversions/execution/v1alpha1"
@@ -105,7 +105,7 @@ func NewRecorder(context controllercontext.ContextInterface) record.EventRecorde
 
 func NewController(
 	ctrlContext controllercontext.ContextInterface,
-	concurrency *configv1alpha1.Concurrency,
+	concurrency *configv1.Concurrency,
 ) (*Controller, error) {
 	ctrl := &Controller{
 		Context: NewContext(ctrlContext),
diff --git a/pkg/execution/controllers/croncontroller/cron.go b/pkg/execution/controllers/croncontroller/cron.go
index d26cb9e..9c194fb 100644
--- a/pkg/execution/controllers/croncontroller/cron.go
+++ b/pkg/execution/controllers/croncontroller/cron.go
@@ -28,7 +28,7 @@ import (
 
 	utiltrace "k8s.io/utils/trace"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	execution "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	"github.com/furiko-io/furiko/pkg/core/tzutils"
 	"github.com/furiko-io/furiko/pkg/execution/util/cronparser"
@@ -135,7 +135,7 @@ func (w *CronWorker) flushKeys() {
 // syncOne reconciles a single JobConfig and enqueues Jobs to be created.
 func (w *CronWorker) syncOne(
 	jobConfig *execution.JobConfig,
-	cfg *configv1alpha1.CronExecutionConfig,
+	cfg *configv1.CronControllerConfig,
 	parser *cronparser.Parser,
 ) error {
 	schedule := jobConfig.Spec.Schedule
@@ -227,7 +227,7 @@ func (w *CronWorker) enqueueJob(jobConfig *execution.JobConfig, scheduleTime tim
 }
 
 // getTimezone returns the timezone for the given JobConfig.
-func (w *CronWorker) getTimezone(cronSchedule *execution.CronSchedule, cfg *configv1alpha1.CronExecutionConfig) string {
+func (w *CronWorker) getTimezone(cronSchedule *execution.CronSchedule, cfg *configv1.CronControllerConfig) string {
 	// Read from spec.
 	if cronSchedule.Timezone != "" {
 		return cronSchedule.Timezone
diff --git a/pkg/execution/controllers/croncontroller/factory.go b/pkg/execution/controllers/croncontroller/factory.go
index a029d3c..b293135 100644
--- a/pkg/execution/controllers/croncontroller/factory.go
+++ b/pkg/execution/controllers/croncontroller/factory.go
@@ -17,7 +17,7 @@
 package croncontroller
 
 import (
-	v1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	v1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/runtime/controllercontext"
 	"github.com/furiko-io/furiko/pkg/runtime/controllermanager"
 )
diff --git a/pkg/execution/controllers/croncontroller/reconciler.go b/pkg/execution/controllers/croncontroller/reconciler.go
index 8955c17..5fb14d9 100644
--- a/pkg/execution/controllers/croncontroller/reconciler.go
+++ b/pkg/execution/controllers/croncontroller/reconciler.go
@@ -26,7 +26,7 @@ import (
 	"k8s.io/klog/v2"
 	utiltrace "k8s.io/utils/trace"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	execution "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	"github.com/furiko-io/furiko/pkg/runtime/controllercontext"
 	"github.com/furiko-io/furiko/pkg/runtime/controllerutil"
@@ -39,7 +39,7 @@ import (
 // same JobConfig concurrently.
 type Reconciler struct {
 	*Context
-	concurrency *configv1alpha1.Concurrency
+	concurrency *configv1.Concurrency
 	client      ExecutionControlInterface
 	store       controllercontext.ActiveJobStore
 }
@@ -48,7 +48,7 @@ func NewReconciler(
 	ctrlContext *Context,
 	client ExecutionControlInterface,
 	store controllercontext.ActiveJobStore,
-	concurrency *configv1alpha1.Concurrency,
+	concurrency *configv1.Concurrency,
 ) *Reconciler {
 	return &Reconciler{
 		Context:     ctrlContext,
diff --git a/pkg/execution/controllers/jobconfigcontroller/controller.go b/pkg/execution/controllers/jobconfigcontroller/controller.go
index e239bf1..2d60b20 100644
--- a/pkg/execution/controllers/jobconfigcontroller/controller.go
+++ b/pkg/execution/controllers/jobconfigcontroller/controller.go
@@ -29,7 +29,7 @@ import (
 	"k8s.io/client-go/util/workqueue"
 	"k8s.io/klog/v2"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/execution/taskexecutor"
 	"github.com/furiko-io/furiko/pkg/generated/clientset/versioned/scheme"
 	executioninformers "github.com/furiko-io/furiko/pkg/generated/informers/externalversions/execution/v1alpha1"
@@ -96,7 +96,7 @@ func NewContext(context controllercontext.ContextInterface) *Context {
 
 func NewController(
 	ctrlContext controllercontext.ContextInterface,
-	concurrency *configv1alpha1.Concurrency,
+	concurrency *configv1.Concurrency,
 ) (*Controller, error) {
 	ctrl := &Controller{
 		Context: NewContext(ctrlContext),
diff --git a/pkg/execution/controllers/jobconfigcontroller/factory.go b/pkg/execution/controllers/jobconfigcontroller/factory.go
index cbdfa89..1524849 100644
--- a/pkg/execution/controllers/jobconfigcontroller/factory.go
+++ b/pkg/execution/controllers/jobconfigcontroller/factory.go
@@ -17,7 +17,7 @@
 package jobconfigcontroller
 
 import (
-	v1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	v1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/runtime/controllercontext"
 	"github.com/furiko-io/furiko/pkg/runtime/controllermanager"
 )
diff --git a/pkg/execution/controllers/jobconfigcontroller/reconciler.go b/pkg/execution/controllers/jobconfigcontroller/reconciler.go
index dcbc691..b279f81 100644
--- a/pkg/execution/controllers/jobconfigcontroller/reconciler.go
+++ b/pkg/execution/controllers/jobconfigcontroller/reconciler.go
@@ -29,7 +29,7 @@ import (
 	"k8s.io/klog/v2"
 	utiltrace "k8s.io/utils/trace"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	execution "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	"github.com/furiko-io/furiko/pkg/runtime/controllerutil"
 	"github.com/furiko-io/furiko/pkg/utils/execution/job"
@@ -40,10 +40,10 @@ import (
 
 type Reconciler struct {
 	*Context
-	concurrency *configv1alpha1.Concurrency
+	concurrency *configv1.Concurrency
 }
 
-func NewReconciler(ctrlContext *Context, concurrency *configv1alpha1.Concurrency) *Reconciler {
+func NewReconciler(ctrlContext *Context, concurrency *configv1.Concurrency) *Reconciler {
 	return &Reconciler{
 		Context:     ctrlContext,
 		concurrency: concurrency,
diff --git a/pkg/execution/controllers/jobcontroller/controller.go b/pkg/execution/controllers/jobcontroller/controller.go
index 60817f2..875c7f7 100644
--- a/pkg/execution/controllers/jobcontroller/controller.go
+++ b/pkg/execution/controllers/jobcontroller/controller.go
@@ -30,7 +30,7 @@ import (
 	"k8s.io/client-go/util/workqueue"
 	"k8s.io/klog/v2"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/execution/taskexecutor"
 	"github.com/furiko-io/furiko/pkg/execution/tasks"
 	"github.com/furiko-io/furiko/pkg/generated/clientset/versioned/scheme"
@@ -108,7 +108,7 @@ func NewContextWithRecorder(context controllercontext.ContextInterface, recorder
 
 func NewController(
 	ctrlContext controllercontext.ContextInterface,
-	concurrency *configv1alpha1.Concurrency,
+	concurrency *configv1.Concurrency,
 ) (*Controller, error) {
 	ctrl := &Controller{
 		Context: NewContext(ctrlContext),
diff --git a/pkg/execution/controllers/jobcontroller/factory.go b/pkg/execution/controllers/jobcontroller/factory.go
index 9d2cc34..a998d6a 100644
--- a/pkg/execution/controllers/jobcontroller/factory.go
+++ b/pkg/execution/controllers/jobcontroller/factory.go
@@ -17,7 +17,7 @@
 package jobcontroller
 
 import (
-	v1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	v1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/runtime/controllercontext"
 	"github.com/furiko-io/furiko/pkg/runtime/controllermanager"
 )
diff --git a/pkg/execution/controllers/jobcontroller/reconciler.go b/pkg/execution/controllers/jobcontroller/reconciler.go
index 72c5ea7..e7e98cd 100644
--- a/pkg/execution/controllers/jobcontroller/reconciler.go
+++ b/pkg/execution/controllers/jobcontroller/reconciler.go
@@ -29,7 +29,7 @@ import (
 	"k8s.io/klog/v2"
 	utiltrace "k8s.io/utils/trace"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	executiongroup "github.com/furiko-io/furiko/apis/execution"
 	execution "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	rerrors "github.com/furiko-io/furiko/pkg/errors"
@@ -45,10 +45,10 @@ import (
 type Reconciler struct {
 	*Context
 	client      *ExecutionControl
-	concurrency *configv1alpha1.Concurrency
+	concurrency *configv1.Concurrency
 }
 
-func NewReconciler(ctrlContext *Context, concurrency *configv1alpha1.Concurrency) *Reconciler {
+func NewReconciler(ctrlContext *Context, concurrency *configv1.Concurrency) *Reconciler {
 	worker := &Reconciler{
 		Context:     ctrlContext,
 		concurrency: concurrency,
@@ -124,7 +124,7 @@ func (w *Reconciler) SyncOne(ctx context.Context, namespace, name string, _ int)
 // sync performs the main logic for the controller.
 // IMPORTANT: This method MUST returns the latest Job we want to update, even if an error was met midway.
 func (w *Reconciler) sync(
-	ctx context.Context, rj *execution.Job, cfg *configv1alpha1.JobExecutionConfig, trace *utiltrace.Trace,
+	ctx context.Context, rj *execution.Job, cfg *configv1.JobControllerConfig, trace *utiltrace.Trace,
 ) (*execution.Job, error) {
 	// Main logic: Perform task creation/adoption and reconciliation. If Job is not
 	// started or is being deleted, this is a no-op.
@@ -161,7 +161,7 @@ func (w *Reconciler) sync(
 
 // syncJobTasks performs the main reconciliation logic for managing tasks and reconciling the status with the tasks.
 func (w *Reconciler) syncJobTasks(
-	ctx context.Context, rj *execution.Job, cfg *configv1alpha1.JobExecutionConfig, trace *utiltrace.Trace,
+	ctx context.Context, rj *execution.Job, cfg *configv1.JobControllerConfig, trace *utiltrace.Trace,
 ) (*execution.Job, error) {
 	taskMgr, err := w.tasks.ForJob(rj)
 	if err != nil {
@@ -427,7 +427,7 @@ func (w *Reconciler) createTask(ctx context.Context, rj *execution.Job) (jobtask
 // handlePendingTasks looks for pending tasks that have exceeded their pending timeout, and subsequently
 // kill those tasks.
 func (w *Reconciler) handlePendingTasks(ctx context.Context, rj *execution.Job, tasks []jobtasks.Task,
-	cfg *configv1alpha1.JobExecutionConfig) error {
+	cfg *configv1.JobControllerConfig) error {
 	now := ktime.Now().Time
 
 	pendingTimeout := jobutil.GetPendingTimeout(rj, cfg)
@@ -539,7 +539,7 @@ func (w *Reconciler) handleKillJob(ctx context.Context, rj *execution.Job, tasks
 
 // handleDeleteKillingTasks uses deletion to kill tasks if prior efforts to set kill timestamp on tasks are ineffective.
 func (w *Reconciler) handleDeleteKillingTasks(
-	ctx context.Context, rj *execution.Job, tasks []jobtasks.Task, cfg *configv1alpha1.JobExecutionConfig,
+	ctx context.Context, rj *execution.Job, tasks []jobtasks.Task, cfg *configv1.JobControllerConfig,
 ) (*execution.Job, error) {
 	timeout := jobutil.GetDeleteKillingTimeout(cfg)
 	needDelete := make([]jobtasks.Task, 0, len(tasks))
@@ -624,7 +624,7 @@ func (w *Reconciler) handleDeleteKillingTasks(
 // handleForceDeleteKillingTasks will perform non-graceful deletion of tasks if it cannot be deleted gracefully.
 // For example, when kubelet is not running, normal deletion will not be effective.
 func (w *Reconciler) handleForceDeleteKillingTasks(
-	ctx context.Context, rj *execution.Job, tasks []jobtasks.Task, cfg *configv1alpha1.JobExecutionConfig,
+	ctx context.Context, rj *execution.Job, tasks []jobtasks.Task, cfg *configv1.JobControllerConfig,
 ) (*execution.Job, error) {
 	timeout := jobutil.GetForceDeleteKillingTimeout(cfg)
 
@@ -714,7 +714,7 @@ func (w *Reconciler) handleForceDeleteKillingTasks(
 func (w *Reconciler) handleTTLAfterFinished(
 	ctx context.Context,
 	rj *execution.Job,
-	cfg *configv1alpha1.JobExecutionConfig,
+	cfg *configv1.JobControllerConfig,
 ) error {
 	ttl := jobutil.GetTTLAfterFinished(rj, cfg)
 
diff --git a/pkg/execution/controllers/jobqueuecontroller/controller.go b/pkg/execution/controllers/jobqueuecontroller/controller.go
index f6b0d95..92af5aa 100644
--- a/pkg/execution/controllers/jobqueuecontroller/controller.go
+++ b/pkg/execution/controllers/jobqueuecontroller/controller.go
@@ -29,7 +29,7 @@ import (
 	"k8s.io/client-go/util/workqueue"
 	"k8s.io/klog/v2"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/generated/clientset/versioned/scheme"
 	executioninformers "github.com/furiko-io/furiko/pkg/generated/informers/externalversions/execution/v1alpha1"
 	"github.com/furiko-io/furiko/pkg/runtime/controllercontext"
@@ -91,7 +91,7 @@ func NewContext(context controllercontext.ContextInterface) *Context {
 
 func NewController(
 	ctrlContext controllercontext.ContextInterface,
-	concurrency *configv1alpha1.Concurrency,
+	concurrency *configv1.Concurrency,
 ) (*Controller, error) {
 	ctrl := &Controller{
 		Context: NewContext(ctrlContext),
diff --git a/pkg/execution/controllers/jobqueuecontroller/factory.go b/pkg/execution/controllers/jobqueuecontroller/factory.go
index a903715..8375236 100644
--- a/pkg/execution/controllers/jobqueuecontroller/factory.go
+++ b/pkg/execution/controllers/jobqueuecontroller/factory.go
@@ -17,7 +17,7 @@
 package jobqueuecontroller
 
 import (
-	v1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	v1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/runtime/controllercontext"
 	"github.com/furiko-io/furiko/pkg/runtime/controllermanager"
 )
diff --git a/pkg/execution/controllers/jobqueuecontroller/reconciler_independent.go b/pkg/execution/controllers/jobqueuecontroller/reconciler_independent.go
index d7f8cde..39f4b87 100644
--- a/pkg/execution/controllers/jobqueuecontroller/reconciler_independent.go
+++ b/pkg/execution/controllers/jobqueuecontroller/reconciler_independent.go
@@ -26,18 +26,18 @@ import (
 	"k8s.io/klog/v2"
 	utiltrace "k8s.io/utils/trace"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/runtime/controllerutil"
 )
 
 // IndependentReconciler reconciles independent Jobs that do not have any JobConfig.
 type IndependentReconciler struct {
 	*Context
-	concurrency *configv1alpha1.Concurrency
+	concurrency *configv1.Concurrency
 	client      JobControlInterface
 }
 
-func NewIndependentReconciler(ctrlContext *Context, concurrency *configv1alpha1.Concurrency) *IndependentReconciler {
+func NewIndependentReconciler(ctrlContext *Context, concurrency *configv1.Concurrency) *IndependentReconciler {
 	reconciler := &IndependentReconciler{
 		Context:     ctrlContext,
 		concurrency: concurrency,
diff --git a/pkg/execution/controllers/jobqueuecontroller/reconciler_perjobconfig.go b/pkg/execution/controllers/jobqueuecontroller/reconciler_perjobconfig.go
index 5c302d0..11182a9 100644
--- a/pkg/execution/controllers/jobqueuecontroller/reconciler_perjobconfig.go
+++ b/pkg/execution/controllers/jobqueuecontroller/reconciler_perjobconfig.go
@@ -29,7 +29,7 @@ import (
 	"k8s.io/klog/v2"
 	utiltrace "k8s.io/utils/trace"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	execution "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	"github.com/furiko-io/furiko/pkg/runtime/controllercontext"
 	"github.com/furiko-io/furiko/pkg/runtime/controllerutil"
@@ -44,11 +44,11 @@ import (
 // JobConfig.
 type PerConfigReconciler struct {
 	*Context
-	concurrency *configv1alpha1.Concurrency
+	concurrency *configv1.Concurrency
 	client      JobControlInterface
 }
 
-func NewPerConfigReconciler(ctrlContext *Context, concurrency *configv1alpha1.Concurrency) *PerConfigReconciler {
+func NewPerConfigReconciler(ctrlContext *Context, concurrency *configv1.Concurrency) *PerConfigReconciler {
 	reconciler := &PerConfigReconciler{
 		Context:     ctrlContext,
 		concurrency: concurrency,
diff --git a/pkg/execution/util/cronparser/parser.go b/pkg/execution/util/cronparser/parser.go
index c56b405..735d5b9 100644
--- a/pkg/execution/util/cronparser/parser.go
+++ b/pkg/execution/util/cronparser/parser.go
@@ -19,7 +19,7 @@ package cronparser
 import (
 	"github.com/furiko-io/cronexpr"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 )
 
 // Parser wraps the raw cronexpr parser to encapsulate common configuration.
@@ -29,7 +29,7 @@ type Parser struct {
 	opts      []cronexpr.ParseOption
 }
 
-func NewParser(cfg *configv1alpha1.CronExecutionConfig) *Parser {
+func NewParser(cfg *configv1.CronControllerConfig) *Parser {
 	format := cronexpr.CronFormatStandard
 	if cronexpr.CronFormat(cfg.CronFormat) == cronexpr.CronFormatQuartz {
 		format = cronexpr.CronFormatQuartz
diff --git a/pkg/generated/clientset/versioned/clientset.go b/pkg/generated/clientset/versioned/clientset.go
index 28bc305..2f481a7 100644
--- a/pkg/generated/clientset/versioned/clientset.go
+++ b/pkg/generated/clientset/versioned/clientset.go
@@ -22,10 +22,11 @@ import (
 	"fmt"
 	"net/http"
 
-	executionv1alpha1 "github.com/furiko-io/furiko/pkg/generated/clientset/versioned/typed/execution/v1alpha1"
 	discovery "k8s.io/client-go/discovery"
 	rest "k8s.io/client-go/rest"
 	flowcontrol "k8s.io/client-go/util/flowcontrol"
+
+	executionv1alpha1 "github.com/furiko-io/furiko/pkg/generated/clientset/versioned/typed/execution/v1alpha1"
 )
 
 type Interface interface {
diff --git a/pkg/generated/clientset/versioned/fake/clientset_generated.go b/pkg/generated/clientset/versioned/fake/clientset_generated.go
index 5c6cc9e..99f37bb 100644
--- a/pkg/generated/clientset/versioned/fake/clientset_generated.go
+++ b/pkg/generated/clientset/versioned/fake/clientset_generated.go
@@ -19,14 +19,15 @@
 package fake
 
 import (
-	clientset "github.com/furiko-io/furiko/pkg/generated/clientset/versioned"
-	executionv1alpha1 "github.com/furiko-io/furiko/pkg/generated/clientset/versioned/typed/execution/v1alpha1"
-	fakeexecutionv1alpha1 "github.com/furiko-io/furiko/pkg/generated/clientset/versioned/typed/execution/v1alpha1/fake"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/watch"
 	"k8s.io/client-go/discovery"
 	fakediscovery "k8s.io/client-go/discovery/fake"
 	"k8s.io/client-go/testing"
+
+	clientset "github.com/furiko-io/furiko/pkg/generated/clientset/versioned"
+	executionv1alpha1 "github.com/furiko-io/furiko/pkg/generated/clientset/versioned/typed/execution/v1alpha1"
+	fakeexecutionv1alpha1 "github.com/furiko-io/furiko/pkg/generated/clientset/versioned/typed/execution/v1alpha1/fake"
 )
 
 // NewSimpleClientset returns a clientset that will respond with the provided objects.
diff --git a/pkg/generated/clientset/versioned/fake/register.go b/pkg/generated/clientset/versioned/fake/register.go
index 49a44d3..ce06e84 100644
--- a/pkg/generated/clientset/versioned/fake/register.go
+++ b/pkg/generated/clientset/versioned/fake/register.go
@@ -19,12 +19,13 @@
 package fake
 
 import (
-	executionv1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	runtime "k8s.io/apimachinery/pkg/runtime"
 	schema "k8s.io/apimachinery/pkg/runtime/schema"
 	serializer "k8s.io/apimachinery/pkg/runtime/serializer"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+
+	executionv1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 )
 
 var scheme = runtime.NewScheme()
diff --git a/pkg/generated/clientset/versioned/scheme/register.go b/pkg/generated/clientset/versioned/scheme/register.go
index 272e4ca..9d72ae6 100644
--- a/pkg/generated/clientset/versioned/scheme/register.go
+++ b/pkg/generated/clientset/versioned/scheme/register.go
@@ -19,12 +19,13 @@
 package scheme
 
 import (
-	executionv1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	runtime "k8s.io/apimachinery/pkg/runtime"
 	schema "k8s.io/apimachinery/pkg/runtime/schema"
 	serializer "k8s.io/apimachinery/pkg/runtime/serializer"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+
+	executionv1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 )
 
 var Scheme = runtime.NewScheme()
diff --git a/pkg/generated/clientset/versioned/typed/execution/v1alpha1/execution_client.go b/pkg/generated/clientset/versioned/typed/execution/v1alpha1/execution_client.go
index 8cac3bc..619dad1 100644
--- a/pkg/generated/clientset/versioned/typed/execution/v1alpha1/execution_client.go
+++ b/pkg/generated/clientset/versioned/typed/execution/v1alpha1/execution_client.go
@@ -21,9 +21,10 @@ package v1alpha1
 import (
 	"net/http"
 
+	rest "k8s.io/client-go/rest"
+
 	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	"github.com/furiko-io/furiko/pkg/generated/clientset/versioned/scheme"
-	rest "k8s.io/client-go/rest"
 )
 
 type ExecutionV1alpha1Interface interface {
diff --git a/pkg/generated/clientset/versioned/typed/execution/v1alpha1/fake/fake_execution_client.go b/pkg/generated/clientset/versioned/typed/execution/v1alpha1/fake/fake_execution_client.go
index 6bca5d9..5909ab9 100644
--- a/pkg/generated/clientset/versioned/typed/execution/v1alpha1/fake/fake_execution_client.go
+++ b/pkg/generated/clientset/versioned/typed/execution/v1alpha1/fake/fake_execution_client.go
@@ -19,9 +19,10 @@
 package fake
 
 import (
-	v1alpha1 "github.com/furiko-io/furiko/pkg/generated/clientset/versioned/typed/execution/v1alpha1"
 	rest "k8s.io/client-go/rest"
 	testing "k8s.io/client-go/testing"
+
+	v1alpha1 "github.com/furiko-io/furiko/pkg/generated/clientset/versioned/typed/execution/v1alpha1"
 )
 
 type FakeExecutionV1alpha1 struct {
diff --git a/pkg/generated/clientset/versioned/typed/execution/v1alpha1/fake/fake_job.go b/pkg/generated/clientset/versioned/typed/execution/v1alpha1/fake/fake_job.go
index c12c370..7ffea43 100644
--- a/pkg/generated/clientset/versioned/typed/execution/v1alpha1/fake/fake_job.go
+++ b/pkg/generated/clientset/versioned/typed/execution/v1alpha1/fake/fake_job.go
@@ -21,13 +21,14 @@ package fake
 import (
 	"context"
 
-	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	labels "k8s.io/apimachinery/pkg/labels"
 	schema "k8s.io/apimachinery/pkg/runtime/schema"
 	types "k8s.io/apimachinery/pkg/types"
 	watch "k8s.io/apimachinery/pkg/watch"
 	testing "k8s.io/client-go/testing"
+
+	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 )
 
 // FakeJobs implements JobInterface
diff --git a/pkg/generated/clientset/versioned/typed/execution/v1alpha1/fake/fake_jobconfig.go b/pkg/generated/clientset/versioned/typed/execution/v1alpha1/fake/fake_jobconfig.go
index 3660d52..da4b48e 100644
--- a/pkg/generated/clientset/versioned/typed/execution/v1alpha1/fake/fake_jobconfig.go
+++ b/pkg/generated/clientset/versioned/typed/execution/v1alpha1/fake/fake_jobconfig.go
@@ -21,13 +21,14 @@ package fake
 import (
 	"context"
 
-	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	labels "k8s.io/apimachinery/pkg/labels"
 	schema "k8s.io/apimachinery/pkg/runtime/schema"
 	types "k8s.io/apimachinery/pkg/types"
 	watch "k8s.io/apimachinery/pkg/watch"
 	testing "k8s.io/client-go/testing"
+
+	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 )
 
 // FakeJobConfigs implements JobConfigInterface
diff --git a/pkg/generated/clientset/versioned/typed/execution/v1alpha1/job.go b/pkg/generated/clientset/versioned/typed/execution/v1alpha1/job.go
index a41a4b1..4ab5087 100644
--- a/pkg/generated/clientset/versioned/typed/execution/v1alpha1/job.go
+++ b/pkg/generated/clientset/versioned/typed/execution/v1alpha1/job.go
@@ -22,12 +22,13 @@ import (
 	"context"
 	"time"
 
-	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
-	scheme "github.com/furiko-io/furiko/pkg/generated/clientset/versioned/scheme"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	types "k8s.io/apimachinery/pkg/types"
 	watch "k8s.io/apimachinery/pkg/watch"
 	rest "k8s.io/client-go/rest"
+
+	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
+	scheme "github.com/furiko-io/furiko/pkg/generated/clientset/versioned/scheme"
 )
 
 // JobsGetter has a method to return a JobInterface.
diff --git a/pkg/generated/clientset/versioned/typed/execution/v1alpha1/jobconfig.go b/pkg/generated/clientset/versioned/typed/execution/v1alpha1/jobconfig.go
index f408173..f7490d5 100644
--- a/pkg/generated/clientset/versioned/typed/execution/v1alpha1/jobconfig.go
+++ b/pkg/generated/clientset/versioned/typed/execution/v1alpha1/jobconfig.go
@@ -22,12 +22,13 @@ import (
 	"context"
 	"time"
 
-	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
-	scheme "github.com/furiko-io/furiko/pkg/generated/clientset/versioned/scheme"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	types "k8s.io/apimachinery/pkg/types"
 	watch "k8s.io/apimachinery/pkg/watch"
 	rest "k8s.io/client-go/rest"
+
+	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
+	scheme "github.com/furiko-io/furiko/pkg/generated/clientset/versioned/scheme"
 )
 
 // JobConfigsGetter has a method to return a JobConfigInterface.
diff --git a/pkg/generated/informers/externalversions/execution/v1alpha1/job.go b/pkg/generated/informers/externalversions/execution/v1alpha1/job.go
index 503d569..403479d 100644
--- a/pkg/generated/informers/externalversions/execution/v1alpha1/job.go
+++ b/pkg/generated/informers/externalversions/execution/v1alpha1/job.go
@@ -22,14 +22,15 @@ import (
 	"context"
 	time "time"
 
-	executionv1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
-	versioned "github.com/furiko-io/furiko/pkg/generated/clientset/versioned"
-	internalinterfaces "github.com/furiko-io/furiko/pkg/generated/informers/externalversions/internalinterfaces"
-	v1alpha1 "github.com/furiko-io/furiko/pkg/generated/listers/execution/v1alpha1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	runtime "k8s.io/apimachinery/pkg/runtime"
 	watch "k8s.io/apimachinery/pkg/watch"
 	cache "k8s.io/client-go/tools/cache"
+
+	executionv1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
+	versioned "github.com/furiko-io/furiko/pkg/generated/clientset/versioned"
+	internalinterfaces "github.com/furiko-io/furiko/pkg/generated/informers/externalversions/internalinterfaces"
+	v1alpha1 "github.com/furiko-io/furiko/pkg/generated/listers/execution/v1alpha1"
 )
 
 // JobInformer provides access to a shared informer and lister for
diff --git a/pkg/generated/informers/externalversions/execution/v1alpha1/jobconfig.go b/pkg/generated/informers/externalversions/execution/v1alpha1/jobconfig.go
index 508dfab..595cf16 100644
--- a/pkg/generated/informers/externalversions/execution/v1alpha1/jobconfig.go
+++ b/pkg/generated/informers/externalversions/execution/v1alpha1/jobconfig.go
@@ -22,14 +22,15 @@ import (
 	"context"
 	time "time"
 
-	executionv1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
-	versioned "github.com/furiko-io/furiko/pkg/generated/clientset/versioned"
-	internalinterfaces "github.com/furiko-io/furiko/pkg/generated/informers/externalversions/internalinterfaces"
-	v1alpha1 "github.com/furiko-io/furiko/pkg/generated/listers/execution/v1alpha1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	runtime "k8s.io/apimachinery/pkg/runtime"
 	watch "k8s.io/apimachinery/pkg/watch"
 	cache "k8s.io/client-go/tools/cache"
+
+	executionv1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
+	versioned "github.com/furiko-io/furiko/pkg/generated/clientset/versioned"
+	internalinterfaces "github.com/furiko-io/furiko/pkg/generated/informers/externalversions/internalinterfaces"
+	v1alpha1 "github.com/furiko-io/furiko/pkg/generated/listers/execution/v1alpha1"
 )
 
 // JobConfigInformer provides access to a shared informer and lister for
diff --git a/pkg/generated/informers/externalversions/factory.go b/pkg/generated/informers/externalversions/factory.go
index e60792c..f1d886c 100644
--- a/pkg/generated/informers/externalversions/factory.go
+++ b/pkg/generated/informers/externalversions/factory.go
@@ -23,13 +23,14 @@ import (
 	sync "sync"
 	time "time"
 
-	versioned "github.com/furiko-io/furiko/pkg/generated/clientset/versioned"
-	execution "github.com/furiko-io/furiko/pkg/generated/informers/externalversions/execution"
-	internalinterfaces "github.com/furiko-io/furiko/pkg/generated/informers/externalversions/internalinterfaces"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	runtime "k8s.io/apimachinery/pkg/runtime"
 	schema "k8s.io/apimachinery/pkg/runtime/schema"
 	cache "k8s.io/client-go/tools/cache"
+
+	versioned "github.com/furiko-io/furiko/pkg/generated/clientset/versioned"
+	execution "github.com/furiko-io/furiko/pkg/generated/informers/externalversions/execution"
+	internalinterfaces "github.com/furiko-io/furiko/pkg/generated/informers/externalversions/internalinterfaces"
 )
 
 // SharedInformerOption defines the functional option type for SharedInformerFactory.
diff --git a/pkg/generated/informers/externalversions/generic.go b/pkg/generated/informers/externalversions/generic.go
index afd84a9..5432c5e 100644
--- a/pkg/generated/informers/externalversions/generic.go
+++ b/pkg/generated/informers/externalversions/generic.go
@@ -21,9 +21,10 @@ package externalversions
 import (
 	"fmt"
 
-	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	schema "k8s.io/apimachinery/pkg/runtime/schema"
 	cache "k8s.io/client-go/tools/cache"
+
+	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 )
 
 // GenericInformer is type of SharedIndexInformer which will locate and delegate to other
diff --git a/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go
index 827fd34..464e785 100644
--- a/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go
+++ b/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go
@@ -21,10 +21,11 @@ package internalinterfaces
 import (
 	time "time"
 
-	versioned "github.com/furiko-io/furiko/pkg/generated/clientset/versioned"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	runtime "k8s.io/apimachinery/pkg/runtime"
 	cache "k8s.io/client-go/tools/cache"
+
+	versioned "github.com/furiko-io/furiko/pkg/generated/clientset/versioned"
 )
 
 // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer.
diff --git a/pkg/generated/listers/execution/v1alpha1/job.go b/pkg/generated/listers/execution/v1alpha1/job.go
index 8f63e76..89f4316 100644
--- a/pkg/generated/listers/execution/v1alpha1/job.go
+++ b/pkg/generated/listers/execution/v1alpha1/job.go
@@ -19,10 +19,11 @@
 package v1alpha1
 
 import (
-	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/client-go/tools/cache"
+
+	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 )
 
 // JobLister helps list Jobs.
diff --git a/pkg/generated/listers/execution/v1alpha1/jobconfig.go b/pkg/generated/listers/execution/v1alpha1/jobconfig.go
index 31f3ff2..7020baa 100644
--- a/pkg/generated/listers/execution/v1alpha1/jobconfig.go
+++ b/pkg/generated/listers/execution/v1alpha1/jobconfig.go
@@ -19,10 +19,11 @@
 package v1alpha1
 
 import (
-	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/client-go/tools/cache"
+
+	v1alpha1 "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 )
 
 // JobConfigLister helps list JobConfigs.
diff --git a/pkg/runtime/configloader/configmap_loader.go b/pkg/runtime/configloader/configmap_loader.go
index 7fef7a3..29b78be 100644
--- a/pkg/runtime/configloader/configmap_loader.go
+++ b/pkg/runtime/configloader/configmap_loader.go
@@ -30,7 +30,7 @@ import (
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/klog/v2"
 
-	v1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	v1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/utils/eventhandler"
 )
 
diff --git a/pkg/runtime/configloader/defaults_loader.go b/pkg/runtime/configloader/defaults_loader.go
index 27157eb..8368c4b 100644
--- a/pkg/runtime/configloader/defaults_loader.go
+++ b/pkg/runtime/configloader/defaults_loader.go
@@ -24,7 +24,7 @@ import (
 
 	"k8s.io/apimachinery/pkg/runtime"
 
-	v1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	v1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/config"
 )
 
@@ -43,8 +43,8 @@ var _ Loader = (*DefaultsLoader)(nil)
 func NewDefaultsLoader() *DefaultsLoader {
 	return &DefaultsLoader{
 		Defaults: map[v1.ConfigName]runtime.Object{
-			v1.JobExecutionConfigName:  config.DefaultJobControllerConfig,
-			v1.CronExecutionConfigName: config.DefaultCronControllerConfig,
+			v1.ConfigNameJobController:  config.DefaultJobControllerConfig,
+			v1.ConfigNameCronController: config.DefaultCronControllerConfig,
 		},
 	}
 }
diff --git a/pkg/runtime/configloader/manager.go b/pkg/runtime/configloader/manager.go
index acd7343..fa571d1 100644
--- a/pkg/runtime/configloader/manager.go
+++ b/pkg/runtime/configloader/manager.go
@@ -26,7 +26,7 @@ import (
 	"github.com/pkg/errors"
 	"k8s.io/klog/v2"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 )
 
 // ConfigManager manages ConfigLoaders and merges structured configuration
@@ -62,7 +62,7 @@ func (c *ConfigManager) Start(ctx context.Context) error {
 // If an error is encountered, it will return a previously known good value if
 // available, and log the error. Otherwise, if there is no previously cached
 // value for configName, then the error will be propagated back to the caller.
-func (c *ConfigManager) LoadAndUnmarshalConfig(configName configv1alpha1.ConfigName, out interface{}) error {
+func (c *ConfigManager) LoadAndUnmarshalConfig(configName configv1.ConfigName, out interface{}) error {
 	err := c.loadAndUnmarshalConfigWithError(configName, out)
 
 	// Return cached value and log error.
@@ -106,7 +106,7 @@ func (c *ConfigManager) LoadAndUnmarshalConfig(configName configv1alpha1.ConfigN
 	return nil
 }
 
-func (c *ConfigManager) loadAndUnmarshalConfigWithError(configName configv1alpha1.ConfigName, out interface{}) error {
+func (c *ConfigManager) loadAndUnmarshalConfigWithError(configName configv1.ConfigName, out interface{}) error {
 	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
 		TagName: "json",
 		Result:  out,
@@ -125,7 +125,7 @@ func (c *ConfigManager) loadAndUnmarshalConfigWithError(configName configv1alpha
 }
 
 // loadConfig will load the given config name from all loaders.
-func (c *ConfigManager) loadConfig(configName configv1alpha1.ConfigName) (res Config, err error) {
+func (c *ConfigManager) loadConfig(configName configv1.ConfigName) (res Config, err error) {
 	if !c.started {
 		return nil, errors.New("config manager is not started")
 	}
diff --git a/pkg/runtime/configloader/secret_loader.go b/pkg/runtime/configloader/secret_loader.go
index ec10ad0..4b12933 100644
--- a/pkg/runtime/configloader/secret_loader.go
+++ b/pkg/runtime/configloader/secret_loader.go
@@ -26,7 +26,7 @@ import (
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/klog/v2"
 
-	v1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	v1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/utils/eventhandler"
 )
 
diff --git a/pkg/runtime/configloader/types.go b/pkg/runtime/configloader/types.go
index 4df8261..a1875b1 100644
--- a/pkg/runtime/configloader/types.go
+++ b/pkg/runtime/configloader/types.go
@@ -19,7 +19,7 @@ package configloader
 import (
 	"context"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 )
 
 // Config is a dynamic config value.
@@ -29,5 +29,5 @@ type Config map[string]interface{}
 type Loader interface {
 	Name() string
 	Start(context.Context) error
-	Load(configName configv1alpha1.ConfigName) (Config, error)
+	Load(configName configv1.ConfigName) (Config, error)
 }
diff --git a/pkg/runtime/controllercontext/context.go b/pkg/runtime/controllercontext/context.go
index c2a8552..d910083 100644
--- a/pkg/runtime/controllercontext/context.go
+++ b/pkg/runtime/controllercontext/context.go
@@ -22,7 +22,7 @@ import (
 	"github.com/pkg/errors"
 	"k8s.io/client-go/rest"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 )
 
 // Context is a shared controller context that can be safely shared between controllers.
@@ -37,7 +37,7 @@ type Context struct {
 var _ ContextInterface = &Context{}
 
 // NewForConfig prepares a new Context from a kubeconfig and controller manager config spec.
-func NewForConfig(cfg *rest.Config, ctrlConfig *configv1alpha1.BootstrapConfigSpec) (*Context, error) {
+func NewForConfig(cfg *rest.Config, ctrlConfig *configv1.BootstrapConfigSpec) (*Context, error) {
 	ctrlContext := &Context{
 		restConfig: cfg,
 	}
diff --git a/pkg/runtime/controllercontext/context_configs.go b/pkg/runtime/controllercontext/context_configs.go
index c74c449..14570bf 100644
--- a/pkg/runtime/controllercontext/context_configs.go
+++ b/pkg/runtime/controllercontext/context_configs.go
@@ -23,7 +23,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/runtime/configloader"
 )
 
@@ -34,9 +34,9 @@ func (c *Context) Configs() Configs {
 
 type Configs interface {
 	Start(ctx context.Context) error
-	AllConfigs() (map[configv1alpha1.ConfigName]runtime.Object, error)
-	JobController() (*configv1alpha1.JobExecutionConfig, error)
-	CronController() (*configv1alpha1.CronExecutionConfig, error)
+	AllConfigs() (map[configv1.ConfigName]runtime.Object, error)
+	JobController() (*configv1.JobControllerConfig, error)
+	CronController() (*configv1.CronControllerConfig, error)
 }
 
 type ContextConfigs struct {
@@ -48,17 +48,17 @@ func NewContextConfigs(mgr *configloader.ConfigManager) *ContextConfigs {
 }
 
 // AllConfigs returns a map of all configs.
-func (c *ContextConfigs) AllConfigs() (map[configv1alpha1.ConfigName]runtime.Object, error) {
-	configNameMap := map[configv1alpha1.ConfigName]func() (runtime.Object, error){
-		configv1alpha1.JobExecutionConfigName: func() (runtime.Object, error) {
+func (c *ContextConfigs) AllConfigs() (map[configv1.ConfigName]runtime.Object, error) {
+	configNameMap := map[configv1.ConfigName]func() (runtime.Object, error){
+		configv1.ConfigNameJobController: func() (runtime.Object, error) {
 			return c.JobController()
 		},
-		configv1alpha1.CronExecutionConfigName: func() (runtime.Object, error) {
+		configv1.ConfigNameCronController: func() (runtime.Object, error) {
 			return c.CronController()
 		},
 	}
 
-	configs := make(map[configv1alpha1.ConfigName]runtime.Object)
+	configs := make(map[configv1.ConfigName]runtime.Object)
 	for configName, load := range configNameMap {
 		cfg, err := load()
 		if err != nil {
@@ -71,25 +71,25 @@ func (c *ContextConfigs) AllConfigs() (map[configv1alpha1.ConfigName]runtime.Obj
 }
 
 // JobController returns the job controller configuration.
-func (c *ContextConfigs) JobController() (*configv1alpha1.JobExecutionConfig, error) {
-	var config configv1alpha1.JobExecutionConfig
-	if err := c.LoadAndUnmarshalConfig(configv1alpha1.JobExecutionConfigName, &config); err != nil {
+func (c *ContextConfigs) JobController() (*configv1.JobControllerConfig, error) {
+	var config configv1.JobControllerConfig
+	if err := c.LoadAndUnmarshalConfig(configv1.ConfigNameJobController, &config); err != nil {
 		return nil, err
 	}
 	return &config, nil
 }
 
 // CronController returns the cron controller configuration.
-func (c *ContextConfigs) CronController() (*configv1alpha1.CronExecutionConfig, error) {
-	var config configv1alpha1.CronExecutionConfig
-	if err := c.LoadAndUnmarshalConfig(configv1alpha1.CronExecutionConfigName, &config); err != nil {
+func (c *ContextConfigs) CronController() (*configv1.CronControllerConfig, error) {
+	var config configv1.CronControllerConfig
+	if err := c.LoadAndUnmarshalConfig(configv1.ConfigNameCronController, &config); err != nil {
 		return nil, err
 	}
 	return &config, nil
 }
 
 // SetUpConfigManager sets up the ConfigManager and returns a composed Configs interface.
-func SetUpConfigManager(cfg *configv1alpha1.BootstrapConfigSpec, client kubernetes.Interface) Configs {
+func SetUpConfigManager(cfg *configv1.BootstrapConfigSpec, client kubernetes.Interface) Configs {
 	configManager := configloader.NewConfigManager()
 	var configMapNamespace, configMapName, secretNamespace, secretName string
 	if cfg := cfg.DynamicConfigs; cfg != nil {
diff --git a/pkg/runtime/controllercontext/context_informers.go b/pkg/runtime/controllercontext/context_informers.go
index 3ef354d..84904ca 100644
--- a/pkg/runtime/controllercontext/context_informers.go
+++ b/pkg/runtime/controllercontext/context_informers.go
@@ -22,7 +22,7 @@ import (
 
 	kubernetes "k8s.io/client-go/informers"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	furiko "github.com/furiko-io/furiko/pkg/generated/informers/externalversions"
 )
 
@@ -56,7 +56,7 @@ func (c *contextInformers) Furiko() furiko.SharedInformerFactory {
 	return c.furiko
 }
 
-func SetUpInformers(clientsets Clientsets, cfg *configv1alpha1.BootstrapConfigSpec) Informers {
+func SetUpInformers(clientsets Clientsets, cfg *configv1.BootstrapConfigSpec) Informers {
 	defaultResync := cfg.DefaultResync.Duration
 	if defaultResync == 0 {
 		defaultResync = defaultDefaultResync
diff --git a/pkg/runtime/controllercontext/mock/context_configs.go b/pkg/runtime/controllercontext/mock/context_configs.go
index 7500557..2bb93b8 100644
--- a/pkg/runtime/controllercontext/mock/context_configs.go
+++ b/pkg/runtime/controllercontext/mock/context_configs.go
@@ -23,7 +23,7 @@ import (
 
 	"k8s.io/apimachinery/pkg/runtime"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/runtime/configloader"
 	"github.com/furiko-io/furiko/pkg/runtime/controllercontext"
 )
@@ -56,7 +56,7 @@ func (c *Configs) MockConfigLoader() *ConfigLoader {
 var _ controllercontext.Configs = (*Configs)(nil)
 
 type ConfigLoader struct {
-	configs map[configv1alpha1.ConfigName]runtime.Object
+	configs map[configv1.ConfigName]runtime.Object
 	mu      sync.RWMutex
 }
 
@@ -64,7 +64,7 @@ var _ configloader.Loader = (*ConfigLoader)(nil)
 
 func NewMockConfigLoader() *ConfigLoader {
 	return &ConfigLoader{
-		configs: make(map[configv1alpha1.ConfigName]runtime.Object),
+		configs: make(map[configv1.ConfigName]runtime.Object),
 	}
 }
 
@@ -76,7 +76,7 @@ func (c *ConfigLoader) Start(ctx context.Context) error {
 	return nil
 }
 
-func (c *ConfigLoader) Load(configName configv1alpha1.ConfigName) (configloader.Config, error) {
+func (c *ConfigLoader) Load(configName configv1.ConfigName) (configloader.Config, error) {
 	config, ok := c.configs[configName]
 	if !ok {
 		return nil, nil
@@ -92,7 +92,7 @@ func (c *ConfigLoader) Load(configName configv1alpha1.ConfigName) (configloader.
 	return m, nil
 }
 
-func (c *ConfigLoader) SetConfig(configName configv1alpha1.ConfigName, config runtime.Object) {
+func (c *ConfigLoader) SetConfig(configName configv1.ConfigName, config runtime.Object) {
 	c.mu.Lock()
 	defer c.mu.Unlock()
 	c.configs[configName] = config
diff --git a/pkg/runtime/controllermanager/controller.go b/pkg/runtime/controllermanager/controller.go
index 25d5169..1cc2e1c 100644
--- a/pkg/runtime/controllermanager/controller.go
+++ b/pkg/runtime/controllermanager/controller.go
@@ -24,7 +24,7 @@ import (
 	"golang.org/x/sync/errgroup"
 	"k8s.io/klog/v2"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/runtime/controllercontext"
 	"github.com/furiko-io/furiko/pkg/runtime/leaderelection"
 )
@@ -53,7 +53,7 @@ type ControllerManager struct {
 
 func NewManager(
 	ctrlContext *controllercontext.Context,
-	ctrlCfg configv1alpha1.ControllerManagerConfigSpec,
+	ctrlCfg configv1.ControllerManagerConfigSpec,
 	defaultLeaseName string,
 ) (*ControllerManager, error) {
 	m := &ControllerManager{
diff --git a/pkg/runtime/controllerutil/concurrency.go b/pkg/runtime/controllerutil/concurrency.go
index 6d5c4db..47e92b6 100644
--- a/pkg/runtime/controllerutil/concurrency.go
+++ b/pkg/runtime/controllerutil/concurrency.go
@@ -19,18 +19,18 @@ package controllerutil
 import (
 	"runtime"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 )
 
 // GetConcurrencyOrDefaultCPUFactor returns the configured concurrency, or a
 // default factor on the number of CPUs.
-func GetConcurrencyOrDefaultCPUFactor(concurrency *configv1alpha1.Concurrency, defaultFactor int) int {
+func GetConcurrencyOrDefaultCPUFactor(concurrency *configv1.Concurrency, defaultFactor int) int {
 	return GetConcurrencyOrDefaultNumber(concurrency, runtime.NumCPU()*defaultFactor)
 }
 
 // GetConcurrencyOrDefaultNumber returns the configured concurrency, or a
 // default number.
-func GetConcurrencyOrDefaultNumber(concurrency *configv1alpha1.Concurrency, defaultNumber int) int {
+func GetConcurrencyOrDefaultNumber(concurrency *configv1.Concurrency, defaultNumber int) int {
 	if concurrency != nil {
 		if num := concurrency.Workers; num > 0 {
 			return int(num)
diff --git a/pkg/runtime/httphandler/health.go b/pkg/runtime/httphandler/health.go
index 947e4bf..94768f2 100644
--- a/pkg/runtime/httphandler/health.go
+++ b/pkg/runtime/httphandler/health.go
@@ -23,7 +23,7 @@ import (
 
 	"k8s.io/klog/v2"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 )
 
 const (
@@ -32,7 +32,7 @@ const (
 )
 
 // ServeHealth adds health probe handlers to the given serve mux.
-func ServeHealth(mux *http.ServeMux, cfg *configv1alpha1.HealthSpec, mgr Manager) {
+func ServeHealth(mux *http.ServeMux, cfg *configv1.HealthSpec, mgr Manager) {
 	// Not enabled.
 	if cfg == nil {
 		return
diff --git a/pkg/runtime/httphandler/metrics.go b/pkg/runtime/httphandler/metrics.go
index a466bc9..e3aaa3f 100644
--- a/pkg/runtime/httphandler/metrics.go
+++ b/pkg/runtime/httphandler/metrics.go
@@ -23,7 +23,7 @@ import (
 	"k8s.io/klog/v2"
 	"sigs.k8s.io/controller-runtime/pkg/metrics"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 )
 
 const (
@@ -31,7 +31,7 @@ const (
 )
 
 // ServeMetrics adds Prometheus metrics handlers to the given serve mux.
-func ServeMetrics(mux *http.ServeMux, cfg *configv1alpha1.MetricsSpec) {
+func ServeMetrics(mux *http.ServeMux, cfg *configv1.MetricsSpec) {
 	// Not enabled.
 	if cfg == nil {
 		return
diff --git a/pkg/runtime/httphandler/server.go b/pkg/runtime/httphandler/server.go
index 70f857a..9863c9d 100644
--- a/pkg/runtime/httphandler/server.go
+++ b/pkg/runtime/httphandler/server.go
@@ -24,16 +24,16 @@ import (
 	"github.com/pkg/errors"
 	"k8s.io/klog/v2"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/runtime/controllermanager"
 )
 
 var (
-	defaultHTTPConfig = &configv1alpha1.HTTPSpec{
+	defaultHTTPConfig = &configv1.HTTPSpec{
 		BindAddress: ":8080",
 	}
 
-	defaultWebhooksConfig = &configv1alpha1.WebhookServerSpec{
+	defaultWebhooksConfig = &configv1.WebhookServerSpec{
 		BindAddress: ":9443",
 	}
 )
@@ -45,7 +45,7 @@ type Manager interface {
 
 // ListenAndServe listens on the given TCP address and gracefully stops when the
 // given context is canceled, setting up all HTTP handlers.
-func ListenAndServe(ctx context.Context, config *configv1alpha1.HTTPSpec, mgr Manager) error {
+func ListenAndServe(ctx context.Context, config *configv1.HTTPSpec, mgr Manager) error {
 	if config == nil {
 		config = defaultHTTPConfig
 	}
@@ -69,7 +69,7 @@ func ListenAndServe(ctx context.Context, config *configv1alpha1.HTTPSpec, mgr Ma
 // given context is canceled, setting up all webhooks handlers.
 func ListenAndServeWebhooks(
 	ctx context.Context,
-	config *configv1alpha1.WebhookServerSpec,
+	config *configv1.WebhookServerSpec,
 	webhooks []controllermanager.Webhook,
 ) error {
 	if config == nil {
diff --git a/pkg/utils/execution/job/timeout.go b/pkg/utils/execution/job/timeout.go
index 04992eb..dedb695 100644
--- a/pkg/utils/execution/job/timeout.go
+++ b/pkg/utils/execution/job/timeout.go
@@ -19,12 +19,12 @@ package job
 import (
 	"time"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	execution "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 )
 
 // GetPendingTimeout returns the pending timeout for the given Job.
-func GetPendingTimeout(rj *execution.Job, cfg *configv1alpha1.JobExecutionConfig) time.Duration {
+func GetPendingTimeout(rj *execution.Job, cfg *configv1.JobControllerConfig) time.Duration {
 	var sec int64
 	if spec := cfg.DefaultPendingTimeoutSeconds; spec != nil {
 		sec = *spec
@@ -36,7 +36,7 @@ func GetPendingTimeout(rj *execution.Job, cfg *configv1alpha1.JobExecutionConfig
 }
 
 // GetDeleteKillingTimeout returns the timeout before the controller starts killing tasks with deletion.
-func GetDeleteKillingTimeout(cfg *configv1alpha1.JobExecutionConfig) time.Duration {
+func GetDeleteKillingTimeout(cfg *configv1.JobControllerConfig) time.Duration {
 	var sec int64
 	if spec := cfg.DeleteKillingTasksTimeoutSeconds; spec != nil {
 		sec = *spec
@@ -45,7 +45,7 @@ func GetDeleteKillingTimeout(cfg *configv1alpha1.JobExecutionConfig) time.Durati
 }
 
 // GetForceDeleteKillingTimeout returns the timeout before the controller starts force deletion.
-func GetForceDeleteKillingTimeout(cfg *configv1alpha1.JobExecutionConfig) time.Duration {
+func GetForceDeleteKillingTimeout(cfg *configv1.JobControllerConfig) time.Duration {
 	var sec int64
 	if spec := cfg.ForceDeleteKillingTasksTimeoutSeconds; spec != nil {
 		sec = *spec
@@ -54,7 +54,7 @@ func GetForceDeleteKillingTimeout(cfg *configv1alpha1.JobExecutionConfig) time.D
 }
 
 // GetTTLAfterFinished returns the TTL after a Job is finished.
-func GetTTLAfterFinished(rj *execution.Job, cfg *configv1alpha1.JobExecutionConfig) time.Duration {
+func GetTTLAfterFinished(rj *execution.Job, cfg *configv1.JobControllerConfig) time.Duration {
 	var sec int64
 	if spec := cfg.DefaultTTLSecondsAfterFinished; spec != nil {
 		sec = *spec


Test patch:
diff --git a/pkg/execution/controllers/jobcontroller/reconciler_test.go b/pkg/execution/controllers/jobcontroller/reconciler_test.go
index 57a6576..aeab809 100644
--- a/pkg/execution/controllers/jobcontroller/reconciler_test.go
+++ b/pkg/execution/controllers/jobcontroller/reconciler_test.go
@@ -31,7 +31,7 @@ import (
 	"k8s.io/client-go/tools/record"
 	"k8s.io/utils/pointer"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	executiongroup "github.com/furiko-io/furiko/apis/execution"
 	execution "github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	"github.com/furiko-io/furiko/pkg/config"
@@ -60,7 +60,7 @@ func TestReconciler(t *testing.T) {
 		coreReactors     []*ktesting.SimpleReactor
 		coreActions      runtimetesting.ActionTest
 		executionActions runtimetesting.ActionTest
-		configs          map[configv1alpha1.ConfigName]runtime.Object
+		configs          map[configv1.ConfigName]runtime.Object
 	}{
 		{
 			name:   "create pod",
@@ -157,8 +157,8 @@ func TestReconciler(t *testing.T) {
 			initialPods: []*corev1.Pod{
 				fakePodPending,
 			},
-			configs: map[configv1alpha1.ConfigName]runtime.Object{
-				configv1alpha1.JobExecutionConfigName: &configv1alpha1.JobExecutionConfig{
+			configs: map[configv1.ConfigName]runtime.Object{
+				configv1.ConfigNameJobController: &configv1.JobControllerConfig{
 					DefaultPendingTimeoutSeconds: pointer.Int64(0),
 				},
 			},
@@ -250,8 +250,8 @@ func TestReconciler(t *testing.T) {
 			initialPods: []*corev1.Pod{
 				fakePodDeleting,
 			},
-			configs: map[configv1alpha1.ConfigName]runtime.Object{
-				configv1alpha1.JobExecutionConfigName: &configv1alpha1.JobExecutionConfig{
+			configs: map[configv1.ConfigName]runtime.Object{
+				configv1.ConfigNameJobController: &configv1.JobControllerConfig{
 					ForceDeleteKillingTasksTimeoutSeconds: pointer.Int64(0),
 				},
 			},
@@ -350,8 +350,8 @@ func TestReconciler(t *testing.T) {
 					runtimetesting.NewDeleteAction(resourceJob, jobNamespace, fakeJob.Name),
 				},
 			},
-			configs: map[configv1alpha1.ConfigName]runtime.Object{
-				configv1alpha1.JobExecutionConfigName: &configv1alpha1.JobExecutionConfig{
+			configs: map[configv1.ConfigName]runtime.Object{
+				configv1.ConfigNameJobController: &configv1.JobControllerConfig{
 					DefaultTTLSecondsAfterFinished: pointer.Int64(0),
 				},
 			},
@@ -378,7 +378,7 @@ func TestReconciler(t *testing.T) {
 
 			c := mock.NewContext()
 			ctrlCtx := jobcontroller.NewContextWithRecorder(c, &record.FakeRecorder{})
-			reconciler := jobcontroller.NewReconciler(ctrlCtx, &configv1alpha1.Concurrency{
+			reconciler := jobcontroller.NewReconciler(ctrlCtx, &configv1.Concurrency{
 				Workers: 1,
 			})
 			for configName, cfg := range tt.configs {
diff --git a/pkg/execution/mutation/mutation_test.go b/pkg/execution/mutation/mutation_test.go
index 1943d42..362263b 100644
--- a/pkg/execution/mutation/mutation_test.go
+++ b/pkg/execution/mutation/mutation_test.go
@@ -35,7 +35,7 @@ import (
 	"k8s.io/utils/pointer"
 	"sigs.k8s.io/yaml"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/apis/execution"
 	"github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	"github.com/furiko-io/furiko/pkg/config"
@@ -584,7 +584,7 @@ func TestMutator_MutateUpdateJobConfig(t *testing.T) {
 func TestMutator_MutateJob(t *testing.T) {
 	tests := []struct {
 		name         string
-		cfgs         map[configv1alpha1.ConfigName]runtime.Object
+		cfgs         map[configv1.ConfigName]runtime.Object
 		rj           *v1alpha1.Job
 		want         *v1alpha1.Job
 		wantErrors   string
@@ -648,7 +648,7 @@ func TestMutator_MutateJob(t *testing.T) {
 func TestMutator_MutateCreateJob(t *testing.T) {
 	tests := []struct {
 		name         string
-		cfgs         map[configv1alpha1.ConfigName]runtime.Object
+		cfgs         map[configv1.ConfigName]runtime.Object
 		rj           *v1alpha1.Job
 		want         *v1alpha1.Job
 		rjcs         []*v1alpha1.JobConfig
@@ -1201,7 +1201,7 @@ func (m *mockProvider) MakeVariablesFromTask(rj *v1alpha1.Job, task *tasks.TaskT
 	return nil
 }
 
-func setup(t *testing.T, cfgs map[configv1alpha1.ConfigName]runtime.Object, rjcs []*v1alpha1.JobConfig) *mutation.Mutator {
+func setup(t *testing.T, cfgs map[configv1.ConfigName]runtime.Object, rjcs []*v1alpha1.JobConfig) *mutation.Mutator {
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
diff --git a/pkg/execution/validation/validation_test.go b/pkg/execution/validation/validation_test.go
index 02e30de..053cbac 100644
--- a/pkg/execution/validation/validation_test.go
+++ b/pkg/execution/validation/validation_test.go
@@ -31,7 +31,7 @@ import (
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/utils/pointer"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/apis/execution/v1alpha1"
 	"github.com/furiko-io/furiko/pkg/execution/validation"
 	"github.com/furiko-io/furiko/pkg/runtime/controllercontext/mock"
@@ -220,7 +220,7 @@ func TestValidateJobConfig(t *testing.T) {
 	tests := []struct {
 		name    string
 		rjc     *v1alpha1.JobConfig
-		cfgs    map[configv1alpha1.ConfigName]runtime.Object
+		cfgs    map[configv1.ConfigName]runtime.Object
 		wantErr string
 	}{
 		{
@@ -275,8 +275,8 @@ func TestValidateJobConfig(t *testing.T) {
 					Schedule:    &scheduleSpecWithHash,
 				},
 			},
-			cfgs: map[configv1alpha1.ConfigName]runtime.Object{
-				configv1alpha1.CronExecutionConfigName: &configv1alpha1.CronExecutionConfig{
+			cfgs: map[configv1.ConfigName]runtime.Object{
+				configv1.ConfigNameCronController: &configv1.CronControllerConfig{
 					CronHashNames: pointer.Bool(false),
 				},
 			},
@@ -1038,7 +1038,7 @@ func TestValidateJobCreate(t *testing.T) {
 	}
 }
 
-func setup(t *testing.T, cfgs map[configv1alpha1.ConfigName]runtime.Object, rjcs []*v1alpha1.JobConfig) *validation.Validator {
+func setup(t *testing.T, cfgs map[configv1.ConfigName]runtime.Object, rjcs []*v1alpha1.JobConfig) *validation.Validator {
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
diff --git a/pkg/runtime/configloader/configmap_loader_test.go b/pkg/runtime/configloader/configmap_loader_test.go
index 84cced0..a0765c3 100644
--- a/pkg/runtime/configloader/configmap_loader_test.go
+++ b/pkg/runtime/configloader/configmap_loader_test.go
@@ -28,7 +28,7 @@ import (
 	fakeclientset "k8s.io/client-go/kubernetes/fake"
 	"k8s.io/utils/pointer"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/runtime/configloader"
 )
 
@@ -63,7 +63,7 @@ func (c *configMapLoaderTest) Update(ctx context.Context, client kubernetes.Inte
 			Name:      configMapName,
 		},
 		Data: map[string]string{
-			string(configv1alpha1.JobExecutionConfigName): data,
+			string(configv1.ConfigNameJobController): data,
 		},
 	}, metav1.UpdateOptions{})
 	return err
diff --git a/pkg/runtime/configloader/defaults_loader_test.go b/pkg/runtime/configloader/defaults_loader_test.go
index f8a3fb9..f4ba85b 100644
--- a/pkg/runtime/configloader/defaults_loader_test.go
+++ b/pkg/runtime/configloader/defaults_loader_test.go
@@ -25,7 +25,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/utils/pointer"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/runtime/configloader"
 )
 
@@ -34,8 +34,8 @@ func TestDefaultsLoader(t *testing.T) {
 	defer cancel()
 
 	loader := configloader.NewDefaultsLoader()
-	loader.Defaults = map[configv1alpha1.ConfigName]runtime.Object{
-		configv1alpha1.JobExecutionConfigName: &configv1alpha1.JobExecutionConfig{
+	loader.Defaults = map[configv1.ConfigName]runtime.Object{
+		configv1.ConfigNameJobController: &configv1.JobControllerConfig{
 			DefaultTTLSecondsAfterFinished: pointer.Int64(123),
 			DefaultPendingTimeoutSeconds:   pointer.Int64(234),
 		},
@@ -67,14 +67,14 @@ func TestDefaultsLoader_LoaderOverride(t *testing.T) {
 	defer cancel()
 
 	defaultLoader := configloader.NewDefaultsLoader()
-	defaultLoader.Defaults = map[configv1alpha1.ConfigName]runtime.Object{
-		configv1alpha1.JobExecutionConfigName: &configv1alpha1.JobExecutionConfig{
+	defaultLoader.Defaults = map[configv1.ConfigName]runtime.Object{
+		configv1.ConfigNameJobController: &configv1.JobControllerConfig{
 			DefaultTTLSecondsAfterFinished: pointer.Int64(123),
 			DefaultPendingTimeoutSeconds:   pointer.Int64(234),
 		},
 	}
 	mockLoader := newMockConfigLoader(MockConfig{
-		configv1alpha1.CronExecutionConfigName: map[string]interface{}{
+		configv1.ConfigNameCronController: map[string]interface{}{
 			"maxMissedSchedules": 100,
 		},
 	})
@@ -84,7 +84,7 @@ func TestDefaultsLoader_LoaderOverride(t *testing.T) {
 	err := mgr.Start(ctx)
 	assert.NoError(t, err)
 
-	// Non-empty CronExecutionConfig overridden by mock loader.
+	// Non-empty CronControllerConfig overridden by mock loader.
 	cronCfg, err := loadCronControllerConfig(mgr)
 	assert.NoError(t, err)
 	assert.Equal(t, pointer.Int64(100), cronCfg.MaxMissedSchedules)
diff --git a/pkg/runtime/configloader/manager_test.go b/pkg/runtime/configloader/manager_test.go
index 3c75afc..aaf4427 100644
--- a/pkg/runtime/configloader/manager_test.go
+++ b/pkg/runtime/configloader/manager_test.go
@@ -26,7 +26,7 @@ import (
 	"github.com/pkg/errors"
 	"k8s.io/utils/pointer"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/runtime/configloader"
 )
 
@@ -35,7 +35,7 @@ const (
 )
 
 type Config struct {
-	// Fields from JobExecutionConfig.
+	// Fields from JobControllerConfig.
 	DefaultTTLSecondsAfterFinished   int64 `json:"defaultTTLSecondsAfterFinished,omitempty"`
 	DefaultPendingTimeoutSeconds     int64 `json:"defaultPendingTimeoutSeconds,omitempty"`
 	DeleteKillingTasksTimeoutSeconds int64 `json:"deleteKillingTasksTimeoutSeconds,omitempty"`
@@ -47,7 +47,7 @@ type Config struct {
 	IntPtr  *int  `json:"intPtr,omitempty"`
 }
 
-type MockConfig map[configv1alpha1.ConfigName]configloader.Config
+type MockConfig map[configv1.ConfigName]configloader.Config
 
 type mockLoader struct {
 }
@@ -70,7 +70,7 @@ func newMockErrorLoader() *mockErrorLoader {
 	}
 }
 
-func (m *mockErrorLoader) Load(_ configv1alpha1.ConfigName) (configloader.Config, error) {
+func (m *mockErrorLoader) Load(_ configv1.ConfigName) (configloader.Config, error) {
 	return nil, errors.New("load config error")
 }
 
@@ -86,7 +86,7 @@ func newMockConfigLoader(values MockConfig) *mockConfigLoader {
 	}
 }
 
-func (m *mockConfigLoader) Load(configName configv1alpha1.ConfigName) (configloader.Config, error) {
+func (m *mockConfigLoader) Load(configName configv1.ConfigName) (configloader.Config, error) {
 	return m.values[configName], nil
 }
 
@@ -498,17 +498,17 @@ func TestConfigManager_Dynamic(t *testing.T) {
 	}
 }
 
-func loadJobControllerConfig(mgr *configloader.ConfigManager) (*configv1alpha1.JobExecutionConfig, error) {
-	var config configv1alpha1.JobExecutionConfig
-	if err := mgr.LoadAndUnmarshalConfig(configv1alpha1.JobExecutionConfigName, &config); err != nil {
+func loadJobControllerConfig(mgr *configloader.ConfigManager) (*configv1.JobControllerConfig, error) {
+	var config configv1.JobControllerConfig
+	if err := mgr.LoadAndUnmarshalConfig(configv1.ConfigNameJobController, &config); err != nil {
 		return nil, err
 	}
 	return &config, nil
 }
 
-func loadCronControllerConfig(mgr *configloader.ConfigManager) (*configv1alpha1.CronExecutionConfig, error) {
-	var config configv1alpha1.CronExecutionConfig
-	if err := mgr.LoadAndUnmarshalConfig(configv1alpha1.CronExecutionConfigName, &config); err != nil {
+func loadCronControllerConfig(mgr *configloader.ConfigManager) (*configv1.CronControllerConfig, error) {
+	var config configv1.CronControllerConfig
+	if err := mgr.LoadAndUnmarshalConfig(configv1.ConfigNameCronController, &config); err != nil {
 		return nil, err
 	}
 	return &config, nil
diff --git a/pkg/runtime/configloader/secret_loader_test.go b/pkg/runtime/configloader/secret_loader_test.go
index 38548cc..12e308f 100644
--- a/pkg/runtime/configloader/secret_loader_test.go
+++ b/pkg/runtime/configloader/secret_loader_test.go
@@ -26,7 +26,7 @@ import (
 	"k8s.io/client-go/kubernetes"
 	fakeclientset "k8s.io/client-go/kubernetes/fake"
 
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 	"github.com/furiko-io/furiko/pkg/runtime/configloader"
 )
 
@@ -54,7 +54,7 @@ func (c *secretLoaderTest) Update(ctx context.Context, client kubernetes.Interfa
 			Name:      secretName,
 		},
 		Data: map[string][]byte{
-			string(configv1alpha1.JobExecutionConfigName): []byte(base64.StdEncoding.EncodeToString([]byte(data))),
+			string(configv1.ConfigNameJobController): []byte(base64.StdEncoding.EncodeToString([]byte(data))),
 		},
 	}, metav1.UpdateOptions{})
 	return err
diff --git a/pkg/runtime/testing/concurrency.go b/pkg/runtime/testing/concurrency.go
index ab8160c..8ce80a0 100644
--- a/pkg/runtime/testing/concurrency.go
+++ b/pkg/runtime/testing/concurrency.go
@@ -17,13 +17,13 @@
 package testing
 
 import (
-	configv1alpha1 "github.com/furiko-io/furiko/apis/config/v1alpha1"
+	configv1 "github.com/furiko-io/furiko/apis/config/v1"
 )
 
 var (
 	// ReconcilerDefaultConcurrency is the default concurrency to use for tests.
 	// For simplicity, reconcilers should not have concurrent workers.
-	ReconcilerDefaultConcurrency = &configv1alpha1.Concurrency{
+	ReconcilerDefaultConcurrency = &configv1.Concurrency{
 		Workers: 1,
 	}
 )