diff --git a/.github/workflows/bootstrap.yaml b/.github/workflows/bootstrap.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..211b567f2c0ec6d3abdfcf912e8fd190a804e06d
--- /dev/null
+++ b/.github/workflows/bootstrap.yaml
@@ -0,0 +1,58 @@
+name: bootstrap
+
+on:
+  push:
+    branches:
+      - '*'
+
+jobs:
+  github:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Restore Go cache
+        uses: actions/cache@v1
+        with:
+          path: ~/go/pkg/mod
+          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
+          restore-keys: |
+            ${{ runner.os }}-go-
+      - name: Setup Go
+        uses: actions/setup-go@v2
+        with:
+          go-version: 1.14.x
+      - name: Setup Kubernetes
+        uses: engineerd/setup-kind@v0.4.0
+      - name: Set outputs
+        id: vars
+        run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
+      - name: Build
+        run: sudo go build -o ./bin/gotk ./cmd/gotk
+      - name: gotk bootstrap github
+        run: |
+          ./bin/gotk bootstrap github \
+          --owner=fluxcd-testing \
+          --repository=gotk-test-${{ steps.vars.outputs.sha_short }} \
+          --path=test-cluster
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
+      - name: uninstall
+        run: |
+          ./bin/gotk suspend kustomization gitops-system
+          ./bin/gotk uninstall --resources --crds -s
+      - name: delete repository
+        run: |
+          ./bin/gotk bootstrap github \
+          --owner=fluxcd-testing \
+          --repository=gotk-test-${{ steps.vars.outputs.sha_short }} \
+          --path=test-cluster \
+          --delete
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
+      - name: Debug failure
+        if: failure()
+        run: |
+          kubectl -n gitops-system get all
+          kubectl -n gitops-system logs deploy/source-controller
+          kubectl -n gitops-system logs deploy/kustomize-controller
diff --git a/cmd/gotk/bootstrap_github.go b/cmd/gotk/bootstrap_github.go
index bd8b83ee2214cbe844ce22b36cc2356999eeea2e..c7d0a4aeedb7d95313e238a9c7770e63ac5ff26a 100644
--- a/cmd/gotk/bootstrap_github.go
+++ b/cmd/gotk/bootstrap_github.go
@@ -68,6 +68,7 @@ var (
 	ghHostname   string
 	ghPath       string
 	ghTeams      []string
+	ghDelete     bool
 )
 
 const (
@@ -84,6 +85,9 @@ func init() {
 	bootstrapGitHubCmd.Flags().StringVar(&ghHostname, "hostname", git.GitHubDefaultHostname, "GitHub hostname")
 	bootstrapGitHubCmd.Flags().StringVar(&ghPath, "path", "", "repository path, when specified the cluster sync will be scoped to this path")
 
+	bootstrapGitHubCmd.Flags().BoolVar(&ghDelete, "delete", false, "delete repository (used for testing only)")
+	bootstrapGitHubCmd.Flags().MarkHidden("delete")
+
 	bootstrapCmd.AddCommand(bootstrapGitHubCmd)
 }
 
@@ -107,11 +111,6 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
 		IsPersonal: ghPersonal,
 	}
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
-	if err != nil {
-		return err
-	}
-
 	tmpDir, err := ioutil.TempDir("", namespace)
 	if err != nil {
 		return err
@@ -121,6 +120,14 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
+	if ghDelete {
+		if err := provider.DeleteRepository(ctx, repository); err != nil {
+			return err
+		}
+		logger.Successf("repository deleted")
+		return nil
+	}
+
 	// create GitHub repository if doesn't exists
 	logger.Actionf("connecting to %s", ghHostname)
 	changed, err := provider.CreateRepository(ctx, repository)
@@ -173,6 +180,11 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
 		logger.Successf("components are up to date")
 	}
 
+	kubeClient, err := utils.kubeClient(kubeconfig)
+	if err != nil {
+		return err
+	}
+
 	// determine if repo synchronization is working
 	isInstall := shouldInstallManifests(ctx, kubeClient, namespace)
 
