diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
index 4a94c12812374bd07a75672f40ea6736c033a438..d0bac8f57732dddfe3b5743ba62b322e3114cb84 100644
--- a/.github/workflows/docs.yaml
+++ b/.github/workflows/docs.yaml
@@ -26,6 +26,7 @@ jobs:
             curl -# -f "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/spec/v1alpha1/gitrepositories.md" > docs/components/source/gitrepositories.md
             curl -# -f "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/spec/v1alpha1/helmrepositories.md" > docs/components/source/helmrepositories.md
             curl -# -f "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/spec/v1alpha1/helmcharts.md" > docs/components/source/helmcharts.md
+            curl -# -f "https://raw.githubusercontent.com/fluxcd/source-controller/$SOURCE_VER/docs/spec/v1alpha1/buckets.md" > docs/components/source/buckets.md
           }
 
           {
diff --git a/cmd/gotk/create_source_bucket.go b/cmd/gotk/create_source_bucket.go
new file mode 100644
index 0000000000000000000000000000000000000000..c7f072cb1f980dd8ad859dd0da7d05a9f8ae85d4
--- /dev/null
+++ b/cmd/gotk/create_source_bucket.go
@@ -0,0 +1,230 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+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"
+	"io/ioutil"
+	"os"
+
+	"github.com/spf13/cobra"
+	corev1 "k8s.io/api/core/v1"
+	"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"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
+)
+
+var createSourceBucketCmd = &cobra.Command{
+	Use:   "bucket [name]",
+	Short: "Create or update a Bucket source",
+	Long: `
+The create source bucket command generates a Bucket resource and waits for it to be downloaded.
+For Buckets with static authentication, the credentials are stored in a Kubernetes secret.`,
+	Example: `  # Create a source from a Buckets using static authentication
+  gotk create source bucket podinfo \
+	--bucket-name=podinfo \
+    --endpoint=minio.minio.svc.cluster.local:9000 \
+	--insecure=true \
+	--access-key=myaccesskey \
+	--secret-key=mysecretkey \
+    --interval=10m
+
+  # Create a source from an Amazon S3 Bucket using IAM authentication
+  gotk create source bucket podinfo \
+	--bucket-name=podinfo \
+	--provider=aws \
+    --endpoint=s3.amazonaws.com \
+	--region=us-east-1 \
+    --interval=10m
+`,
+	RunE: createSourceBucketCmdRun,
+}
+
+var (
+	sourceBucketName      string
+	sourceBucketProvider  string
+	sourceBucketEndpoint  string
+	sourceBucketAccessKey string
+	sourceBucketSecretKey string
+	sourceBucketRegion    string
+	sourceBucketInsecure  bool
+)
+
+func init() {
+	createSourceBucketCmd.Flags().StringVar(&sourceBucketProvider, "provider", sourcev1.GenericBucketProvider, "the S3 compatible storage provider name, can be 'generic' or 'aws'")
+	createSourceBucketCmd.Flags().StringVar(&sourceBucketName, "bucket-name", "", "the bucket name")
+	createSourceBucketCmd.Flags().StringVar(&sourceBucketEndpoint, "endpoint", "", "the bucket endpoint address")
+	createSourceBucketCmd.Flags().StringVar(&sourceBucketAccessKey, "access-key", "", "the bucket access key")
+	createSourceBucketCmd.Flags().StringVar(&sourceBucketSecretKey, "secret-key", "", "the bucket secret key")
+	createSourceBucketCmd.Flags().StringVar(&sourceBucketRegion, "region", "", "the bucket region")
+	createSourceBucketCmd.Flags().BoolVar(&sourceBucketInsecure, "insecure", false, "for when connecting to a non-TLS S3 HTTP endpoint")
+
+	createSourceCmd.AddCommand(createSourceBucketCmd)
+}
+
+func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
+	if len(args) < 1 {
+		return fmt.Errorf("source name is required")
+	}
+	name := args[0]
+	secretName := fmt.Sprintf("bucket-%s", name)
+
+	if !utils.containsItemString(supportedSourceBucketProviders, sourceBucketProvider) {
+		return fmt.Errorf("bucket provider %s is not supported, can be %v",
+			sourceBucketProvider, supportedSourceBucketProviders)
+	}
+
+	if sourceBucketName == "" {
+		return fmt.Errorf("bucket-name is required")
+	}
+
+	if sourceBucketEndpoint == "" {
+		return fmt.Errorf("endpoint is required")
+	}
+
+	sourceLabels, err := parseLabels()
+	if err != nil {
+		return err
+	}
+
+	tmpDir, err := ioutil.TempDir("", name)
+	if err != nil {
+		return err
+	}
+	defer os.RemoveAll(tmpDir)
+
+	bucket := sourcev1.Bucket{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: namespace,
+			Labels:    sourceLabels,
+		},
+		Spec: sourcev1.BucketSpec{
+			BucketName: sourceBucketName,
+			Provider:   sourceBucketProvider,
+			Insecure:   sourceBucketInsecure,
+			Endpoint:   sourceBucketEndpoint,
+			Region:     sourceBucketRegion,
+			Interval: metav1.Duration{
+				Duration: interval,
+			},
+		},
+	}
+
+	if export {
+		return exportBucket(bucket)
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+
+	kubeClient, err := utils.kubeClient(kubeconfig)
+	if err != nil {
+		return err
+	}
+
+	logger.Generatef("generating source")
+
+	secret := corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      secretName,
+			Namespace: namespace,
+		},
+		StringData: map[string]string{},
+	}
+
+	if sourceBucketAccessKey != "" && sourceBucketSecretKey != "" {
+		secret.StringData["accesskey"] = sourceBucketAccessKey
+		secret.StringData["secretkey"] = sourceBucketSecretKey
+	}
+
+	if len(secret.StringData) > 0 {
+		logger.Actionf("applying secret with the bucket credentials")
+		if err := upsertSecret(ctx, kubeClient, secret); err != nil {
+			return err
+		}
+		bucket.Spec.SecretRef = &corev1.LocalObjectReference{
+			Name: secretName,
+		}
+		logger.Successf("authentication configured")
+	}
+
+	logger.Actionf("applying source")
+	if err := upsertBucket(ctx, kubeClient, bucket); err != nil {
+		return err
+	}
+
+	logger.Waitingf("waiting for download")
+	if err := wait.PollImmediate(pollInterval, timeout,
+		isBucketReady(ctx, kubeClient, name, namespace)); err != nil {
+		return err
+	}
+
+	logger.Successf("download completed")
+
+	namespacedName := types.NamespacedName{
+		Namespace: namespace,
+		Name:      name,
+	}
+	err = kubeClient.Get(ctx, namespacedName, &bucket)
+	if err != nil {
+		return fmt.Errorf("could not retrieve bucket: %w", err)
+	}
+
+	if bucket.Status.Artifact != nil {
+		logger.Successf("fetched revision: %s", bucket.Status.Artifact.Revision)
+	} else {
+		return fmt.Errorf("download failed, artifact not found")
+	}
+
+	return nil
+}
+
+func upsertBucket(ctx context.Context, kubeClient client.Client, bucket sourcev1.Bucket) error {
+	namespacedName := types.NamespacedName{
+		Namespace: bucket.GetNamespace(),
+		Name:      bucket.GetName(),
+	}
+
+	var existing sourcev1.Bucket
+	err := kubeClient.Get(ctx, namespacedName, &existing)
+	if err != nil {
+		if errors.IsNotFound(err) {
+			if err := kubeClient.Create(ctx, &bucket); err != nil {
+				return err
+			} else {
+				logger.Successf("source created")
+				return nil
+			}
+		}
+		return err
+	}
+
+	existing.Labels = bucket.Labels
+	existing.Spec = bucket.Spec
+	if err := kubeClient.Update(ctx, &existing); err != nil {
+		return err
+	}
+
+	logger.Successf("source updated")
+	return nil
+}
diff --git a/cmd/gotk/delete_source_bucket.go b/cmd/gotk/delete_source_bucket.go
new file mode 100644
index 0000000000000000000000000000000000000000..c1d8c94c61a7f3452ab48184dd7921b3b289ba31
--- /dev/null
+++ b/cmd/gotk/delete_source_bucket.go
@@ -0,0 +1,86 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+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"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
+	"github.com/manifoldco/promptui"
+	"github.com/spf13/cobra"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+var deleteSourceBucketCmd = &cobra.Command{
+	Use:   "bucket [name]",
+	Short: "Delete a Bucket source",
+	Long:  "The delete source bucket command deletes the given Bucket from the cluster.",
+	Example: `  # Delete a Bucket source
+  gotk delete source bucket podinfo
+`,
+	RunE: deleteSourceBucketCmdRun,
+}
+
+func init() {
+	deleteSourceCmd.AddCommand(deleteSourceBucketCmd)
+}
+
+func deleteSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
+	if len(args) < 1 {
+		return fmt.Errorf("name is required")
+	}
+	name := args[0]
+
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+
+	kubeClient, err := utils.kubeClient(kubeconfig)
+	if err != nil {
+		return err
+	}
+
+	namespacedName := types.NamespacedName{
+		Namespace: namespace,
+		Name:      name,
+	}
+
+	var bucket sourcev1.Bucket
+	err = kubeClient.Get(ctx, namespacedName, &bucket)
+	if err != nil {
+		return err
+	}
+
+	if !deleteSilent {
+		prompt := promptui.Prompt{
+			Label:     "Are you sure you want to delete this source",
+			IsConfirm: true,
+		}
+		if _, err := prompt.Run(); err != nil {
+			return fmt.Errorf("aborting")
+		}
+	}
+
+	logger.Actionf("deleting source %s in %s namespace", name, namespace)
+	err = kubeClient.Delete(ctx, &bucket)
+	if err != nil {
+		return err
+	}
+	logger.Successf("source deleted")
+
+	return nil
+}
diff --git a/cmd/gotk/export_source_bucket.go b/cmd/gotk/export_source_bucket.go
new file mode 100644
index 0000000000000000000000000000000000000000..904ddc55895fee84b31a08fb8fec1775de47721d
--- /dev/null
+++ b/cmd/gotk/export_source_bucket.go
@@ -0,0 +1,166 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+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"
+
+	"github.com/spf13/cobra"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/yaml"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
+)
+
+var exportSourceBucketCmd = &cobra.Command{
+	Use:   "bucket [name]",
+	Short: "Export Bucket sources in YAML format",
+	Long:  "The export source git command exports on or all Bucket sources in YAML format.",
+	Example: `  # Export all Bucket sources
+  gotk export source bucket --all > sources.yaml
+
+  # Export a Bucket source including the static credentials
+  gotk export source bucket my-bucket --with-credentials > source.yaml
+`,
+	RunE: exportSourceBucketCmdRun,
+}
+
+func init() {
+	exportSourceCmd.AddCommand(exportSourceBucketCmd)
+}
+
+func exportSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
+	if !exportAll && len(args) < 1 {
+		return fmt.Errorf("name is required")
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+
+	kubeClient, err := utils.kubeClient(kubeconfig)
+	if err != nil {
+		return err
+	}
+
+	if exportAll {
+		var list sourcev1.BucketList
+		err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
+		if err != nil {
+			return err
+		}
+
+		if len(list.Items) == 0 {
+			logger.Failuref("no source found in %s namespace", namespace)
+			return nil
+		}
+
+		for _, bucket := range list.Items {
+			if err := exportBucket(bucket); err != nil {
+				return err
+			}
+			if exportSourceWithCred {
+				if err := exportBucketCredentials(ctx, kubeClient, bucket); err != nil {
+					return err
+				}
+			}
+		}
+	} else {
+		name := args[0]
+		namespacedName := types.NamespacedName{
+			Namespace: namespace,
+			Name:      name,
+		}
+		var bucket sourcev1.Bucket
+		err = kubeClient.Get(ctx, namespacedName, &bucket)
+		if err != nil {
+			return err
+		}
+		if err := exportBucket(bucket); err != nil {
+			return err
+		}
+		if exportSourceWithCred {
+			return exportBucketCredentials(ctx, kubeClient, bucket)
+		}
+	}
+	return nil
+}
+
+func exportBucket(source sourcev1.Bucket) error {
+	gvk := sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)
+	export := sourcev1.Bucket{
+		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,
+	}
+
+	data, err := yaml.Marshal(export)
+	if err != nil {
+		return err
+	}
+
+	fmt.Println("---")
+	fmt.Println(resourceToString(data))
+	return nil
+}
+
+func exportBucketCredentials(ctx context.Context, kubeClient client.Client, source sourcev1.Bucket) error {
+	if source.Spec.SecretRef != nil {
+		namespacedName := types.NamespacedName{
+			Namespace: source.Namespace,
+			Name:      source.Spec.SecretRef.Name,
+		}
+		var cred corev1.Secret
+		err := kubeClient.Get(ctx, namespacedName, &cred)
+		if err != nil {
+			return fmt.Errorf("failed to retrieve secret %s, error: %w", namespacedName.Name, err)
+		}
+
+		exported := corev1.Secret{
+			TypeMeta: metav1.TypeMeta{
+				APIVersion: "v1",
+				Kind:       "Secret",
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      namespacedName.Name,
+				Namespace: namespacedName.Namespace,
+			},
+			Data: cred.Data,
+			Type: cred.Type,
+		}
+
+		data, err := yaml.Marshal(exported)
+		if err != nil {
+			return err
+		}
+
+		fmt.Println("---")
+		fmt.Println(resourceToString(data))
+	}
+	return nil
+}
diff --git a/cmd/gotk/get_source_bucket.go b/cmd/gotk/get_source_bucket.go
new file mode 100644
index 0000000000000000000000000000000000000000..6174de49cf6451a78593eab25d760ee23258ff30
--- /dev/null
+++ b/cmd/gotk/get_source_bucket.go
@@ -0,0 +1,80 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+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"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
+	"github.com/spf13/cobra"
+	corev1 "k8s.io/api/core/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+var getSourceBucketCmd = &cobra.Command{
+	Use:   "bucket",
+	Short: "Get Bucket source statuses",
+	Long:  "The get sources bucket command prints the status of the Bucket sources.",
+	Example: `  # List all Buckets and their status
+  gotk get sources bucket
+`,
+	RunE: getSourceBucketCmdRun,
+}
+
+func init() {
+	getSourceCmd.AddCommand(getSourceBucketCmd)
+}
+
+func getSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+
+	kubeClient, err := utils.kubeClient(kubeconfig)
+	if err != nil {
+		return err
+	}
+
+	var list sourcev1.BucketList
+	err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
+	if err != nil {
+		return err
+	}
+
+	if len(list.Items) == 0 {
+		logger.Failuref("no sources found in %s namespace", namespace)
+		return nil
+	}
+
+	for _, source := range list.Items {
+		isInitialized := false
+		for _, condition := range source.Status.Conditions {
+			if condition.Type == sourcev1.ReadyCondition {
+				if condition.Status != corev1.ConditionFalse {
+					logger.Successf("%s last fetched revision: %s", source.GetName(), source.Status.Artifact.Revision)
+				} else {
+					logger.Failuref("%s %s", source.GetName(), condition.Message)
+				}
+				isInitialized = true
+				break
+			}
+		}
+		if !isInitialized {
+			logger.Failuref("%s is not ready", source.GetName())
+		}
+	}
+	return nil
+}
diff --git a/cmd/gotk/main.go b/cmd/gotk/main.go
index 3ca762daa6fab375ab1773cf79ab9e948375ac8b..7087b818d1296aa9c139b175bdc7b4af45d62dcf 100644
--- a/cmd/gotk/main.go
+++ b/cmd/gotk/main.go
@@ -106,14 +106,15 @@ var (
 )
 
 var (
-	defaultComponents             = []string{"source-controller", "kustomize-controller", "helm-controller", "notification-controller"}
-	defaultVersion                = "latest"
-	defaultNamespace              = "gitops-system"
-	defaultNotification           = "notification-controller"
-	supportedArch                 = []string{"amd64", "arm", "arm64"}
-	supportedDecryptionProviders  = []string{"sops"}
-	supportedHelmChartSourceKinds = []string{sourcev1.HelmRepositoryKind, sourcev1.GitRepositoryKind}
-	supportedLogLevels            = []string{"debug", "info", "error"}
+	defaultComponents              = []string{"source-controller", "kustomize-controller", "helm-controller", "notification-controller"}
+	defaultVersion                 = "latest"
+	defaultNamespace               = "gitops-system"
+	defaultNotification            = "notification-controller"
+	supportedLogLevels             = []string{"debug", "info", "error"}
+	supportedArch                  = []string{"amd64", "arm", "arm64"}
+	supportedDecryptionProviders   = []string{"sops"}
+	supportedHelmChartSourceKinds  = []string{sourcev1.HelmRepositoryKind, sourcev1.GitRepositoryKind}
+	supportedSourceBucketProviders = []string{sourcev1.GenericBucketProvider, sourcev1.AmazonBucketProvider}
 )
 
 func init() {
diff --git a/cmd/gotk/reconcile_source_bucket.go b/cmd/gotk/reconcile_source_bucket.go
new file mode 100644
index 0000000000000000000000000000000000000000..fb4fbbbf69beac9467af7e8138760f3315457a3d
--- /dev/null
+++ b/cmd/gotk/reconcile_source_bucket.go
@@ -0,0 +1,131 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+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"
+	"time"
+
+	"github.com/spf13/cobra"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/wait"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	consts "github.com/fluxcd/pkg/runtime"
+	sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
+)
+
+var reconcileSourceBucketCmd = &cobra.Command{
+	Use:   "bucket [name]",
+	Short: "Reconcile a Bucket source",
+	Long:  `The reconcile source command triggers a reconciliation of a Bucket resource and waits for it to finish.`,
+	Example: `  # Trigger a reconciliation for an existing source
+  gotk reconcile source bucket podinfo
+`,
+	RunE: syncSourceBucketCmdRun,
+}
+
+func init() {
+	reconcileSourceCmd.AddCommand(reconcileSourceBucketCmd)
+}
+
+func syncSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
+	if len(args) < 1 {
+		return fmt.Errorf("source name is required")
+	}
+	name := args[0]
+
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+
+	kubeClient, err := utils.kubeClient(kubeconfig)
+	if err != nil {
+		return err
+	}
+
+	namespacedName := types.NamespacedName{
+		Namespace: namespace,
+		Name:      name,
+	}
+
+	logger.Actionf("annotating source %s in %s namespace", name, namespace)
+	var bucket sourcev1.Bucket
+	err = kubeClient.Get(ctx, namespacedName, &bucket)
+	if err != nil {
+		return err
+	}
+
+	if bucket.Annotations == nil {
+		bucket.Annotations = map[string]string{
+			consts.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
+		}
+	} else {
+		bucket.Annotations[consts.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
+	}
+	if err := kubeClient.Update(ctx, &bucket); err != nil {
+		return err
+	}
+	logger.Successf("source annotated")
+
+	logger.Waitingf("waiting for reconciliation")
+	if err := wait.PollImmediate(pollInterval, timeout,
+		isBucketReady(ctx, kubeClient, name, namespace)); err != nil {
+		return err
+	}
+
+	logger.Successf("bucket reconciliation completed")
+
+	err = kubeClient.Get(ctx, namespacedName, &bucket)
+	if err != nil {
+		return err
+	}
+
+	if bucket.Status.Artifact != nil {
+		logger.Successf("fetched revision %s", bucket.Status.Artifact.Revision)
+	} else {
+		return fmt.Errorf("bucket reconciliation failed, artifact not found")
+	}
+	return nil
+}
+
+func isBucketReady(ctx context.Context, kubeClient client.Client, name, namespace string) wait.ConditionFunc {
+	return func() (bool, error) {
+		var bucket sourcev1.Bucket
+		namespacedName := types.NamespacedName{
+			Namespace: namespace,
+			Name:      name,
+		}
+
+		err := kubeClient.Get(ctx, namespacedName, &bucket)
+		if err != nil {
+			return false, err
+		}
+
+		for _, condition := range bucket.Status.Conditions {
+			if condition.Type == sourcev1.ReadyCondition {
+				if condition.Status == corev1.ConditionTrue {
+					return true, nil
+				} else if condition.Status == corev1.ConditionFalse {
+					return false, fmt.Errorf(condition.Message)
+				}
+			}
+		}
+		return false, nil
+	}
+}
diff --git a/docs/cmd/gotk_create_source.md b/docs/cmd/gotk_create_source.md
index a75c25a262e71838794b424722c41ccde1b01a86..642977a86c252e7ef275699a655457a125ba323e 100644
--- a/docs/cmd/gotk_create_source.md
+++ b/docs/cmd/gotk_create_source.md
@@ -27,6 +27,7 @@ The create source sub-commands generate sources.
 ### SEE ALSO
 
 * [gotk create](gotk_create.md)	 - Create or update sources and resources
