diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml
index 14c38578a720bb1e24dbfe65e2bc04313d35f36a..9f4fcb669d4c3f4da7dd50802ef92434923e5141 100644
--- a/.github/workflows/e2e.yaml
+++ b/.github/workflows/e2e.yaml
@@ -168,6 +168,27 @@ jobs:
       - name: flux delete source git
         run: |
           /tmp/flux delete source git podinfo --silent
+      - name: flux oci
+        run: |
+          /tmp/flux create source oci podinfo-oci \
+            --url ghcr.io/stefanprodan/manifests/podinfo \
+            --tag 6.1.6 \
+            --interval 10m
+          /tmp/flux create kustomization podinfo-oci \
+            --source=OCIRepository/podinfo-oci \
+            --path="./kustomize" \
+            --prune=true \
+            --interval=5m \
+            --target-namespace=default \
+            --wait=true \
+            --health-check-timeout=3m
+          /tmp/flux reconcile source oci podinfo-oci
+          /tmp/flux suspend source oci podinfo-oci
+          /tmp/flux get sources oci
+          /tmp/flux resume source oci podinfo-oci
+          /tmp/flux export source oci podinfo-oci
+          /tmp/flux delete ks podinfo-oci --silent
+          /tmp/flux delete source oci podinfo-oci --silent
       - name: flux create tenant
         run: |
           /tmp/flux create tenant dev-team --with-namespace=apps
