diff --git a/.github/workflows/bootstrap.yaml b/.github/workflows/bootstrap.yaml
index d65ee2a2996d237b7840037044ab9003fc7486f4..41d569a6ef2c9814aca5ec4358ef7c4fcdac00e8 100644
--- a/.github/workflows/bootstrap.yaml
+++ b/.github/workflows/bootstrap.yaml
@@ -17,23 +17,27 @@ jobs:
         uses: actions/cache@v1
         with:
           path: ~/go/pkg/mod
-          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
+          key: ${{ runner.os }}-go1.16-${{ hashFiles('**/go.sum') }}
           restore-keys: |
-            ${{ runner.os }}-go-
+            ${{ runner.os }}-go1.16-
       - name: Setup Go
         uses: actions/setup-go@v2
         with:
           go-version: 1.16.x
       - name: Setup Kubernetes
         uses: engineerd/setup-kind@v0.5.0
+      - name: Setup Kustomize
+        uses: fluxcd/pkg//actions/kustomize@main
+      - name: Build
+        run: |
+          make build-manifests
+          go build -o /tmp/flux ./cmd/flux
       - 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/flux ./cmd/flux
       - name: bootstrap init
         run: |
-          ./bin/flux bootstrap github --manifests ./manifests/install/ \
+          /tmp/flux bootstrap github --manifests ./manifests/install/ \
           --owner=fluxcd-testing \
           --repository=flux-test-${{ steps.vars.outputs.sha_short }} \
           --branch=main \
@@ -42,7 +46,7 @@ jobs:
           GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
       - name: bootstrap no-op
         run: |
-          ./bin/flux bootstrap github --manifests ./manifests/install/ \
+          /tmp/flux bootstrap github --manifests ./manifests/install/ \
           --owner=fluxcd-testing \
           --repository=flux-test-${{ steps.vars.outputs.sha_short }} \
           --branch=main \
@@ -51,11 +55,11 @@ jobs:
           GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
       - name: uninstall
         run: |
-          ./bin/flux uninstall -s --keep-namespace
+          /tmp/flux uninstall -s --keep-namespace
           kubectl delete ns flux-system --timeout=10m --wait=true
       - name: bootstrap reinstall
         run: |
-          ./bin/flux bootstrap github --manifests ./manifests/install/ \
+          /tmp/flux bootstrap github --manifests ./manifests/install/ \
           --owner=fluxcd-testing \
           --repository=flux-test-${{ steps.vars.outputs.sha_short }} \
           --branch=main \
@@ -64,7 +68,7 @@ jobs:
           GITHUB_TOKEN: ${{ secrets.GITPROVIDER_BOT_TOKEN }}
       - name: delete repository
         run: |
-          ./bin/flux bootstrap github --manifests ./manifests/install/ \
+          /tmp/flux bootstrap github --manifests ./manifests/install/ \
           --owner=fluxcd-testing \
           --repository=flux-test-${{ steps.vars.outputs.sha_short }} \
           --branch=main \
diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml
index 70d8c2e3f9f4ecee86eec19aceca881734df2bf5..088be0eaa73448d620a668036194a88ecbc61d68 100644
--- a/.github/workflows/e2e.yaml
+++ b/.github/workflows/e2e.yaml
@@ -16,9 +16,9 @@ jobs:
         uses: actions/cache@v1
         with:
           path: ~/go/pkg/mod
-          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
+          key: ${{ runner.os }}-go1.16-${{ hashFiles('**/go.sum') }}
           restore-keys: |
-            ${{ runner.os }}-go-
+            ${{ runner.os }}-go1.16-
       - name: Setup Go
         uses: actions/setup-go@v2
         with:
@@ -33,6 +33,8 @@ jobs:
         run: |
           kubectl apply -f https://docs.projectcalico.org/v3.16/manifests/calico.yaml
           kubectl -n kube-system set env daemonset/calico-node FELIX_IGNORELOOSERPF=true
+      - name: Setup Kustomize
+        uses: fluxcd/pkg//actions/kustomize@main
       - name: Run test
         run: make test
       - name: Check if working tree is dirty
@@ -43,43 +45,44 @@ jobs:
             exit 1
           fi
       - name: Build
-        run: sudo go build -o ./bin/flux ./cmd/flux
+        run: |
+          go build -o /tmp/flux ./cmd/flux
       - name: flux check --pre
         run: |
-          ./bin/flux check --pre
+          /tmp/flux check --pre
       - name: flux install --manifests
         run: |
-          ./bin/flux install --manifests ./manifests/install/
+          /tmp/flux install --manifests ./manifests/install/
       - name: flux create secret
         run: |
-          ./bin/flux create secret git git-ssh-test \
+          /tmp/flux create secret git git-ssh-test \
             --url ssh://git@github.com/stefanprodan/podinfo
-          ./bin/flux create secret git git-https-test \
+          /tmp/flux create secret git git-https-test \
             --url https://github.com/stefanprodan/podinfo \
             --username=test --password=test