diff --git a/cmd/gotk/create.go b/cmd/gotk/create.go
index 0afef7e57c0da89559d67dc7623a896cdbf9bce3..5083fa2a5c2b494929558b8f356a28cc44282f1a 100644
--- a/cmd/gotk/create.go
+++ b/cmd/gotk/create.go
@@ -21,6 +21,8 @@ import (
 	"strings"
 	"time"
 
+	"k8s.io/apimachinery/pkg/util/validation"
+
 	"github.com/spf13/cobra"
 )
 
@@ -47,10 +49,22 @@ func init() {
 func parseLabels() (map[string]string, error) {
 	result := make(map[string]string)
 	for _, label := range labels {
+		// validate key value pair
 		parts := strings.Split(label, "=")
 		if len(parts) != 2 {
 			return nil, fmt.Errorf("invalid label format '%s', must be key=value", label)
 		}
+
+		// validate label name
+		if errors := validation.IsQualifiedName(parts[0]); len(errors) > 0 {
+			return nil, fmt.Errorf("invalid label '%s': %v", parts[0], errors)
+		}
+
+		// validate label value
+		if errors := validation.IsValidLabelValue(parts[1]); len(errors) > 0 {
+			return nil, fmt.Errorf("invalid label value '%s': %v", parts[1], errors)
+		}
+
 		result[parts[0]] = parts[1]
 	}
 
diff --git a/go.mod b/go.mod
index e691b41d3d66a17c6a78f72cfc41bbbff33c3793..f11a5317f59d0e983df52c4a77d4bb4344697b04 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/blang/semver v3.5.1+incompatible
 	github.com/fluxcd/helm-controller/api v0.0.7
 	github.com/fluxcd/kustomize-controller/api v0.0.10
-	github.com/fluxcd/pkg/git v0.0.6
+	github.com/fluxcd/pkg/git v0.0.7
 	github.com/fluxcd/pkg/ssh v0.0.5
 	github.com/fluxcd/pkg/untar v0.0.5
 	github.com/fluxcd/source-controller/api v0.0.14
diff --git a/go.sum b/go.sum
index 36ea8f41424e6a6af693789856a2ba39baaeb0cb..c32b053a5aea1086c00bc8dceec974ef1e9879f3 100644
--- a/go.sum
+++ b/go.sum
@@ -115,8 +115,8 @@ github.com/fluxcd/helm-controller/api v0.0.7 h1:aidjXvcklClH8omhYqiKswZ+MS6t8knO
 github.com/fluxcd/helm-controller/api v0.0.7/go.mod h1:KlzwTkpphQxulgWBwCl/uxfBU0QxK/X+w4YcJqGy/1c=
 github.com/fluxcd/kustomize-controller/api v0.0.10 h1:dhkTOg3LzNQwRL+lO0YlzOP7AhdpZdghUQNXYhvfiYU=
 github.com/fluxcd/kustomize-controller/api v0.0.10/go.mod h1:88m3p6xY3J2pjh5OsL3ANy7PkyA93KiqAJE58JMQyoc=
-github.com/fluxcd/pkg/git v0.0.6 h1:4qktw8M3zj98MAs4ny6qSi36sYvTiI1czif5FqlQl4o=
-github.com/fluxcd/pkg/git v0.0.6/go.mod h1:9AI9yPkb2ruIcE70moVG3WhunA2/RAMJPc3rtoH8QFE=
+github.com/fluxcd/pkg/git v0.0.7 h1:tFSYPy7tcIYfOt8H5EUERXIRz7fk0id302oQZde1NtU=
+github.com/fluxcd/pkg/git v0.0.7/go.mod h1:5Vu92x6Q3CpxDUllmB69kAkVY5jAtPpXcY2TSZ/oCJI=
 github.com/fluxcd/pkg/ssh v0.0.5 h1:rnbFZ7voy2JBlUfMbfyqArX2FYaLNpDhccGFC3qW83A=
 github.com/fluxcd/pkg/ssh v0.0.5/go.mod h1:7jXPdXZpc0ttMNz2kD9QuMi3RNn/e0DOFbj0Tij/+Hs=
 github.com/fluxcd/pkg/untar v0.0.5 h1:UGI3Ch1UIEIaqQvMicmImL1s9npQa64DJ/ozqHKB7gk=
@@ -265,8 +265,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-github/v32 v32.0.0 h1:q74KVb22spUq0U5HqZ9VCYqQz8YRuOtL/39ZnfwO+NM=
-github.com/google/go-github/v32 v32.0.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
+github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II=
+github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
 github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=