diff --git a/cmd/flux/create_kustomization.go b/cmd/flux/create_kustomization.go
index f808a99fea57c280f4fa33bc07c127bbfd8df537..26d1e3ea2ccfb29531382fbb0ee0de9d9a66e8a9 100644
--- a/cmd/flux/create_kustomization.go
+++ b/cmd/flux/create_kustomization.go
@@ -68,6 +68,13 @@ var createKsCmd = &cobra.Command{
     --prune=true \
     --interval=5m
 
+  # Create a Kustomization resource that references an OCIRepository
+  flux create kustomization podinfo \
+    --source=OCIRepository/podinfo \
+    --target-namespace=default \
+    --prune=true \
+    --interval=5m
+
   # Create a Kustomization resource that references a Bucket
   flux create kustomization secrets \
     --source=Bucket/secrets \
diff --git a/cmd/flux/create_source_oci.go b/cmd/flux/create_source_oci.go
new file mode 100644
index 0000000000000000000000000000000000000000..e1d26c4351e719ec30a457fbb3a6079dd9ba624e
--- /dev/null
+++ b/cmd/flux/create_source_oci.go
@@ -0,0 +1,222 @@
+/*
+Copyright 2022 The Flux authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/spf13/cobra"
+	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/wait"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/pkg/runtime/conditions"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
+
+	"github.com/fluxcd/flux2/internal/utils"
+)
+
+var createSourceOCIRepositoryCmd = &cobra.Command{
+	Use:   "oci [name]",
+	Short: "Create or update an OCIRepository",
+	Long:  `The create source oci command generates an OCIRepository resource and waits for it to be ready.`,
+	Example: `  # Create an OCIRepository for a public container image
+  flux create source oci podinfo \
+	--url=ghcr.io/stefanprodan/manifests/podinfo \
+    --tag=6.1.6 \
+    --interval=10m
+`,
+	RunE: createSourceOCIRepositoryCmdRun,
+}
+
+type sourceOCIRepositoryFlags struct {
+	url         string
+	tag         string
+	digest      string
+	secretRef   string
+	ignorePaths []string
+}
+
+var sourceOCIRepositoryArgs = sourceOCIRepositoryFlags{}
+
+func init() {
+	createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.url, "url", "", "the OCI repository URL")
+	createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.tag, "tag", "", "the OCI artifact tag")
+	createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.digest, "digest", "", "the OCI artifact digest")
+	createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, "secret-ref", "", "the name of an existing secret containing credentials")
+	createSourceOCIRepositoryCmd.Flags().StringSliceVar(&sourceOCIRepositoryArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore resources (can specify multiple paths with commas: path1,path2)")
+
+	createSourceCmd.AddCommand(createSourceOCIRepositoryCmd)
+}
+
+func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error {
+	name := args[0]
+
+	if sourceOCIRepositoryArgs.url == "" {
+		return fmt.Errorf("url is required")
+	}
+
+	if sourceOCIRepositoryArgs.tag == "" && sourceOCIRepositoryArgs.digest == "" {
+		return fmt.Errorf("--tag or --digest is required")
+	}
+
+	sourceLabels, err := parseLabels()
+	if err != nil {
+		return err
+	}
+
+	tmpDir, err := os.MkdirTemp("", name)
+	if err != nil {
+		return err
+	}
+	defer os.RemoveAll(tmpDir)
+
+	var ignorePaths *string
+	if len(sourceOCIRepositoryArgs.ignorePaths) > 0 {
+		ignorePathsStr := strings.Join(sourceOCIRepositoryArgs.ignorePaths, "\n")
+		ignorePaths = &ignorePathsStr
+	}
+
+	repository := &sourcev1.OCIRepository{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: *kubeconfigArgs.Namespace,
+			Labels:    sourceLabels,
+		},
+		Spec: sourcev1.OCIRepositorySpec{
+			URL: sourceOCIRepositoryArgs.url,
+			Interval: metav1.Duration{
+				Duration: createArgs.interval,
+			},
+			Reference: &sourcev1.OCIRepositoryRef{},
+			Ignore:    ignorePaths,
+		},
+	}
+
+	if sourceOCIRepositoryArgs.tag != "" {
+		repository.Spec.Reference.Tag = sourceOCIRepositoryArgs.tag
+	}
+	if sourceOCIRepositoryArgs.digest != "" {
+		repository.Spec.Reference.Digest = sourceOCIRepositoryArgs.digest
+	}
+
+	if createSourceArgs.fetchTimeout > 0 {
+		repository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout}
+	}
+
+	if sourceOCIRepositoryArgs.secretRef != "" {
+		repository.Spec.SecretRef = &meta.LocalObjectReference{
+			Name: sourceOCIRepositoryArgs.secretRef,
+		}
+	}
+
+	if createArgs.export {
+		return printExport(exportOCIRepository(repository))
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
+	defer cancel()
+
+	kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
+	if err != nil {
+		return err
+	}
+
+	logger.Actionf("applying OCIRepository")
+	namespacedName, err := upsertOCIRepository(ctx, kubeClient, repository)
+	if err != nil {
+		return err
+	}
+
+	logger.Waitingf("waiting for OCIRepository reconciliation")
+	if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout,
+		isOCIRepositoryReady(ctx, kubeClient, namespacedName, repository)); err != nil {
+		return err
+	}
+	logger.Successf("OCIRepository reconciliation completed")
+
+	if repository.Status.Artifact == nil {
+		return fmt.Errorf("no artifact was found")
+	}
+	logger.Successf("fetched revision: %s", repository.Status.Artifact.Revision)
+	return nil
+}
+
+func upsertOCIRepository(ctx context.Context, kubeClient client.Client,
+	ociRepository *sourcev1.OCIRepository) (types.NamespacedName, error) {
+	namespacedName := types.NamespacedName{
+		Namespace: ociRepository.GetNamespace(),
+		Name:      ociRepository.GetName(),
+	}
+
+	var existing sourcev1.OCIRepository
+	err := kubeClient.Get(ctx, namespacedName, &existing)
+	if err != nil {
+		if errors.IsNotFound(err) {
+			if err := kubeClient.Create(ctx, ociRepository); err != nil {
+				return namespacedName, err
+			} else {
+				logger.Successf("OCIRepository created")
+				return namespacedName, nil
+			}
+		}
+		return namespacedName, err
+	}
+
+	existing.Labels = ociRepository.Labels
+	existing.Spec = ociRepository.Spec
+	if err := kubeClient.Update(ctx, &existing); err != nil {
+		return namespacedName, err
+	}
+	ociRepository = &existing
+	logger.Successf("OCIRepository updated")
+	return namespacedName, nil
+}
+
+func isOCIRepositoryReady(ctx context.Context, kubeClient client.Client,
+	namespacedName types.NamespacedName, ociRepository *sourcev1.OCIRepository) wait.ConditionFunc {
+	return func() (bool, error) {
+		err := kubeClient.Get(ctx, namespacedName, ociRepository)
+		if err != nil {
+			return false, err
+		}
+
+		if c := conditions.Get(ociRepository, meta.ReadyCondition); c != nil {
+			// Confirm the Ready condition we are observing is for the
+			// current generation
+			if c.ObservedGeneration != ociRepository.GetGeneration() {
+				return false, nil
+			}
+
+			// Further check the Status
+			switch c.Status {
+			case metav1.ConditionTrue:
+				return true, nil
+			case metav1.ConditionFalse:
+				return false, fmt.Errorf(c.Message)
+			}
+		}
+		return false, nil
+	}
+}
diff --git a/cmd/flux/delete_source_oci.go b/cmd/flux/delete_source_oci.go
new file mode 100644
index 0000000000000000000000000000000000000000..1d641bbf51206710a4c231171fd8439d61d73d0d
--- /dev/null
+++ b/cmd/flux/delete_source_oci.go
@@ -0,0 +1,40 @@
+/*
+Copyright 2022 The Flux authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+	"github.com/spf13/cobra"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
+)
+
+var deleteSourceOCIRepositoryCmd = &cobra.Command{
+	Use:   "oci [name]",
+	Short: "Delete a OCIRepository source",
+	Long:  "The delete source oci command deletes the given OCIRepository from the cluster.",
+	Example: `  # Delete an OCIRepository 
+  flux delete source oci podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),
+	RunE: deleteCommand{
+		apiType: ociRepositoryType,
+		object:  universalAdapter{&sourcev1.OCIRepository{}},
+	}.run,
+}
+
+func init() {
+	deleteSourceCmd.AddCommand(deleteSourceOCIRepositoryCmd)
+}
diff --git a/cmd/flux/export_source_oci.go b/cmd/flux/export_source_oci.go
new file mode 100644
index 0000000000000000000000000000000000000000..c6ea9c77034dd72be10fb342435ecb644df15344
--- /dev/null
+++ b/cmd/flux/export_source_oci.go
@@ -0,0 +1,92 @@
+/*
+Copyright 2022 The Flux authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+	"github.com/spf13/cobra"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
+)
+
+var exportSourceOCIRepositoryCmd = &cobra.Command{
+	Use:   "oci [name]",
+	Short: "Export OCIRepository sources in YAML format",
+	Long:  "The export source oci command exports one or all OCIRepository sources in YAML format.",
+	Example: `  # Export all OCIRepository sources
+  flux export source oci --all > sources.yaml
+
+  # Export a OCIRepository including the static credentials
+  flux export source oci my-app --with-credentials > source.yaml`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),
+	RunE: exportWithSecretCommand{
+		list:   ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},
+		object: ociRepositoryAdapter{&sourcev1.OCIRepository{}},
+	}.run,
+}
+
+func init() {
+	exportSourceCmd.AddCommand(exportSourceOCIRepositoryCmd)
+}
+
+func exportOCIRepository(source *sourcev1.OCIRepository) interface{} {
+	gvk := sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)
+	export := sourcev1.OCIRepository{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       gvk.Kind,
+			APIVersion: gvk.GroupVersion().String(),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:        source.Name,
+			Namespace:   source.Namespace,
+			Labels:      source.Labels,
+			Annotations: source.Annotations,
+		},
+		Spec: source.Spec,
+	}
+	return export
+}
+
+func getOCIRepositorySecret(source *sourcev1.OCIRepository) *types.NamespacedName {
+	if source.Spec.SecretRef != nil {
+		namespacedName := types.NamespacedName{
+			Namespace: source.Namespace,
+			Name:      source.Spec.SecretRef.Name,
+		}
+
+		return &namespacedName
+	}
+
+	return nil
+}
+
+func (ex ociRepositoryAdapter) secret() *types.NamespacedName {
+	return getOCIRepositorySecret(ex.OCIRepository)
+}
+
+func (ex ociRepositoryListAdapter) secretItem(i int) *types.NamespacedName {
+	return getOCIRepositorySecret(&ex.OCIRepositoryList.Items[i])
+}
+
+func (ex ociRepositoryAdapter) export() interface{} {
+	return exportOCIRepository(ex.OCIRepository)
+}
+
+func (ex ociRepositoryListAdapter) exportItem(i int) interface{} {
+	return exportOCIRepository(&ex.OCIRepositoryList.Items[i])
+}
diff --git a/cmd/flux/get_source_all.go b/cmd/flux/get_source_all.go
index b9b62555329813600a0b39973e05ea9571427784..ac6560fe83417b3be03ef29dcb8242ce15704d51 100644
--- a/cmd/flux/get_source_all.go
+++ b/cmd/flux/get_source_all.go
@@ -40,6 +40,10 @@ var getSourceAllCmd = &cobra.Command{
 		}
 
 		var allSourceCmd = []getCommand{
+			{
+				apiType: ociRepositoryType,
+				list:    &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},
+			},
 			{
 				apiType: bucketType,
 				list:    &bucketListAdapter{&sourcev1.BucketList{}},
diff --git a/cmd/flux/get_source_oci.go b/cmd/flux/get_source_oci.go
new file mode 100644
index 0000000000000000000000000000000000000000..8f948d8122941ccd3a4c59cfe1d2c68f3286c425
--- /dev/null
+++ b/cmd/flux/get_source_oci.go
@@ -0,0 +1,98 @@
+/*
+Copyright 2022 The Flux authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/spf13/cobra"
+	"k8s.io/apimachinery/pkg/runtime"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
+)
+
+var getSourceOCIRepositoryCmd = &cobra.Command{
+	Use:   "oci",
+	Short: "Get OCIRepository status",
+	Long:  "The get sources oci command prints the status of the OCIRepository sources.",
+	Example: `  # List all OCIRepositories and their status
+  flux get sources oci
+
+ # List OCIRepositories from all namespaces
+  flux get sources oci --all-namespaces`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),
+	RunE: func(cmd *cobra.Command, args []string) error {
+		get := getCommand{
+			apiType: ociRepositoryType,
+			list:    &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},
+			funcMap: make(typeMap),
+		}
+
+		err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
+			o, ok := obj.(*sourcev1.OCIRepository)
+			if !ok {
+				return nil, fmt.Errorf("impossible to cast type %#v to OCIRepository", obj)
+			}
+
+			sink := &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{
+				Items: []sourcev1.OCIRepository{
+					*o,
+				}}}
+			return sink, nil
+		})
+
+		if err != nil {
+			return err
+		}
+
+		if err := get.run(cmd, args); err != nil {
+			return err
+		}
+
+		return nil
+	},
+}
+
+func init() {
+	getSourceCmd.AddCommand(getSourceOCIRepositoryCmd)
+}
+
+func (a *ociRepositoryListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
+	item := a.Items[i]
+	var revision string
+	if item.GetArtifact() != nil {
+		revision = item.GetArtifact().Revision
+	}
+	status, msg := statusAndMessage(item.Status.Conditions)
+	return append(nameColumns(&item, includeNamespace, includeKind),
+		revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
+}
+
+func (a ociRepositoryListAdapter) headers(includeNamespace bool) []string {
+	headers := []string{"Name", "Revision", "Suspended", "Ready", "Message"}
+	if includeNamespace {
+		headers = append([]string{"Namespace"}, headers...)
+	}
+	return headers
+}
+
+func (a ociRepositoryListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
+	item := a.Items[i]
+	return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
+}
diff --git a/cmd/flux/reconcile_kustomization.go b/cmd/flux/reconcile_kustomization.go
index 05035a50352aa69feb8f29e7e91320ce614ffe8a..cdc6e7ac859c1e01d2b7a8648ad6699232264c95 100644
--- a/cmd/flux/reconcile_kustomization.go
+++ b/cmd/flux/reconcile_kustomization.go
@@ -65,6 +65,11 @@ func (obj kustomizationAdapter) reconcileSource() bool {
 func (obj kustomizationAdapter) getSource() (reconcileCommand, types.NamespacedName) {
 	var cmd reconcileCommand
 	switch obj.Spec.SourceRef.Kind {
+	case sourcev1.OCIRepositoryKind:
+		cmd = reconcileCommand{
+			apiType: ociRepositoryType,
+			object:  ociRepositoryAdapter{&sourcev1.OCIRepository{}},
+		}
 	case sourcev1.GitRepositoryKind:
 		cmd = reconcileCommand{
 			apiType: gitRepositoryType,
diff --git a/cmd/flux/reconcile_source_oci.go b/cmd/flux/reconcile_source_oci.go
new file mode 100644
index 0000000000000000000000000000000000000000..17df7174f82d96c95f8c549f83a2d79d50b8efbb
--- /dev/null
+++ b/cmd/flux/reconcile_source_oci.go
@@ -0,0 +1,50 @@
+/*
+Copyright 2020 The Flux authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+	"fmt"
+
+	"github.com/spf13/cobra"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
+)
+
+var reconcileSourceOCIRepositoryCmd = &cobra.Command{
+	Use:   "oci [name]",
+	Short: "Reconcile an OCIRepository",
+	Long:  `The reconcile source command triggers a reconciliation of an OCIRepository resource and waits for it to finish.`,
+	Example: `  # Trigger a reconciliation for an existing source
+  flux reconcile source oci podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),
+	RunE: reconcileCommand{
+		apiType: ociRepositoryType,
+		object:  ociRepositoryAdapter{&sourcev1.OCIRepository{}},
+	}.run,
+}
+
+func init() {
+	reconcileSourceCmd.AddCommand(reconcileSourceOCIRepositoryCmd)
+}
+
+func (obj ociRepositoryAdapter) lastHandledReconcileRequest() string {
+	return obj.Status.GetLastHandledReconcileRequest()
+}
+
+func (obj ociRepositoryAdapter) successMessage() string {
+	return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision)
+}
diff --git a/cmd/flux/resume_source_oci.go b/cmd/flux/resume_source_oci.go
new file mode 100644
index 0000000000000000000000000000000000000000..42ad48ab1d162acd2c8179ac947fdc4ff0806733
--- /dev/null
+++ b/cmd/flux/resume_source_oci.go
@@ -0,0 +1,53 @@
+/*
+Copyright 2020 The Flux authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+	"github.com/spf13/cobra"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
+)
+
+var resumeSourceOCIRepositoryCmd = &cobra.Command{
+	Use:   "oci [name]",
+	Short: "Resume a suspended OCIRepository",
+	Long:  `The resume command marks a previously suspended OCIRepository resource for reconciliation and waits for it to finish.`,
+	Example: `  # Resume reconciliation for an existing OCIRepository
+  flux resume source oci podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),
+	RunE: resumeCommand{
+		apiType: ociRepositoryType,
+		object:  ociRepositoryAdapter{&sourcev1.OCIRepository{}},
+		list:    ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},
+	}.run,
+}
+
+func init() {
+	resumeSourceCmd.AddCommand(resumeSourceOCIRepositoryCmd)
+}
+
+func (obj ociRepositoryAdapter) getObservedGeneration() int64 {
+	return obj.OCIRepository.Status.ObservedGeneration
+}
+
+func (obj ociRepositoryAdapter) setUnsuspended() {
+	obj.OCIRepository.Spec.Suspend = false
+}
+
+func (a ociRepositoryListAdapter) resumeItem(i int) resumable {
+	return &ociRepositoryAdapter{&a.OCIRepositoryList.Items[i]}
+}
diff --git a/cmd/flux/source.go b/cmd/flux/source.go
index b6c9077cbe8c1525c9b9cb1a254e00d1f13691f6..4177c159a5465df12212a56a690d70e045800285 100644
--- a/cmd/flux/source.go
+++ b/cmd/flux/source.go
@@ -26,6 +26,40 @@ import (
 // the various commands. The *List adapters implement len(), since
 // it's used in at least a couple of commands.
 
+// sourcev1.ociRepository
+
+var ociRepositoryType = apiType{
+	kind:         sourcev1.OCIRepositoryKind,
+	humanKind:    "source oci",
+	groupVersion: sourcev1.GroupVersion,
+}
+
+type ociRepositoryAdapter struct {
+	*sourcev1.OCIRepository
+}
+
+func (a ociRepositoryAdapter) asClientObject() client.Object {
+	return a.OCIRepository
+}
+
+func (a ociRepositoryAdapter) deepCopyClientObject() client.Object {
+	return a.OCIRepository.DeepCopy()
+}
+
+// sourcev1.OCIRepositoryList
+
+type ociRepositoryListAdapter struct {
+	*sourcev1.OCIRepositoryList
+}
+
+func (a ociRepositoryListAdapter) asClientList() client.ObjectList {
+	return a.OCIRepositoryList
+}
+
+func (a ociRepositoryListAdapter) len() int {
+	return len(a.OCIRepositoryList.Items)
+}
+
 // sourcev1.Bucket
 
 var bucketType = apiType{
diff --git a/cmd/flux/suspend_source_oci.go b/cmd/flux/suspend_source_oci.go
new file mode 100644
index 0000000000000000000000000000000000000000..06ab4c6c3595a2d03db4bfcdaab433be36629ecf
--- /dev/null
+++ b/cmd/flux/suspend_source_oci.go
@@ -0,0 +1,53 @@
+/*
+Copyright 2022 The Flux authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+	"github.com/spf13/cobra"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
+)
+
+var suspendSourceOCIRepositoryCmd = &cobra.Command{
+	Use:   "oci [name]",
+	Short: "Suspend reconciliation of a OCIRepository",
+	Long:  "The suspend command disables the reconciliation of an OCIRepository resource.",
+	Example: `  # Suspend reconciliation for an existing OCIRepository
+  flux suspend source oci podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)),
+	RunE: suspendCommand{
+		apiType: ociRepositoryType,
+		object:  ociRepositoryAdapter{&sourcev1.OCIRepository{}},
+		list:    ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}},
+	}.run,
+}
+
+func init() {
+	suspendSourceCmd.AddCommand(suspendSourceOCIRepositoryCmd)
+}
+
+func (obj ociRepositoryAdapter) isSuspended() bool {
+	return obj.OCIRepository.Spec.Suspend
+}
+
+func (obj ociRepositoryAdapter) setSuspended() {
+	obj.OCIRepository.Spec.Suspend = true
+}
+
+func (a ociRepositoryListAdapter) item(i int) suspendable {
+	return &ociRepositoryAdapter{&a.OCIRepositoryList.Items[i]}
+}
diff --git a/internal/flags/kustomization_source.go b/internal/flags/kustomization_source.go
index b506230d0a74e38647830deda2d513122d69eef5..f3455b25fb91a5f077ee09e4212ea727bbe75616 100644
--- a/internal/flags/kustomization_source.go
+++ b/internal/flags/kustomization_source.go
@@ -25,7 +25,7 @@ import (
 	"github.com/fluxcd/flux2/internal/utils"
 )
 
-var supportedKustomizationSourceKinds = []string{sourcev1.GitRepositoryKind, sourcev1.BucketKind}
+var supportedKustomizationSourceKinds = []string{sourcev1.OCIRepositoryKind, sourcev1.GitRepositoryKind, sourcev1.BucketKind}
 
 type KustomizationSource struct {
 	Kind      string
diff --git a/internal/oci/build.go b/internal/oci/build.go
index ba508c0fdf3359f7954bda863931750287adbc68..3deb3fc69d9c89f001c6df39e58a133fd93f6b01 100644
--- a/internal/oci/build.go
+++ b/internal/oci/build.go
@@ -28,7 +28,7 @@ import (
 
 // Build archives the given directory as a tarball to the given local path.
 // While archiving, any environment specific data (for example, the user and group name) is stripped from file headers.
-func Build(artifactPath, sourceDir string) error {
+func Build(artifactPath, sourceDir string) (err error) {
 	if f, err := os.Stat(sourceDir); os.IsNotExist(err) || !f.IsDir() {
 		return fmt.Errorf("invalid source dir path: %s", sourceDir)
 	}