+* [gotk create source bucket](gotk_create_source_bucket.md)	 - Create or update a Bucket source
 * [gotk create source git](gotk_create_source_git.md)	 - Create or update a GitRepository source
 * [gotk create source helm](gotk_create_source_helm.md)	 - Create or update a HelmRepository source
 
diff --git a/docs/cmd/gotk_create_source_bucket.md b/docs/cmd/gotk_create_source_bucket.md
new file mode 100644
index 0000000000000000000000000000000000000000..76c3a543ec53e48ef979acd96836b45937e0c1d2
--- /dev/null
+++ b/docs/cmd/gotk_create_source_bucket.md
@@ -0,0 +1,65 @@
+## gotk create source bucket
+
+Create or update a Bucket source
+
+### Synopsis
+
+
+The create source bucket command generates a Bucket resource and waits for it to be downloaded.
+For Buckets with static authentication, the credentials are stored in a Kubernetes secret.
+
+```
+gotk create source bucket [name] [flags]
+```
+
+### Examples
+
+```
+  # Create a source from a Buckets using static authentication
+  gotk create source bucket podinfo \
+	--bucket-name=podinfo \
+    --endpoint=minio.minio.svc.cluster.local:9000 \
+	--insecure=true \
+	--access-key=myaccesskey \
+	--secret-key=mysecretkey \
+    --interval=10m
+
+  # Create a source from an Amazon S3 Bucket using IAM authentication
+  gotk create source bucket podinfo \
+	--bucket-name=podinfo \
+	--provider=aws \
+    --endpoint=s3.amazonaws.com \
+	--region=us-east-1 \
+    --interval=10m
+
+```
+
+### Options
+
+```
+      --access-key string    the bucket access key
+      --bucket-name string   the bucket name
+      --endpoint string      the bucket endpoint address
+  -h, --help                 help for bucket
+      --insecure             for when connecting to a non-TLS S3 HTTP endpoint
+      --provider string      the S3 compatible storage provider name, can be 'generic' or 'aws' (default "generic")
+      --region string        the bucket region
+      --secret-key string    the bucket secret key
+```
+
+### Options inherited from parent commands
+
+```
+      --export              export in YAML format to stdout
+      --interval duration   source sync interval (default 1m0s)
+      --kubeconfig string   path to the kubeconfig file (default "~/.kube/config")
+      --label strings       set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2)
+  -n, --namespace string    the namespace scope for this operation (default "gitops-system")
+      --timeout duration    timeout for this operation (default 5m0s)
+      --verbose             print generated objects
+```
+
+### SEE ALSO
+
+* [gotk create source](gotk_create_source.md)	 - Create or update sources
+
diff --git a/docs/cmd/gotk_delete_source.md b/docs/cmd/gotk_delete_source.md
index 180ba57ec051c02224149cc731e4f92b8acb036e..9626c113e52e98f7f770e5b6db842a102e0b29cf 100644
--- a/docs/cmd/gotk_delete_source.md
+++ b/docs/cmd/gotk_delete_source.md
@@ -25,6 +25,7 @@ The delete source sub-commands delete sources.
 ### SEE ALSO
 
 * [gotk delete](gotk_delete.md)	 - Delete sources and resources
