diff --git a/cmd/flux/install.go b/cmd/flux/install.go
index 0af11cee46fdfbe5bf670b7cfb75fac1913483f7..4d0e6d82565a6e4ace81cb2fd3c894a0f94dca9d 100644
--- a/cmd/flux/install.go
+++ b/cmd/flux/install.go
@@ -194,7 +194,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
 		return nil
 	}
 
-	applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, filepath.Join(tmpDir, manifest.Path))
+	applyOutput, err := utils.Apply(ctx, kubeconfigArgs, kubeclientOptions, tmpDir, filepath.Join(tmpDir, manifest.Path))
 	if err != nil {
 		return fmt.Errorf("install failed: %w", err)
 	}
diff --git a/internal/bootstrap/bootstrap_plain_git.go b/internal/bootstrap/bootstrap_plain_git.go
index 863c4cd7aa05e5f1d664085de0a8eaf4a3b6b29b..685c1f0dbee9de96de7b9afd3ee37b505ba02a27 100644
--- a/internal/bootstrap/bootstrap_plain_git.go
+++ b/internal/bootstrap/bootstrap_plain_git.go
@@ -163,19 +163,18 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
 
 	// Conditionally install manifests
 	if mustInstallManifests(ctx, b.kube, options.Namespace) {
-		componentsYAML := filepath.Join(b.git.Path(), manifests.Path)
+		b.logger.Actionf("installing components in %q namespace", options.Namespace)
 
-		// Apply components using any existing customisations
+		componentsYAML := filepath.Join(b.git.Path(), manifests.Path)
 		kfile := filepath.Join(filepath.Dir(componentsYAML), konfig.DefaultKustomizationFileName())
 		if _, err := os.Stat(kfile); err == nil {
 			// Apply the components and their patches
-			b.logger.Actionf("installing components in %q namespace", options.Namespace)
-			if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, kfile); err != nil {
+			if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.git.Path(), kfile); err != nil {
 				return err
 			}
 		} else {
 			// Apply the CRDs and controllers
-			if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, componentsYAML); err != nil {
+			if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.git.Path(), componentsYAML); err != nil {
 				return err
 			}
 		}
@@ -328,7 +327,7 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
 
 	// Apply to cluster
 	b.logger.Actionf("applying sync manifests")
-	if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, filepath.Join(b.git.Path(), kusManifests.Path)); err != nil {
+	if _, err := utils.Apply(ctx, b.restClientGetter, b.restClientOptions, b.git.Path(), filepath.Join(b.git.Path(), kusManifests.Path)); err != nil {
 		return err
 	}
 
diff --git a/internal/utils/apply.go b/internal/utils/apply.go
index 95badc1f61553fb6c6f2dba59e7f40f89cb8018b..1979759b4fe90d480d09a14db216c4b0f9637907 100644
--- a/internal/utils/apply.go
+++ b/internal/utils/apply.go
@@ -31,16 +31,15 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/kustomize/api/konfig"
 
+	"github.com/fluxcd/flux2/pkg/manifestgen/kustomization"
 	runclient "github.com/fluxcd/pkg/runtime/client"
 	"github.com/fluxcd/pkg/ssa"
-
-	"github.com/fluxcd/flux2/pkg/manifestgen/kustomization"
 )
 
 // Apply is the equivalent of 'kubectl apply --server-side -f'.
 // If the given manifest is a kustomization.yaml, then apply performs the equivalent of 'kubectl apply --server-side -k'.
-func Apply(ctx context.Context, rcg genericclioptions.RESTClientGetter, opts *runclient.Options, manifestPath string) (string, error) {
-	objs, err := readObjects(manifestPath)
+func Apply(ctx context.Context, rcg genericclioptions.RESTClientGetter, opts *runclient.Options, root, manifestPath string) (string, error) {
+	objs, err := readObjects(root, manifestPath)
 	if err != nil {
 		return "", err
 	}
@@ -92,13 +91,17 @@ func Apply(ctx context.Context, rcg genericclioptions.RESTClientGetter, opts *ru
 	return changeSet.String(), nil
 }
 
-func readObjects(manifestPath string) ([]*unstructured.Unstructured, error) {
-	if _, err := os.Stat(manifestPath); err != nil {
+func readObjects(root, manifestPath string) ([]*unstructured.Unstructured, error) {
+	fi, err := os.Lstat(manifestPath)
+	if err != nil {
 		return nil, err
 	}
+	if fi.IsDir() || !fi.Mode().IsRegular() {
+		return nil, fmt.Errorf("expected %q to be a file", manifestPath)
+	}
 
-	if filepath.Base(manifestPath) == konfig.DefaultKustomizationFileName() {
-		resources, err := kustomization.Build(filepath.Dir(manifestPath))
+	if isRecognizedKustomizationFile(manifestPath) {
+		resources, err := kustomization.BuildWithRoot(root, filepath.Dir(manifestPath))
 		if err != nil {
 			return nil, err
 		}
@@ -152,3 +155,13 @@ func waitForSet(rcg genericclioptions.RESTClientGetter, opts *runclient.Options,
 	}
 	return man.WaitForSet(changeSet.ToObjMetadataSet(), ssa.WaitOptions{Interval: 2 * time.Second, Timeout: time.Minute})
 }
+
+func isRecognizedKustomizationFile(path string) bool {
+	base := filepath.Base(path)
+	for _, v := range konfig.RecognizedKustomizationFileNames() {
+		if base == v {
+			return true
+		}
+	}
+	return false
+}
diff --git a/pkg/manifestgen/kustomization/kustomization.go b/pkg/manifestgen/kustomization/kustomization.go
index 45a4031532222c48c40def58c73565f0c7edadbf..058049b75ba5759f045fd29f1c8d1ed794e2d2f0 100644
--- a/pkg/manifestgen/kustomization/kustomization.go
+++ b/pkg/manifestgen/kustomization/kustomization.go
@@ -138,14 +138,22 @@ func Generate(options Options) (*manifestgen.Manifest, error) {
 var kustomizeBuildMutex sync.Mutex
 
 // Build takes the path to a directory with a konfig.RecognizedKustomizationFileNames,
-// builds it, and returns the resulting manifests as multi-doc YAML.
+// builds it, and returns the resulting manifests as multi-doc YAML. It restricts the
+// Kustomize file system to the parent directory of the base.
 func Build(base string) ([]byte, error) {
+	// TODO(hidde): drop this when consumers have moved away to BuildWithRoot.
+	parent := filepath.Dir(strings.TrimSuffix(base, string(filepath.Separator)))
+	return BuildWithRoot(parent, base)
+}
+
+// BuildWithRoot takes the path to a directory with a konfig.RecognizedKustomizationFileNames,
+// builds it, and returns the resulting manifests as multi-doc YAML.
+// The Kustomize file system is restricted to root.
+func BuildWithRoot(root, base string) ([]byte, error) {
 	kustomizeBuildMutex.Lock()
 	defer kustomizeBuildMutex.Unlock()
 
-	// TODO(hidde): make this configurable to a specific root (relative to base)
-	parent := filepath.Dir(strings.TrimSuffix(base, string(filepath.Separator)))
-	fs, err := filesys.MakeFsOnDiskSecureBuild(parent)
+	fs, err := filesys.MakeFsOnDiskSecureBuild(root)
 	if err != nil {
 		return nil, err
 	}