-          ./bin/flux create secret helm helm-test \
+          /tmp/flux create secret helm helm-test \
             --username=test --password=test
       - name: flux create source git
         run: |
-          ./bin/flux create source git podinfo \
+          /tmp/flux create source git podinfo \
             --url https://github.com/stefanprodan/podinfo  \
             --tag-semver=">=3.2.3"
       - name: flux create source git export apply
         run: |
-          ./bin/flux create source git podinfo-export \
+          /tmp/flux create source git podinfo-export \
             --url https://github.com/stefanprodan/podinfo  \
             --tag-semver=">=3.2.3" \
             --export | kubectl apply -f -
-          ./bin/flux delete source git podinfo-export --silent
+          /tmp/flux delete source git podinfo-export --silent
       - name: flux get sources git
         run: |
-          ./bin/flux get sources git
+          /tmp/flux get sources git
       - name: flux get sources git --all-namespaces
         run: |
-          ./bin/flux get sources git --all-namespaces
+          /tmp/flux get sources git --all-namespaces
       - name: flux create kustomization
         run: |
-          ./bin/flux create kustomization podinfo \
+          /tmp/flux create kustomization podinfo \
             --source=podinfo \
             --path="./deploy/overlays/dev" \
             --prune=true \
@@ -90,112 +93,112 @@ jobs:
             --health-check-timeout=3m
       - name: flux reconcile kustomization --with-source
         run: |
-          ./bin/flux reconcile kustomization podinfo --with-source
+          /tmp/flux reconcile kustomization podinfo --with-source
       - name: flux get kustomizations
         run: |
-          ./bin/flux get kustomizations
+          /tmp/flux get kustomizations
       - name: flux get kustomizations --all-namespaces
         run: |
-          ./bin/flux get kustomizations --all-namespaces
+          /tmp/flux get kustomizations --all-namespaces
       - name: flux suspend kustomization
         run: |
-          ./bin/flux suspend kustomization podinfo
+          /tmp/flux suspend kustomization podinfo
       - name: flux resume kustomization
         run: |
-          ./bin/flux resume kustomization podinfo
+          /tmp/flux resume kustomization podinfo
       - name: flux export
         run: |
-          ./bin/flux export source git --all
-          ./bin/flux export kustomization --all
+          /tmp/flux export source git --all
+          /tmp/flux export kustomization --all
       - name: flux delete kustomization
         run: |
-          ./bin/flux delete kustomization podinfo --silent
+          /tmp/flux delete kustomization podinfo --silent
       - name: flux create source helm
         run: |
-          ./bin/flux create source helm podinfo \
+          /tmp/flux create source helm podinfo \
             --url https://stefanprodan.github.io/podinfo
       - name: flux create helmrelease --source=HelmRepository/podinfo
         run: |
-          ./bin/flux create hr podinfo-helm \
+          /tmp/flux create hr podinfo-helm \
             --target-namespace=default \
             --source=HelmRepository/podinfo \
             --chart=podinfo \
             --chart-version=">4.0.0 <5.0.0"
       - name: flux create helmrelease --source=GitRepository/podinfo
         run: |
-          ./bin/flux create hr podinfo-git \
+          /tmp/flux create hr podinfo-git \
             --target-namespace=default \
             --source=GitRepository/podinfo \
             --chart=./charts/podinfo
       - name: flux reconcile helmrelease --with-source
         run: |
-          ./bin/flux reconcile helmrelease podinfo-git --with-source
+          /tmp/flux reconcile helmrelease podinfo-git --with-source
       - name: flux get helmreleases
         run: |
-          ./bin/flux get helmreleases
+          /tmp/flux get helmreleases
       - name: flux get helmreleases --all-namespaces
         run: |
-          ./bin/flux get helmreleases --all-namespaces
+          /tmp/flux get helmreleases --all-namespaces
       - name: flux export helmrelease
         run: |
-          ./bin/flux export hr --all
+          /tmp/flux export hr --all
       - name: flux delete helmrelease podinfo-helm
         run: |
-          ./bin/flux delete hr podinfo-helm --silent
+          /tmp/flux delete hr podinfo-helm --silent
       - name: flux delete helmrelease podinfo-git
         run: |
-          ./bin/flux delete hr podinfo-git --silent
+          /tmp/flux delete hr podinfo-git --silent
       - name: flux delete source helm
         run: |
-          ./bin/flux delete source helm podinfo --silent
+          /tmp/flux delete source helm podinfo --silent
       - name: flux delete source git
         run: |
-          ./bin/flux delete source git podinfo --silent
+          /tmp/flux delete source git podinfo --silent
       - name: flux create tenant
         run: |
-          ./bin/flux create tenant dev-team --with-namespace=apps
-          ./bin/flux -n apps create source helm podinfo \
+          /tmp/flux create tenant dev-team --with-namespace=apps
+          /tmp/flux -n apps create source helm podinfo \
             --url https://stefanprodan.github.io/podinfo