+* [gotk delete source bucket](gotk_delete_source_bucket.md)	 - Delete a Bucket source
 * [gotk delete source git](gotk_delete_source_git.md)	 - Delete a GitRepository source
 * [gotk delete source helm](gotk_delete_source_helm.md)	 - Delete a HelmRepository source
 
diff --git a/docs/cmd/gotk_delete_source_bucket.md b/docs/cmd/gotk_delete_source_bucket.md
new file mode 100644
index 0000000000000000000000000000000000000000..8241acb946c9d5231386450929e38767c28c4085
--- /dev/null
+++ b/docs/cmd/gotk_delete_source_bucket.md
@@ -0,0 +1,40 @@
+## gotk delete source bucket
+
+Delete a Bucket source
+
+### Synopsis
+
+The delete source bucket command deletes the given Bucket from the cluster.
+
+```
+gotk delete source bucket [name] [flags]
+```
+
+### Examples
+
+```
+  # Delete a Bucket source
+  gotk delete source bucket podinfo
+
+```
+
+### Options
+
+```
+  -h, --help   help for bucket
+```
+
+### Options inherited from parent commands
+
+```
+      --kubeconfig string   path to the kubeconfig file (default "~/.kube/config")
+  -n, --namespace string    the namespace scope for this operation (default "gitops-system")
+  -s, --silent              delete resource without asking for confirmation
+      --timeout duration    timeout for this operation (default 5m0s)
+      --verbose             print generated objects
+```
+
+### SEE ALSO
+
+* [gotk delete source](gotk_delete_source.md)	 - Delete sources
+
diff --git a/docs/cmd/gotk_export_source.md b/docs/cmd/gotk_export_source.md
index 5cbf32fdbcdc21131630711a97810a3ae2b4570e..d80fc8287aec3019df544ef26424b7155ea3df72 100644
--- a/docs/cmd/gotk_export_source.md
+++ b/docs/cmd/gotk_export_source.md
@@ -26,6 +26,7 @@ The export source sub-commands export sources in YAML format.
 ### SEE ALSO
 
 * [gotk export](gotk_export.md)	 - Export resources in YAML format
