FluxCD GitOps Made Simple: My Journey to Automated Kubernetes Deployments
When I first started exploring GitOps and Kubernetes, the landscape felt a bit overwhelming. I had obviously experienced various automation techniques throughout my years but on Kubernetes, I had heard about automation on Kubernetes and declarative infrastructure, but the practical steps to get there were unclear. This blog post shares my journey implementing FluxCD for GitOps-driven Kubernetes deployments, both locally and in a sandbox environment, and how I structured my repository for clarity and scalability.
Pre-requisites
The following tools installed are required to follow this blog post (on macOS X):
- Docker Desktop, download at https://www.docker.com/products/docker-desktop/
- Google Cloud SDK, installed with:
brew install google-cloud-sdk - Kubectl, installed with:
brew install kubectl - Helm, installed with:
brew install helm - FluxCD, installed with:
brew install fluxcd - Istio, download at https://istio.io/latest/docs/setup/getting-started/
- Terraform, installed with:
brew install terraformorbrew install tfenv
Technology Stack
My technology stack for my personal project consists of:
- Kubernetes, via Docker Desktop for local development, and via GKE on Google Cloud Platform for my cloud sandbox instances.
- Terraform for the Infrastructure as Code for the sandbox setup
- Helm as the deployment manager
- Cert-Manager for the cluster issuer and certificates, for both local with self-signed certificates and Let's Encrypt certificates for my sandbox
- Istio as my service mesh
- Keycloak as my Identity Provider
- OAuth2-proxy as my external authorization, connected from Istio for session management
- Node.js for the API code, as an example
- REACT VITE app for the applications I built as examples to connect to the API
- GitLab as the CI/CD and Git repositories
Getting Started: From Zero to GitOps
Like many, I began with limited knowledge of GitOps or FluxCD. My initial goal was simple: automate my Kubernetes deployments and manage everything through a solid version control system. At the same time, I wanted to learn about the technique as well as the best practices to ensure a proper use within my work context. After some research, I was struggling to choose between FluxCD and ArgoCD. The community has been strong on both sides while the various articles comparing the two always come back with the same conclusion: both are solid GitOps and automation tool for Kubernetes and it amounts to personal preferences. I chose FluxCD for its strong community, integration with GitLab (as well as the recommended solution by GitLab themselves), and support for advanced features like image update automation and Helm chart management. I also selected it for the supposedly simplicity to learn, and although it is simpler, I wouldn't say it is "simple".
Key Concepts
As a reminder, here are the key concepts of GitOps as well as FluxCD that I worked from, with a bit of my own interpretation around them and how I perceived their implementation within my context:
GitOps principles
1. DeclarativeA system managed by GitOps must have its desired state expressed declaratively.
Meaning... The state of the infrastructure and application setup must be contained within one or many Git repos!
2. Versioned and ImmutableDesired state is stored in a way that enforces immutability, versioning and retains a complete version history.
Meaning... You have to be able to refer to a version and recreate the exact same infrastructure and application configuration, which mostly mean in my world adding a versioned tag in my Git repo so I can refer to it later.
3. Pulled AutomaticallySoftware agents automatically pull the desired state declarations from the source.
Meaning... Changes submitted from the previous two principles are automatically pulled or pushed (I know, there are debates if "push" can be considered as "GitOps" but for my own sanity - and I suspect for a lot of others - I do consider a "push" system valid). In my current setup, I use both "push" and "pull" to ensure the desired state is always in sync with the actual state. I use "pull" using the FluxCD automation and configuration while I mostly use the "push" for the CI portion of my entire project.
4. Continuously ReconciledSoftware agents continuously observe actual system state and attempt to apply the desired state.
Meaning... The "pulling" (or "pushing") must be done constantly for validation and adjusted if any discrepancies are found. In my current project, FluxCD is constantly pulling the desired state from the Git repo and pushing the actual state to the Kubernetes cluster. Then GitLab CI pipelines are constantly executing as soon as a change is detected in the Git repo.
FluxCD
If you are not familiar with FluxCD, I highly recommend to read the documentation at https://fluxcd.io/flux/. It is extremely well written for newbies like me and allowed me to start fairly quickly and build on top of it as I was adding requirements and needs.
The important thing to remember, FluxCD will be critical to complete the principle #3 and #4 from the above list for the management of the desired state of my Kubernetes cluster.
Repository Structure: Organizing for Success
One of the first challenges was designing a repository structure that could support multiple environments and scale as my needs grew. At first, everything was built out of the "base" folders but eventually, I learned about Kustomize, mixed with FluxCD, and setup the structure properly to manage both my local and sandbox clusters. Here's how I organized my FluxCD repository:
fluxcdboucio/
├── clusters/ # Environment-specific configs
│ ├── local/ # Local development
│ └── sandbox/ # Sandbox (cloud) environment
├── infrastructure/ # Infrastructure components
│ ├── base/ # Common base configs
│ └── overlays/ # Environment-specific overlays
├── apps/ # Application deployments
│ └── examples/ # Example apps and overlays
└── README.md
This structure keeps environment-specific settings isolated, while sharing common infrastructure and application definitions.
Note, my needs are very limited and do not require various access scheme, segregation of duty, etc. So in my case, I set it up with only one Git repo for FluxCD. If you need more granularity and control over access for software engineers versus infrastructure engineers, I would suggest to investigate many Git repos for FluxCD (i.e. one repo for "infrastructure", another for the "application", etc.). In my case, I have a single repo for FluxCD and I have a single GitLab deploy token for the entire project.
Bootstrapping FluxCD: Local and Sandbox Environments
With the structure in place, I set out to bootstrap FluxCD for both my local (Docker Desktop) and
sandbox (GCP) clusters. The process was straightforward thanks to FluxCD's bootstrap
command and GitLab integration. Here's how I did it:
1. Prepare GitLab Deploy Token
- Create a deploy token in GitLab with
read_repository,read_registry,write_registry, and related permissions. - Export the token as
GITLAB_TOKENin your shell.
2. Bootstrap FluxCD
For the local cluster:
flux bootstrap gitlab \
--token-auth=true \
--read-write-key=true \
--owner=bouc-io/fluxcd \
--repository=fluxcdboucio \
--branch=master \
--path=clusters/local \
--components=source-controller,kustomize-controller,helm-controller,image-reflector-controller,image-automation-controller \
--visibility=private --personal=false --author-name "AUTHOR" --author-email "EMAIL" --verbose \
--cluster docker-desktop
For the sandbox (GCP) cluster:
flux bootstrap gitlab \
--token-auth=true \
--read-write-key=true \
--owner=bouc-io/fluxcd \
--repository=fluxcdboucio \
--branch=master \
--path=clusters/sandbox \
--components=source-controller,kustomize-controller,helm-controller,image-reflector-controller,image-automation-controller \
--visibility=private --personal=false --author-name "AUTHOR" --author-email "EMAIL" --verbose \
--cluster CLUSTERNAME
Make sure your kubectl context is set to the correct cluster before running these
commands.
Secrets and ConfigMaps: Managing Sensitive Data
To enable secure deployments, I created Kubernetes secrets for Docker registry and GitLab access, as well as ConfigMaps for environment-specific values. For example:
kubectl create secret docker-registry gitlab-fluxcd-imgrepo-access \
--namespace=flux-system \
--docker-server=registry.gitlab.com \
--docker-username=<username bot> \
--docker-password=<token password> \
--cluster
And for ConfigMaps:
kubectl create configmap static-web-sandbox-values \
--from-file=sandbox.values.yaml \
--namespace=default \
--cluster
This approach keeps secrets out of version control and allows for flexible configuration per environment.
Another note here, since I was using this usual Helm technique with a local YAML file during the installation, I continue to do so with my FluxCD configuration. In other words, my HelmRelease object refer to values to be found within the namespace under a specific ConfigMap name. I am currently researching how to properly set that up while respecting GitOps principles; see my next steps.
Working Locally: Fast Feedback and Customization
For local development, I often need to test changes quickly. By modifying the overlays in
apps/examples/overlays/local, I could point deployments to locally built images and
apply changes instantly:
image:
repository: api-example
imagePullPolicy: Never
tag: "0.3.42"
Then apply the overlay:
kubectl apply -f apps/examples/overlays/local/flux-api-chart.yaml
This workflow enables rapid iteration without pushing every change to the remote registry. Once complete, I can simply reset my local Git repo back to the original state and move on to the next change.
Conclusion & Next Steps
Implementing FluxCD has helped me how I manage Kubernetes deployments, bringing automation, repeatability, and peace of mind. My setup now supports both local and cloud environments, with a clear structure for infrastructure and applications. Next, I plan to:
- Adopt OCI Helm repositories as they become available in GitLab (GitLab doesn't support OCI helm repositories yet)
- Automate more secrets management (e.g., using Sealed Secrets or external secret stores)
- Expand GitOps to my configurations values for each component (i.e. Istio, Cert-Manager, OAuth2-proxy, Keycloak)
- Integrate more advanced monitoring and progressive delivery (e.g. Istio)
If you're starting your own GitOps journey, I hope this post helps you avoid some of my early confusion and inspires you to automate your Kubernetes workflows with FluxCD!
This blog post was revised on 2025-07-05 for improvements on readability and clarity.
AI Usage Disclosure
This document was created with assistance from AI tools. The content has been reviewed and edited by a human. For more information on the extent and nature of AI usage, please contact the author.