-          ./bin/flux -n apps create hr podinfo-helm \
+          /tmp/flux -n apps create hr podinfo-helm \
             --source=HelmRepository/podinfo \
             --chart=podinfo \
             --chart-version="5.0.x" \
             --service-account=dev-team
       - name: flux create image repository
         run: |
-          ./bin/flux create image repository podinfo \
+          /tmp/flux create image repository podinfo \
             --image=ghcr.io/stefanprodan/podinfo \
             --interval=1m
       - name: flux create image policy
         run: |
-          ./bin/flux create image policy podinfo \
+          /tmp/flux create image policy podinfo \
             --image-ref=podinfo \
             --interval=1m \
             --select-semver=5.0.x
       - name: flux create image policy podinfo-select-alpha
         run: |
-          ./bin/flux create image policy podinfo-alpha \
+          /tmp/flux create image policy podinfo-alpha \
             --image-ref=podinfo \
             --interval=1m \
             --select-alpha=desc
       - name: flux get image policy
         run: |
-          ./bin/flux get image policy podinfo | grep '5.0.3'
+          /tmp/flux get image policy podinfo | grep '5.0.3'
       - name: flux2-kustomize-helm-example
         run: |
-          ./bin/flux create source git flux-system \
+          /tmp/flux create source git flux-system \
           --url=https://github.com/fluxcd/flux2-kustomize-helm-example \
           --branch=main
-          ./bin/flux create kustomization flux-system \
+          /tmp/flux create kustomization flux-system \
           --source=flux-system \
           --path=./clusters/staging
           kubectl -n flux-system wait kustomization/apps --for=condition=ready --timeout=2m
       - name: flux check
         run: |
-          ./bin/flux check
+          /tmp/flux check
       - name: flux uninstall
         run: |
-          ./bin/flux uninstall --silent
+          /tmp/flux uninstall --silent
       - name: Debug failure
         if: failure()
         run: |
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 6ac8ee8425a34c8a90a43e15a9c2bc7c1cc9e71b..8a911d1276a636474f3ff577258075e28e36ba8e 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -2,7 +2,7 @@ name: release
 
 on:
   push:
-    tags: [ '*' ]
+    tags: [ 'v*' ]
 
 jobs:
   goreleaser:
@@ -28,38 +28,10 @@ jobs:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
       - name: Setup Kustomize
         uses: fluxcd/pkg//actions/kustomize@main