+* [gotk export source bucket](gotk_export_source_bucket.md)	 - Export Bucket sources in YAML format
 * [gotk export source git](gotk_export_source_git.md)	 - Export GitRepository sources in YAML format
 * [gotk export source helm](gotk_export_source_helm.md)	 - Export HelmRepository sources in YAML format
 
diff --git a/docs/cmd/gotk_export_source_bucket.md b/docs/cmd/gotk_export_source_bucket.md
new file mode 100644
index 0000000000000000000000000000000000000000..2d0edc7831f919796c5bc8b87f14f85416d1e995
--- /dev/null
+++ b/docs/cmd/gotk_export_source_bucket.md
@@ -0,0 +1,44 @@
+## gotk export source bucket
+
+Export Bucket sources in YAML format
+
+### Synopsis
+
+The export source git command exports on or all Bucket sources in YAML format.
+
+```
+gotk export source bucket [name] [flags]
+```
+
+### Examples
+
+```
+  # Export all Bucket sources
+  gotk export source bucket --all > sources.yaml
+
+  # Export a Bucket source including the static credentials
+  gotk export source bucket my-bucket --with-credentials > source.yaml
+
+```
+
+### Options
+
+```
+  -h, --help   help for bucket
+```
+
+### Options inherited from parent commands
+
+```
+      --all                 select all resources
+      --kubeconfig string   path to the kubeconfig file (default "~/.kube/config")
+  -n, --namespace string    the namespace scope for this operation (default "gitops-system")
+      --timeout duration    timeout for this operation (default 5m0s)
+      --verbose             print generated objects
+      --with-credentials    include credential secrets
+```
+
+### SEE ALSO
+
+* [gotk export source](gotk_export_source.md)	 - Export sources
+
diff --git a/docs/cmd/gotk_get_sources.md b/docs/cmd/gotk_get_sources.md
index efcfdb7c2cb949d4e0f3510ab9e2bd9a169485af..5cae4a07d82b5a7f367ffc797805177fb36baaf3 100644
--- a/docs/cmd/gotk_get_sources.md
+++ b/docs/cmd/gotk_get_sources.md
@@ -24,6 +24,7 @@ The get source sub-commands print the statuses of the sources.
 ### SEE ALSO
 
 * [gotk get](gotk_get.md)	 - Get sources and resources
