Flux, or FluxCD, is a “set of continuous and progressive delivery solutions for Kubernetes that are open and extensible” and is my preferred way to do “GitOps” in my Kubernetes clusters. I like Flux because it handles Helm extremely well (I’m a big fan of Helm) and allows me to have a simple fallback if something goes wrong. In this post, I will go through the process of installing flux, k3s that can be used for testing and then creating a Flux project that adopts Flux’s documented monorepo design. I have also published a copy of the work detailed here to Github so you can use as a working starter for your own project.
Required Tools and Software
Flux works with virtually any Kubernetes solution but for this post I am assuming you are using k3s and will guide you through installing that first. Additionally I am assuming you are using a Unix or Unix like environment such as Linux, macOS or WSL2. My examples will concentrate on macOS, since that is what I use. All options can support brew and I recommend installing that first if you don’t have it. Brew makes installing all the tools much easier. The complete list of requirements are:
- git repository hosting: You will need some place where you are hosting your repository. Flux supports a number of options so before you go any further check the list and make sure you are using something it supports – https://fluxcd.io/flux/installation/bootstrap/
- colima:
brew install colima
– colima provides you with a Docker environment that also includes k3s which you can easily activate. If you already have a Kubernetes solution installed like Docker Desktop you can use that as well. - flux:
brew install fluxcd/tap/flux
– installs the Flux cli tool. - helm:
brew install helm
– installs Helm. - kubectl:
brew install kubectl
– installs the Kubernetes command line client. - k9s:
brew install k9s
– installs a nice console, text based UI for Kubernetes
Of course, you can combine all of these requirements into a single brew command: brew install colima fluxcd/tap/flux helm kubectl
k9s.
If you install k3s manually or with a different method, please disable the traefik ingress controller. Here is an example command to achieve this: curl -sfL https://get.k3s.io | K3S_NODE_NAME=k3s sh -s - --disable=traefik
Setup colima and k3s
Colima is quite simple to get running and configured. Starting with a fresh system, running colima start -c 6 -m 6
will start a new instance with 6 cores and 6GB memory dedicated to the virtual machine. The first time it starts may take some extra time as it will download a machine image. It will also leave Kubernetes/k3s disabled but ready to go. I prefer having k3s disabled because most of the time I do not run k3s on my local system. Next, start k3s by running colima k3s start
. This will start k3s inside of colima and switch your Kubernetes configuration to point at the cluster. Validate you can connect to your k3s instance using kubectl version
and you should get back the version of Kubernetes. At the time of this writing, this is what I get back:
kubectl version
Client Version: v1.32.0
Kustomize Version: v5.5.0
Server Version: v1.31.2+k3s1
Initialize Flux
When Flux is installed, or bootstrapped, it will want to initialize a directory and repository that belongs to the cluster you intend to control. In this directory, we will later make our configuration changes. The repository itself must exist and, when starting from scratch, be empty. You must also have whatever authentication requirements are detailed on the bootstrap page provided earlier. For my example, I will use Github. When I created the repository, the form looked like this:

Note that I created a public repository with no readme or .gitignore
file. I already have a Github token that has access to this repository, but if you don’t you would want to then create one.
Next, bootstrap the project using the example provided in the Flux documentation for how to bootstrap a project. Since I used a Github based repository, I came up with this command:
flux bootstrap github \
--token-auth \
--owner=dustinrue \
--repository=flux-monorepo-example \
--branch=main \
--path=clusters/colima \
--personal
Note that I already keep my Github token as part of my environment using the named expected by Flux. How I did this is detailed in this post. Once complete, the repository will be updated and new components will have been installed into the cluster. The next two screenshots show my updated Github repository and a view of my cluster after the initialization process:


At this point, the flux system is installed and will watch the repository for changes. Let’s get things setup.
Building the layout
A quick note before we go deeper. What flux generated should not be modified at any time. Changing the default files flux generates can lead to problems later on as you upgrade flux. It is best to let flux manage the files inside of the flux-system directory on its own while you keep your work outside of it. The flux-system directory is the “entry point” for the Flux solution and from here we will reference all other files to be applied to our cluster.
For those familiar, Flux leverages Kustomize under the hood. If you understand Kustomize, then how Flux works may feel very natural. In this example, Helm will be leveraged to install an application though you can use other types of resources to manage cluster state.
The first app we will install into the cluster is ingress-nginx. Start by cloning your new repository locally. Then, in the repository, create a new directory called sources and then inside of that one called helm. mkdir -p sources/helm
is a one shot method to get this done. In the helm directory, create a file called ingress-inginx.yaml
and put the following contents into it:
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: ingress-nginx
namespace: default
spec:
interval: 60m0s
url: https://kubernetes.github.io/ingress-nginx
This file defines a few things. First, we are creating a source of type HelmRepository called ingress-inginx which will live in the default namespace. It will be checked every hour (60 minutes) and the URL for the repository is https://kubernetes.github.io/ingress-nginx. Save this file.
In the same directory, create another file called kustomization.yaml
and put the following into it:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ingress-nginx.yaml
For this to be applied, we must now reference this in the clusters/colima
directory that we created earlier. To do so, create a file called sources.yaml
and put in the following contents:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: sources
namespace: flux-system
spec:
interval: 1h
retryInterval: 1m
timeout: 5m
sourceRef:
kind: GitRepository
name: flux-system
path: ./sources/helm
prune: true
wait: true
This file defines a few things. First, it says we are creating an object called sources in the flux-system namespace. The contents of this object is a GitRepository called flux-system. This GitRepository exists because we initialized it using the bootstrap command. In other words, we are self referencing our repository and saying there are sources located inside the repository but at a different path than our flux-system path. Indeed, you could reference a GitRepository from a different Git provider entirely. This is simply a different kind of source. When committed and push to the repository, Flux will notice the change and apply it to the cluster. Let’s see that in action by adding the files and pushing them up to GitHub. In Finder, the directory layout now looks like this:

You can watch flux ingest the changes using flux logs -f
and you will eventually see a message matching your commit message:
2025-03-09T17:46:30.360Z info GitRepository/flux-system.flux-system - stored artifact for commit 'adds our first source'
2025-03-09T17:46:30.521Z info Kustomization/sources.flux-system - server-side apply completed
2025-03-09T17:46:30.604Z info Kustomization/flux-system.flux-system - server-side apply for cluster definitions completed
2025-03-09T17:46:30.647Z info Kustomization/flux-system.flux-system - server-side apply completed
2025-03-09T17:46:30.652Z info Kustomization/flux-system.flux-system - Reconciliation finished in 284.633212ms, next run in 10m0s
2025-03-09T17:46:35.562Z info Kustomization/sources.flux-system - Reconciliation finished in 5.195390042s, next run in 1h0m0s
Running kubectl get helmrepositories -n default
will result in the following output:
NAME URL AGE READY STATUS
ingress-nginx https://kubernetes.github.io/ingress-nginx 52s True stored artifact: revision 'sha256:79f424dbbc344395f9cf19d4ade457e9de14af43a337bd76ee593fa1ad920330'
Create a catalog of applications
Now we will define our apps. First we’ll create a catalog of apps and then combine them into a collection. A collection of apps is how we will assign a group of apps to target clusters so that they are consistent. Maybe you’d have a collection for a cluster responsible for running Mastodon and another that monitors applications. With this arrangement you can do almost anything you want.
Continuing the example, the application we’ll work with is ingress-nginx. Since traefik is disabled, we need an ingress controller anyway.
At the root of the repository, create a new directory called apps
, then in apps
create catalog
and finally, inside catalog
, create another directory called ingress-nginx
. Like before, the one shot command would be mkdir -p apps/catalog/ingress-nginx
. Create the following files:
- kustomization.yaml
- main.yaml
- values.yaml
In kustomization.yaml
put in the following contents:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- main.yaml
- values.yaml
Like before, this is a Kustomization file that defines what other files should be included.
Next, in main.yaml put in:
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: ingress-nginx
namespace: default
spec:
interval: 1m
chart:
spec:
chart: ingress-nginx
version: "4.x.x"
sourceRef:
kind: HelmRepository
name: ingress-nginx
namespace: default
interval: 1m
valuesFrom:
- kind: ConfigMap
name: ingress-nginx-values
valuesKey: values
This file is defining a number of things. Among other things, it defines we can to install an app into the default namespace. The name of the app, or in Helm terms, the release name, we are installing ingress-nginx. The chart is coming from HelmRepository we created earlier in the default namespace called ingress-nginx. We are requesting that major version 4 of the chart be used and any minor or patch releases should be applied automatically. Remember, Flux will check the Helm Repository we defined earlier every hour for updates. By specifying the version as 4.x.x, we are saying Flux should automatically upgrade ingress-nginx as long as the major release is still 4.
The last section defines where to find our values for the chart. Not all charts require values to be set, but for the purpose of demonstration we are defining values in a separate file which will become a ConfigMap in Kubernetes.
In the file, values.yaml
, put:
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-values
namespace: default
data:
values: |
USER-SUPPLIED VALUES:
controller:
autoscaling:
enabled: true
maxReplicas: 1
minReplicas: 1
targetCPUUtilizationPercentage: 50
targetMemoryUtilizationPercentage: 100
config:
use-forwarded-headers: "true"
ingressClassResource:
default: false
resources:
requests:
cpu: 50m
memory: 128Mi
This file defines the values to override in the Helm chart for ingress-nginx. Notice how the name supplied here matches the name referenced in main.yaml. This is important.
Create a collection of applications
Now we will combine applications into a collection to be installed into a target cluster. Although we have only defined a single application, you could install any number of applications using this method. Start by creating an additional directory in apps
called collections
. Then create a directory that names your collection. For the sake of this example, I will call my collection example
. Inside example create a kustomization.yaml
file with the following contents:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: default
resources:
- ../../catalog/ingress-nginx
Putting it all together
Now that an app collection has been defined, it can be installed into the cluster by creating another file called apps.yaml
in clusters/colima
. Note that the names sources.yaml and apps.yaml are arbitrary, they could be anything (other than kustomization.yaml). I used names to help me understand the purpose of each file. In apps.yaml add the following:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 1h
retryInterval: 1m
timeout: 5m
dependsOn:
- name: sources
sourceRef:
kind: GitRepository
name: flux-system
path: ./apps/collections/example
prune: true
wait: true
Notice how this file references a collection of apps, which itself is a kustomization file that references the actual apps. In a future post I will extend this example to show how you can override values that are passed to the app so they are specific to the environment you are working with. This is how the file system will look now:

Add and commit all of these changes and push then up to your Git server and allow your cluster to pull in the updated info. You will now be able to kubectl get helmrelease -n default
and see the ingress-nginx has been added. Running kubectl get pods -n default
will list a running ingress-inginx pod.
If you had an additional cluster, you could apply the exact same set of applications by assigning the same collection to it.
Finishing up
I hope this post helped further your understanding of how to setup a monorepo layout for use with Flux and that the benefit of doing this, even installing a single application, is more obvious than before. The repository for the work detailed here is at https://github.com/dustinrue/flux-monorepo-example and will work as an excellent starter. As mentioned, a future post will dive a bit deeper into how to customize the installation of software for a given cluster or environment.