-      - name: Generate manifests tarball
-        run: |
-          mkdir -p ./output
-          files=""
-
-          # build controllers
-          for controller in ./manifests/bases/*/; do
-              output_path="./output/$(basename $controller).yaml"
-              echo "building $controller to $output_path"
-
-              kustomize build $controller > $output_path
-              files+=" $(basename $output_path)"
-          done
-
-          # build rbac
-          rbac_path="./manifests/rbac"
-          rbac_output_path="./output/rbac.yaml"
-          echo "building $rbac_path to $rbac_output_path"
-          kustomize build $rbac_path > $rbac_output_path
-          files+=" $(basename $rbac_output_path)"
-
-          # build policies
-          policies_path="./manifests/policies"
-          policies_output_path="./output/policies.yaml"
-          echo "building $policies_path to $policies_output_path"
-          kustomize build $policies_path > $policies_output_path
-          files+=" $(basename $policies_output_path)"
-
-          # create tarball
-          cd ./output && tar -cvzf manifests.tar.gz $files
-      - name: Generate install manifest
+      - name: Generate manifests
         run: |
+          make build-manifests
+          ./manifests/scripts/bundle.sh ./output manifests.tar.gz
           kustomize build ./manifests/install > ./output/install.yaml
       - name: Run GoReleaser
         uses: goreleaser/goreleaser-action@v1
diff --git a/.github/workflows/scan.yaml b/.github/workflows/scan.yaml
index a949ab300f450d1594d03e5155385df30daf88bc..b020c509ec997d422496b441fd73a322d1645c57 100644
--- a/.github/workflows/scan.yaml
+++ b/.github/workflows/scan.yaml
@@ -27,6 +27,11 @@ jobs:
     if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
     steps:
       - uses: actions/checkout@v2
+      - name: Setup Kustomize
+        uses: fluxcd/pkg//actions/kustomize@main
+      - name: Build manifests
+        run: |
+          make build-manifests
       - name: Run Snyk to check for vulnerabilities
         uses: snyk/actions/golang@master
         continue-on-error: true
diff --git a/.gitignore b/.gitignore
index d6ce0835c438b55f2b703aaf9af3f4ac56126b8b..2966ab637e0b35af17fab852563c5e94bc5cab4a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,4 +14,5 @@
 # Dependency directories (remove the comment below to include it)
 # vendor/
 bin/
-output/
\ No newline at end of file
+output/
+cmd/flux/manifests/
diff --git a/Makefile b/Makefile
index 3d4765bdf51704774c0bd9682ad7780bfb4add3d..ed6333f37db975a2e25c8ba544111d9749f78614 100644
--- a/Makefile
+++ b/Makefile
@@ -11,9 +11,12 @@ fmt:
 vet:
 	go vet ./...
 
-test: tidy fmt vet docs
+test: build-manifests tidy fmt vet docs
 	go test ./... -coverprofile cover.out
 
+build-manifests:
+	./manifests/scripts/bundle.sh
+
 build:
 	CGO_ENABLED=0 go build -o ./bin/flux ./cmd/flux
 
diff --git a/cmd/flux/bootstrap.go b/cmd/flux/bootstrap.go
index b90e71608869a8167492d402b2840645c85dabe6..3243e62942d1ef510f6e476d293eadd11510ea72 100644
--- a/cmd/flux/bootstrap.go
+++ b/cmd/flux/bootstrap.go
@@ -70,8 +70,8 @@ const (
 var bootstrapArgs = NewBootstrapFlags()
 
 func init() {
-	bootstrapCmd.PersistentFlags().StringVarP(&bootstrapArgs.version, "version", "v", rootArgs.defaults.Version,
-		"toolkit version")
+	bootstrapCmd.PersistentFlags().StringVarP(&bootstrapArgs.version, "version", "v", "",
+		"toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases")
 	bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.defaultComponents, "components", rootArgs.defaults.Components,
 		"list of components, accepts comma-separated values")
 	bootstrapCmd.PersistentFlags().StringSliceVar(&bootstrapArgs.extraComponents, "components-extra", nil,
@@ -126,23 +126,18 @@ func bootstrapValidate() error {
 }
 
 func generateInstallManifests(targetPath, namespace, tmpDir string, localManifests string) (string, error) {
-	if bootstrapArgs.version == install.MakeDefaultOptions().Version {
-		version, err := install.GetLatestVersion()
-		if err != nil {
-			return "", err
-		}
-		bootstrapArgs.version = version
+	if ver, err := getVersion(bootstrapArgs.version); err != nil {
+		return "", err
 	} else {
-		if ok, err := install.ExistingVersion(bootstrapArgs.version); err != nil || !ok {
-			if err == nil {
-				err = fmt.Errorf("targeted version '%s' does not exist", bootstrapArgs.version)
-			}
-			return "", err
-		}
+		bootstrapArgs.version = ver
 	}
 
-	if !utils.CompatibleVersion(VERSION, bootstrapArgs.version) {
-		return "", fmt.Errorf("targeted version '%s' is not compatible with your current version of flux (%s)", bootstrapArgs.version, VERSION)
+	manifestsBase := ""
+	if isEmbeddedVersion(bootstrapArgs.version) {
+		if err := writeEmbeddedManifests(tmpDir); err != nil {
+			return "", err
+		}
+		manifestsBase = tmpDir
 	}
 
 	opts := install.Options{
@@ -167,7 +162,7 @@ func generateInstallManifests(targetPath, namespace, tmpDir string, localManifes
 		opts.BaseURL = rootArgs.defaults.BaseURL
 	}
 
-	output, err := install.Generate(opts)
+	output, err := install.Generate(opts, manifestsBase)
 	if err != nil {
 		return "", fmt.Errorf("generating install manifests failed: %w", err)
 	}
diff --git a/cmd/flux/embed.go b/cmd/flux/embed.go
new file mode 100644
index 0000000000000000000000000000000000000000..3e5142eaf4b33d875f9d3a36c31d09e95f488ee4
--- /dev/null
+++ b/cmd/flux/embed.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+	"embed"
+	"fmt"
+	"io/fs"
+	"os"
+	"path"
+)
+
+//go:embed manifests/*.yaml
+var embeddedManifests embed.FS
+
+func writeEmbeddedManifests(dir string) error {
+	manifests, err := fs.ReadDir(embeddedManifests, "manifests")
+	if err != nil {
+		return err
+	}
+	for _, manifest := range manifests {
+		data, err := fs.ReadFile(embeddedManifests, path.Join("manifests", manifest.Name()))
+		if err != nil {
+			return fmt.Errorf("reading file failed: %w", err)
+		}
+
+		err = os.WriteFile(path.Join(dir, manifest.Name()), data, 0666)
+		if err != nil {
+			return fmt.Errorf("writing file failed: %w", err)
+		}
+	}
+	return nil
+}
diff --git a/cmd/flux/install.go b/cmd/flux/install.go
index bac6d0586bb659127bbb6badc8d5f1246e61671c..fb2740e28864ad46476cd2b44850de20bb3fcf95 100644
--- a/cmd/flux/install.go
+++ b/cmd/flux/install.go
@@ -55,79 +55,81 @@ If a previous version is installed, then an in-place upgrade will be performed.`
 	RunE: installCmdRun,
 }
 
-var (
-	installExport             bool
-	installDryRun             bool
-	installManifestsPath      string
-	installVersion            string
-	installDefaultComponents  []string
-	installExtraComponents    []string
-	installRegistry           string
-	installImagePullSecret    string
-	installWatchAllNamespaces bool
-	installNetworkPolicy      bool
-	installArch               flags.Arch
-	installLogLevel           = flags.LogLevel(rootArgs.defaults.LogLevel)
-	installClusterDomain      string
-	installTolerationKeys     []string
-)
+type installFlags struct {
+	export             bool
+	dryRun             bool
+	version            string
+	defaultComponents  []string
+	extraComponents    []string
+	registry           string
+	imagePullSecret    string
+	branch             string
+	watchAllNamespaces bool
+	networkPolicy      bool
+	manifestsPath      string
+	arch               flags.Arch
+	logLevel           flags.LogLevel
+	tokenAuth          bool
+	clusterDomain      string
+	tolerationKeys     []string
+}
+
+var installArgs = NewInstallFlags()
 
 func init() {
-	installCmd.Flags().BoolVar(&installExport, "export", false,
+	installCmd.Flags().BoolVar(&installArgs.export, "export", false,
 		"write the install manifests to stdout and exit")
-	installCmd.Flags().BoolVarP(&installDryRun, "dry-run", "", false,
+	installCmd.Flags().BoolVarP(&installArgs.dryRun, "dry-run", "", false,
 		"only print the object that would be applied")
-	installCmd.Flags().StringVarP(&installVersion, "version", "v", rootArgs.defaults.Version,
-		"toolkit version")
-	installCmd.Flags().StringSliceVar(&installDefaultComponents, "components", rootArgs.defaults.Components,
+	installCmd.Flags().StringVarP(&installArgs.version, "version", "v", "",
+		"toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases")
+	installCmd.Flags().StringSliceVar(&installArgs.defaultComponents, "components", rootArgs.defaults.Components,
 		"list of components, accepts comma-separated values")
-	installCmd.Flags().StringSliceVar(&installExtraComponents, "components-extra", nil,
+	installCmd.Flags().StringSliceVar(&installArgs.extraComponents, "components-extra", nil,
 		"list of components in addition to those supplied or defaulted, accepts comma-separated values")
-	installCmd.Flags().StringVar(&installManifestsPath, "manifests", "", "path to the manifest directory")
-	installCmd.Flags().StringVar(&installRegistry, "registry", rootArgs.defaults.Registry,
+	installCmd.Flags().StringVar(&installArgs.manifestsPath, "manifests", "", "path to the manifest directory")
+	installCmd.Flags().StringVar(&installArgs.registry, "registry", rootArgs.defaults.Registry,
 		"container registry where the toolkit images are published")
-	installCmd.Flags().StringVar(&installImagePullSecret, "image-pull-secret", "",
+	installCmd.Flags().StringVar(&installArgs.imagePullSecret, "image-pull-secret", "",
 		"Kubernetes secret name used for pulling the toolkit images from a private registry")
-	installCmd.Flags().Var(&installArch, "arch", installArch.Description())
-	installCmd.Flags().BoolVar(&installWatchAllNamespaces, "watch-all-namespaces", rootArgs.defaults.WatchAllNamespaces,
+	installCmd.Flags().Var(&installArgs.arch, "arch", installArgs.arch.Description())
+	installCmd.Flags().BoolVar(&installArgs.watchAllNamespaces, "watch-all-namespaces", rootArgs.defaults.WatchAllNamespaces,
 		"watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed")
-	installCmd.Flags().Var(&installLogLevel, "log-level", installLogLevel.Description())
-	installCmd.Flags().BoolVar(&installNetworkPolicy, "network-policy", rootArgs.defaults.NetworkPolicy,
+	installCmd.Flags().Var(&installArgs.logLevel, "log-level", installArgs.logLevel.Description())
+	installCmd.Flags().BoolVar(&installArgs.networkPolicy, "network-policy", rootArgs.defaults.NetworkPolicy,
 		"deny ingress access to the toolkit controllers from other namespaces using network policies")
-	installCmd.Flags().StringVar(&installClusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain")
-	installCmd.Flags().StringSliceVar(&installTolerationKeys, "toleration-keys", nil,
+	installCmd.Flags().StringVar(&installArgs.clusterDomain, "cluster-domain", rootArgs.defaults.ClusterDomain, "internal cluster domain")
+	installCmd.Flags().StringSliceVar(&installArgs.tolerationKeys, "toleration-keys", nil,
 		"list of toleration keys used to schedule the components pods onto nodes with matching taints")
 	installCmd.Flags().MarkHidden("manifests")
 	installCmd.Flags().MarkDeprecated("arch", "multi-arch container image is now available for AMD64, ARMv7 and ARM64")
 	rootCmd.AddCommand(installCmd)
 }
 
+func NewInstallFlags() installFlags {
+	return installFlags{
+		logLevel: flags.LogLevel(rootArgs.defaults.LogLevel),
+	}
+}
+
 func installCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
 	defer cancel()
 
-	components := append(installDefaultComponents, installExtraComponents...)
+	components := append(installArgs.defaultComponents, installArgs.extraComponents...)
 	err := utils.ValidateComponents(components)
 	if err != nil {
 		return err
 	}
 
-	if installVersion == install.MakeDefaultOptions().Version {
-		installVersion, err = install.GetLatestVersion()
-		if err != nil {
-			return err
-		}
+	if ver, err := getVersion(installArgs.version); err != nil {
+		return err
 	} else {
-		if ok, err := install.ExistingVersion(installVersion); err != nil || !ok {
-			if err == nil {
-				err = fmt.Errorf("targeted version '%s' does not exist", installVersion)
-			}
-			return err
-		}
+		installArgs.version = ver
 	}
 
-	if !utils.CompatibleVersion(VERSION, installVersion) {
-		return fmt.Errorf("targeted version '%s' is not compatible with your current version of flux (%s)", installVersion, VERSION)
+	if !installArgs.export {
+		logger.Generatef("generating manifests")
 	}
 
 	tmpDir, err := ioutil.TempDir("", rootArgs.namespace)
@@ -136,32 +138,36 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
 	}
 	defer os.RemoveAll(tmpDir)
 
-	if !installExport {
-		logger.Generatef("generating manifests")
+	manifestsBase := ""
+	if isEmbeddedVersion(installArgs.version) {
+		if err := writeEmbeddedManifests(tmpDir); err != nil {
+			return err
+		}
+		manifestsBase = tmpDir
 	}
 
 	opts := install.Options{
-		BaseURL:                installManifestsPath,
-		Version:                installVersion,
+		BaseURL:                installArgs.manifestsPath,
+		Version:                installArgs.version,
 		Namespace:              rootArgs.namespace,
 		Components:             components,
-		Registry:               installRegistry,
-		ImagePullSecret:        installImagePullSecret,
-		WatchAllNamespaces:     installWatchAllNamespaces,
-		NetworkPolicy:          installNetworkPolicy,
-		LogLevel:               installLogLevel.String(),
+		Registry:               installArgs.registry,
+		ImagePullSecret:        installArgs.imagePullSecret,
+		WatchAllNamespaces:     installArgs.watchAllNamespaces,
+		NetworkPolicy:          installArgs.networkPolicy,
+		LogLevel:               installArgs.logLevel.String(),
 		NotificationController: rootArgs.defaults.NotificationController,
 		ManifestFile:           fmt.Sprintf("%s.yaml", rootArgs.namespace),
 		Timeout:                rootArgs.timeout,
-		ClusterDomain:          installClusterDomain,
-		TolerationKeys:         installTolerationKeys,
+		ClusterDomain:          installArgs.clusterDomain,
+		TolerationKeys:         installArgs.tolerationKeys,
 	}
 
-	if installManifestsPath == "" {
+	if installArgs.manifestsPath == "" {
 		opts.BaseURL = install.MakeDefaultOptions().BaseURL
 	}
 
-	manifest, err := install.Generate(opts)
+	manifest, err := install.Generate(opts, manifestsBase)
 	if err != nil {
 		return fmt.Errorf("install failed: %w", err)
 	}
@@ -172,9 +178,9 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
 
 	if rootArgs.verbose {
 		fmt.Print(manifest.Content)
-	} else if installExport {
+	} else if installArgs.export {
 		fmt.Println("---")
-		fmt.Println("# Flux version:", installVersion)
+		fmt.Println("# Flux version:", installArgs.version)
 		fmt.Println("# Components:", strings.Join(components, ","))
 		fmt.Print(manifest.Content)
 		fmt.Println("---")
@@ -189,7 +195,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
 	}
 
 	kubectlArgs := []string{"apply", "-f", filepath.Join(tmpDir, manifest.Path)}
-	if installDryRun {
+	if installArgs.dryRun {
 		kubectlArgs = append(kubectlArgs, "--dry-run=client")
 		applyOutput = utils.ModeOS
 	}
@@ -197,7 +203,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
 		return fmt.Errorf("install failed")
 	}
 
-	if installDryRun {
+	if installArgs.dryRun {
 		logger.Successf("install dry-run finished")
 		return nil
 	}
diff --git a/cmd/flux/main.go b/cmd/flux/main.go
index d44103386e6c2d635da324f32b1af82ad63bceed..ecd75a902bd7a634261179baa7b1af39ff79ccab 100644
--- a/cmd/flux/main.go
+++ b/cmd/flux/main.go
@@ -115,10 +115,12 @@ func init() {
 }
 
 func NewRootFlags() rootFlags {
-	return rootFlags{
+	rf := rootFlags{
 		pollInterval: 2 * time.Second,
 		defaults:     install.MakeDefaultOptions(),
 	}
+	rf.defaults.Version = "v" + VERSION
+	return rf
 }
 
 func main() {
diff --git a/cmd/flux/version.go b/cmd/flux/version.go
new file mode 100644
index 0000000000000000000000000000000000000000..cbbcf2b659927b30eaf1baab9f9ff1e5c2e4f119
--- /dev/null
+++ b/cmd/flux/version.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+	"fmt"
+
+	"github.com/fluxcd/flux2/internal/utils"
+	"github.com/fluxcd/flux2/pkg/manifestgen/install"
+)
+
+func getVersion(input string) (string, error) {
+	if input == "" {
+		return rootArgs.defaults.Version, nil
+	}
+
+	if isEmbeddedVersion(input) {
+		return input, nil
+	}
+
+	var err error
+	if input == install.MakeDefaultOptions().Version {
+		input, err = install.GetLatestVersion()
+		if err != nil {
+			return "", err
+		}
+	} else {
+		if ok, err := install.ExistingVersion(input); err != nil || !ok {
+			if err == nil {
+				err = fmt.Errorf("targeted version '%s' does not exist", input)
+			}
+			return "", err
+		}
+	}
+
+	if !utils.CompatibleVersion(VERSION, input) {
+		return "", fmt.Errorf("targeted version '%s' is not compatible with your current version of flux (%s)", input, VERSION)
+	}
+	return input, nil
+}
+
+func isEmbeddedVersion(input string) bool {
+	return input == rootArgs.defaults.Version
+}
diff --git a/docs/cmd/flux_bootstrap.md b/docs/cmd/flux_bootstrap.md
index 6ee474cb08a7069c8787daab78c849e9c5f71a67..530a9c3e02959299ba00ca102c9dc41440d8a51c 100644
--- a/docs/cmd/flux_bootstrap.md
+++ b/docs/cmd/flux_bootstrap.md
@@ -20,7 +20,7 @@ The bootstrap sub-commands bootstrap the toolkit components on the targeted Git
       --registry string            container registry where the toolkit images are published (default "ghcr.io/fluxcd")
       --token-auth                 when enabled, the personal access token will be used instead of SSH deploy key
       --toleration-keys strings    list of toleration keys used to schedule the components pods onto nodes with matching taints
-  -v, --version string             toolkit version (default "latest")
+  -v, --version string             toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases
       --watch-all-namespaces       watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
 ```
 
diff --git a/docs/cmd/flux_bootstrap_github.md b/docs/cmd/flux_bootstrap_github.md
index 944305658ffe69ca867c2dc6fb2bcaa285ac9a40..eca8d11790d1b3f280d9bb68b4c8363040b1144c 100644
--- a/docs/cmd/flux_bootstrap_github.md
+++ b/docs/cmd/flux_bootstrap_github.md
@@ -76,7 +76,7 @@ flux bootstrap github [flags]
       --token-auth                 when enabled, the personal access token will be used instead of SSH deploy key
       --toleration-keys strings    list of toleration keys used to schedule the components pods onto nodes with matching taints
       --verbose                    print generated objects
-  -v, --version string             toolkit version (default "latest")
+  -v, --version string             toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases
       --watch-all-namespaces       watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
 ```
 
diff --git a/docs/cmd/flux_bootstrap_gitlab.md b/docs/cmd/flux_bootstrap_gitlab.md
index 498b4c062d79b8647f2e3e65272e0eb14ff9183b..027201e576dec54abee17bb10d570293a8db3960 100644
--- a/docs/cmd/flux_bootstrap_gitlab.md
+++ b/docs/cmd/flux_bootstrap_gitlab.md
@@ -72,7 +72,7 @@ flux bootstrap gitlab [flags]
       --token-auth                 when enabled, the personal access token will be used instead of SSH deploy key
       --toleration-keys strings    list of toleration keys used to schedule the components pods onto nodes with matching taints
       --verbose                    print generated objects
-  -v, --version string             toolkit version (default "latest")
+  -v, --version string             toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases
       --watch-all-namespaces       watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
 ```
 
diff --git a/docs/cmd/flux_install.md b/docs/cmd/flux_install.md
index faad587868abd0ededffc5898938dccc8f6e11a8..8fe68083e61fde8d634f5d7d589bd777e4065c69 100644
--- a/docs/cmd/flux_install.md
+++ b/docs/cmd/flux_install.md
@@ -45,7 +45,7 @@ flux install [flags]
       --network-policy             deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
       --registry string            container registry where the toolkit images are published (default "ghcr.io/fluxcd")
       --toleration-keys strings    list of toleration keys used to schedule the components pods onto nodes with matching taints
-  -v, --version string             toolkit version (default "latest")
+  -v, --version string             toolkit version, when specified the manifests are downloaded from https://github.com/fluxcd/flux2/releases
       --watch-all-namespaces       watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed (default true)
 ```
 
diff --git a/manifests/scripts/bundle.sh b/manifests/scripts/bundle.sh
new file mode 100755
index 0000000000000000000000000000000000000000..b5f8960744cdc6a68e8ea0cc4a2faedf4624e4b8
--- /dev/null
+++ b/manifests/scripts/bundle.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+
+# Copyright 2020 The Flux authors. All rights reserved.
+#
+# 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.
+
+set -e
+
+REPO_ROOT=$(git rev-parse --show-toplevel)
+OUT_PATH=${1:-"${REPO_ROOT}/cmd/flux/manifests"}
+TAR=${2}
+
+info() {
+    echo '[INFO] ' "$@"
+}
+
+fatal() {
+    echo '[ERROR] ' "$@" >&2
+    exit 1
+}
+
+build() {
+  info "building $(basename $2)"
+  kustomize build "$1" > "$2"
+}
+
+if ! [ -x "$(command -v kustomize)" ]; then
+  fatal 'kustomize is not installed'
+fi
+
+rm -rf $OUT_PATH
+mkdir -p $OUT_PATH
+files=""
+
+info using "$(kustomize version --short)"
+
+# build controllers
+for controller in ${REPO_ROOT}/manifests/bases/*/; do
+    output_path="${OUT_PATH}/$(basename $controller).yaml"
+    build $controller $output_path
+    files+=" $(basename $output_path)"
+done
+
+# build rbac
+rbac_path="${REPO_ROOT}/manifests/rbac"
+rbac_output_path="${OUT_PATH}/rbac.yaml"
+build $rbac_path $rbac_output_path
+files+=" $(basename $rbac_output_path)"
+
+# build policies
+policies_path="${REPO_ROOT}/manifests/policies"
+policies_output_path="${OUT_PATH}/policies.yaml"
+build $policies_path $policies_output_path
+files+=" $(basename $policies_output_path)"
+
+# create tarball
+if [[ -n $TAR ]];then
+  info "archiving $TAR"
+  cd ${OUT_PATH} && tar -czf $TAR $files
+fi
diff --git a/pkg/manifestgen/install/install.go b/pkg/manifestgen/install/install.go
index f5168545158c1b2667ac238532732cd76fd440cc..e1b8403773cdbe9869da8c6e4f2c9f62770c4e60 100644
--- a/pkg/manifestgen/install/install.go
+++ b/pkg/manifestgen/install/install.go
@@ -35,17 +35,15 @@ import (
 // Generate returns the install manifests as a multi-doc YAML.
 // The manifests are built from a GitHub release or from a
 // Kustomize overlay if the supplied Options.BaseURL is a local path.
-func Generate(options Options) (*manifestgen.Manifest, error) {
+// The manifestsBase should be set to an empty string when Generate is
+// called by consumers that don't embed the manifests.
+func Generate(options Options, manifestsBase string) (*manifestgen.Manifest, error) {
 	ctx, cancel := context.WithTimeout(context.Background(), options.Timeout)
 	defer cancel()
 
-	tmpDir, err := ioutil.TempDir("", options.Namespace)
-	if err != nil {
-		return nil, fmt.Errorf("temp dir error: %w", err)
-	}
-	defer os.RemoveAll(tmpDir)
+	var err error
 
-	output, err := securejoin.SecureJoin(tmpDir, options.ManifestFile)
+	output, err := securejoin.SecureJoin(manifestsBase, options.ManifestFile)
 	if err != nil {
 		return nil, err
 	}
@@ -55,15 +53,27 @@ func Generate(options Options) (*manifestgen.Manifest, error) {
 			return nil, err
 		}
 	} else {
-		if err := fetch(ctx, options.BaseURL, options.Version, tmpDir); err != nil {
-			return nil, err
+		// download the manifests base from GitHub
+		if manifestsBase == "" {
+			manifestsBase, err = ioutil.TempDir("", options.Namespace)
+			if err != nil {
+				return nil, fmt.Errorf("temp dir error: %w", err)
+			}
+			defer os.RemoveAll(manifestsBase)
+			output, err = securejoin.SecureJoin(manifestsBase, options.ManifestFile)
+			if err != nil {
+				return nil, err
+			}
+			if err := fetch(ctx, options.BaseURL, options.Version, manifestsBase); err != nil {
+				return nil, err
+			}
 		}
 
-		if err := generate(tmpDir, options); err != nil {
+		if err := generate(manifestsBase, options); err != nil {
 			return nil, err
 		}
 
-		if err := build(tmpDir, output); err != nil {
+		if err := build(manifestsBase, output); err != nil {
 			return nil, err
 		}
 	}
diff --git a/pkg/manifestgen/install/install_test.go b/pkg/manifestgen/install/install_test.go
index 834b26d4d2aa9727fd03de05bb1f301de757ac5d..eab771ad51f6135db7b7ff46e421fbbb718d04ba 100644
--- a/pkg/manifestgen/install/install_test.go
+++ b/pkg/manifestgen/install/install_test.go
@@ -25,7 +25,7 @@ import (
 func TestGenerate(t *testing.T) {
 	opts := MakeDefaultOptions()
 	opts.TolerationKeys = []string{"node.kubernetes.io/controllers"}
-	output, err := Generate(opts)
+	output, err := Generate(opts, "")
 	if err != nil {
 		t.Fatal(err)
 	}