+* [gotk get sources bucket](gotk_get_sources_bucket.md)	 - Get Bucket source statuses
 * [gotk get sources git](gotk_get_sources_git.md)	 - Get GitRepository source statuses
 * [gotk get sources helm](gotk_get_sources_helm.md)	 - Get HelmRepository source statuses
 
diff --git a/docs/cmd/gotk_get_sources_bucket.md b/docs/cmd/gotk_get_sources_bucket.md
new file mode 100644
index 0000000000000000000000000000000000000000..bf748ed6875ee932b9fa1e981bf31ea8e7ada8fb
--- /dev/null
+++ b/docs/cmd/gotk_get_sources_bucket.md
@@ -0,0 +1,39 @@
+## gotk get sources bucket
+
+Get Bucket source statuses
+
+### Synopsis
+
+The get sources bucket command prints the status of the Bucket sources.
+
+```
+gotk get sources bucket [flags]
+```
+
+### Examples
+
+```
+  # List all Buckets and their status
+  gotk get sources bucket
+
+```
+
+### Options
+
+```
+  -h, --help   help for bucket
+```
+
+### Options inherited from parent commands
+
+```
+      --kubeconfig string   path to the kubeconfig file (default "~/.kube/config")
+  -n, --namespace string    the namespace scope for this operation (default "gitops-system")
+      --timeout duration    timeout for this operation (default 5m0s)
+      --verbose             print generated objects
+```
+
+### SEE ALSO
+
+* [gotk get sources](gotk_get_sources.md)	 - Get source statuses
+
diff --git a/docs/cmd/gotk_reconcile_source.md b/docs/cmd/gotk_reconcile_source.md
index 439da16a50a3b59b31ff84aa1d87db8887abbc95..56310014af7613af5b4cdbfbaf41abe0d8d61923 100644
--- a/docs/cmd/gotk_reconcile_source.md
+++ b/docs/cmd/gotk_reconcile_source.md
@@ -24,6 +24,7 @@ The reconcile source sub-commands trigger a reconciliation of sources.
 ### SEE ALSO
 
 * [gotk reconcile](gotk_reconcile.md)	 - Reconcile sources and resources
