You are viewing documentation for Flux version: 2.1

Version 2.1 of the documentation is no longer actively maintained. The site that you are currently viewing is an archived snapshot. For up-to-date documentation, see the latest version.

Watching for source changes

Develop a Kubernetes controller that reacts to source changes.

In this guide you’ll be developing a Kubernetes controller with Kubebuilder that subscribes to GitRepository events and reacts to revision changes by downloading the artifact produced by source-controller.


On your dev machine install the following tools:

  • go >= 1.20
  • kubebuilder >= 3.0
  • kind >= 0.17
  • kubectl >= 1.25

Install Flux

Install the Flux CLI with Homebrew on macOS or Linux:

brew install fluxcd/tap/flux

Create a cluster for testing:

kind create cluster --name dev

Verify that your dev machine satisfies the prerequisites with:

flux check --pre

Install source-controller on the dev cluster:

flux install \
--namespace=flux-system \
--network-policy=false \

Clone the sample controller

You’ll be using fluxcd/source-watcher as a template for developing your own controller. The source-watcher was scaffolded with kubebuilder init.

Clone the source-watcher repository:

git clone
cd source-watcher

Build the controller:


Run the controller

Port forward to source-controller artifacts server:

kubectl -n flux-system port-forward svc/source-controller 8181:80

Export the local address as SOURCE_CONTROLLER_LOCALHOST:


Run source-watcher locally:

make run

Create a Git source:

flux create source git test \
--url= \
--ignore-paths='/*,!/manifests' \

The source-watcher will log the revision:

{"level":"info","ts":"2023-03-31T16:43:42.703+0200","msg":"New revision detected","controller":"gitrepository","controllerGroup":"","controllerKind":"GitRepository","GitRepository":{"name":"test","namespace":"flux-system"},"namespace":"flux-system","name":"test","reconcileID":"ef0fe80e-3952-4835-ae9d-01760c4eadde","revision":"v0.41.2@sha1:81606709114f6d16a432f9f4bfc774942f054327"}

Change the Git tag:

flux create source git test \
--url= \
--ignore-paths='/*,!/manifests' \

And source-watcher will log the new revision:

{"level":"info","ts":"2023-03-31T16:51:33.499+0200","msg":"New revision detected","controller":"gitrepository","controllerGroup":"","controllerKind":"GitRepository","GitRepository":{"name":"test","namespace":"flux-system"},"namespace":"flux-system","name":"test","reconcileID":"cc0f83bb-b7a0-4c19-a254-af9962ae39cd","revision":"v2.0.0-rc.1@sha1:658925c2c0e6c408597d907a8ebee06a9a6d7f30"}

The source-controller reports the revision under GitRepository.Status.Artifact.Revision in the format: <branch|tag>@sha1:<commit>.

How it works

The GitRepositoryWatcher controller does the following:

  • subscribes to GitRepository events
  • detects when the Git revision changes
  • downloads and extracts the source artifact
  • writes the extracted dir names to stdout
type GitRepositoryWatcher struct {
	HttpRetry       int
	artifactFetcher *fetch.ArchiveFetcher

func (r *GitRepositoryWatcher) SetupWithManager(mgr ctrl.Manager) error {
	r.artifactFetcher = fetch.NewArchiveFetcher(

	return ctrl.NewControllerManagedBy(mgr).
		For(&sourcev1.GitRepository{}, builder.WithPredicates(GitRepositoryRevisionChangePredicate{})).


func (r *GitRepositoryWatcher) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := ctrl.LoggerFrom(ctx)

	// get source object
	var repository sourcev1.GitRepository
	if err := r.Get(ctx, req.NamespacedName, &repository); err != nil {
		return ctrl.Result{}, client.IgnoreNotFound(err)

	artifact := repository.Status.Artifact
	log.Info("New revision detected", "revision", artifact.Revision)

	// create tmp dir
	tmpDir, err := os.MkdirTemp("", repository.Name)
	if err != nil {
		return ctrl.Result{}, fmt.Errorf("failed to create temp dir, error: %w", err)
	defer os.RemoveAll(tmpDir)

	// download and extract artifact
	if err := r.artifactFetcher.Fetch(artifact.URL, artifact.Digest, tmpDir); err != nil {
		log.Error(err, "unable to fetch artifact")
		return ctrl.Result{}, err

	// list artifact content
	files, err := os.ReadDir(tmpDir)
	if err != nil {
		return ctrl.Result{}, fmt.Errorf("failed to list files, error: %w", err)

	// do something with the artifact content
	for _, f := range files {
		log.Info("Processing " + f.Name())

	return ctrl.Result{}, nil

To add the watcher to an existing project, copy the controller and the revision change predicate to your controllers dir:

In your main.go init function, register the Source API schema:

import (
	utilruntime ""
	clientgoscheme ""

	sourcev1 ""

func init() {

	// +kubebuilder:scaffold:scheme

Start the controller in the main function:

func main()  {

	if err = (&controllers.GitRepositoryWatcher{
		Client:    mgr.GetClient(),
		HttpRetry: httpRetry,
	}).SetupWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "GitRepositoryWatcher")


Note that the watcher depends on Flux runtime and Kubernetes controller-runtime:

require ( v0.35.0 v0.14.6

That’s it! Happy hacking!

Last modified 2023-03-31: update GitOps Toolkit guides (102f4b5)