+* [gotk reconcile source bucket](gotk_reconcile_source_bucket.md)	 - Reconcile a Bucket source
 * [gotk reconcile source git](gotk_reconcile_source_git.md)	 - Reconcile a GitRepository source
 * [gotk reconcile source helm](gotk_reconcile_source_helm.md)	 - Reconcile a HelmRepository source
 
diff --git a/docs/cmd/gotk_reconcile_source_bucket.md b/docs/cmd/gotk_reconcile_source_bucket.md
new file mode 100644
index 0000000000000000000000000000000000000000..ed801c637cb9e183d5615c6aa230004e85572ff5
--- /dev/null
+++ b/docs/cmd/gotk_reconcile_source_bucket.md
@@ -0,0 +1,39 @@
+## gotk reconcile source bucket
+
+Reconcile a Bucket source
+
+### Synopsis
+
+The reconcile source command triggers a reconciliation of a Bucket resource and waits for it to finish.
+
+```
+gotk reconcile source bucket [name] [flags]
+```
+
+### Examples
+
+```
+  # Trigger a reconciliation for an existing source
+  gotk reconcile source bucket podinfo
+
+```
+
+### Options
+
+```
+  -h, --help   help for bucket
+```
+
+### Options inherited from parent commands
+
+```
+      --kubeconfig string   path to the kubeconfig file (default "~/.kube/config")
+  -n, --namespace string    the namespace scope for this operation (default "gitops-system")
+      --timeout duration    timeout for this operation (default 5m0s)
+      --verbose             print generated objects
+```
+
+### SEE ALSO
+
+* [gotk reconcile source](gotk_reconcile_source.md)	 - Reconcile sources
+
diff --git a/docs/index.md b/docs/index.md
index 80b927324e17f2d040e486d8420b153c03961005..d806657183620dd2856a9273d8abcd9a8041bb6f 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -40,6 +40,7 @@ Components:
     - [GitRepository CRD](components/source/gitrepositories.md)
     - [HelmRepository CRD](components/source/helmrepositories.md)
     - [HelmChart CRD](components/source/helmcharts.md)
+    - [Bucket CRD](components/source/buckets.md)
 - [Kustomize Controller](components/kustomize/controller.md)
     - [Kustomization CRD](components/kustomize/kustomization.md)
 - [Helm Controller](components/helm/controller.md)
diff --git a/mkdocs.yml b/mkdocs.yml
index 6053cdc9c4e7105f72be2c6af34c6643449c425b..ec6e27a4b2cb210c8c2d76008e67daf908c86a2e 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -57,6 +57,7 @@ nav:
       - GitRepository CRD: components/source/gitrepositories.md
       - HelmRepository CRD: components/source/helmrepositories.md
       - HelmChart CRD: components/source/helmcharts.md
+      - Bucket CRD: components/source/buckets.md
       - Source API Reference: components/source/api.md
     - Kustomize Controller:
       - Overview: components/kustomize/controller.md
@@ -85,6 +86,7 @@ nav:
     - Create source: cmd/gotk_create_source.md
     - Create source git: cmd/gotk_create_source_git.md
     - Create source helm: cmd/gotk_create_source_helm.md
+    - Create source bucket: cmd/gotk_create_source_bucket.md
     - Create tenant: cmd/gotk_create_tenant.md
     - Delete: cmd/gotk_delete.md
     - Delete kustomization: cmd/gotk_delete_kustomization.md
@@ -92,18 +94,21 @@ nav:
     - Delete source: cmd/gotk_delete_source.md
     - Delete source git: cmd/gotk_delete_source_git.md
     - Delete source helm: cmd/gotk_delete_source_helm.md
+    - Delete source bucket: cmd/gotk_delete_source_bucket.md
     - Export: cmd/gotk_export.md
     - Export kustomization: cmd/gotk_export_kustomization.md
     - Export helmrelease: cmd/gotk_export_helmrelease.md
     - Export source: cmd/gotk_export_source.md
     - Export source git: cmd/gotk_export_source_git.md
     - Export source helm: cmd/gotk_export_source_helm.md
+    - Export source bucket: cmd/gotk_export_source_bucket.md
     - Get: cmd/gotk_get.md
     - Get kustomizations: cmd/gotk_get_kustomizations.md
     - Get helmreleases: cmd/gotk_get_helmreleases.md
     - Get sources: cmd/gotk_get_sources.md
     - Get sources git: cmd/gotk_get_sources_git.md
     - Get sources helm: cmd/gotk_get_sources_helm.md
+    - Get sources bucket: cmd/gotk_get_sources_bucket.md
     - Install: cmd/gotk_install.md
     - Resume: cmd/gotk_resume.md
     - Resume kustomization: cmd/gotk_resume_kustomization.md
@@ -117,6 +122,7 @@ nav:
     - Reconcile source: cmd/gotk_reconcile_source.md
     - Reconcile source git: cmd/gotk_reconcile_source_git.md
     - Reconcile source helm: cmd/gotk_reconcile_source_helm.md
+    - Reconcile source bucket: cmd/gotk_reconcile_source_bucket.md
     - Uninstall: cmd/gotk_uninstall.md
   - Roadmap: roadmap/index.md
   - Contributing: contributing/index.md