# Hands-On with FluxCD: A Practical Guide to GitOps for Beginners

Whether you're a developer, DevOps engineer, sysadmin, or platform engineer, you've heard of GitOps. [GitOps](https://about.gitlab.com/topics/gitops/) is a methodology that extends the CI/CD model, allowing teams to manage infrastructure and application deployments using [Git](https://git-scm.com/) as a single source of truth. Using GitOps, however, might not be as straightforward as it seems. Some would even argue that its hidden complexity is not worth the hassle. GitOps with [FluxCD](https://fluxcd.io/) can help navigate these complexities, as FluxCD simplifies [continuous delivery](https://martinfowler.com/books/continuousDelivery.html) through its built-in reconciliation engine and more.

In this beginner's guide, I aim to break down some of the complexity and deliver some [tacit knowledge](https://helpjuice.com/blog/tacit-knowledge), allowing you to get hands-on with FluxCD. I'll show you the benefits of [FluxCD](https://github.com/fluxcd/flux), so you feel confident getting up and running. Also, you'll learn by doing an actual project!

## What is FluxCD?

FluxCD is an open-source continuous delivery tool designed for Kubernetes. It works by continuously monitoring your Git repository and applying any changes to your Kubernetes cluster. With FluxCD, you can automate updates and rollbacks and even monitor your deployments.

#### Key Features of FluxCD

* **GitOps**: FluxCD leverages Git as the single source of truth, enabling declarative management of infrastructure and application deployments.
    
* **Automated Deployments**: FluxCD continuously monitors your Git repository and automatically updates your Kubernetes cluster with the desired state.
    
* **Syncing and Reconciliation**: FluxCD ensures that the live state of your cluster matches the state defined in your Git repository.
    
* **Multi-Tenancy**: FluxCD supports managing multiple environments, allowing you to use the same Git repository for staging, production, and more.
    
* **Integrations**: FluxCD integrates well with tools like Helm, Prometheus, and others to provide a comprehensive solution for GitOps.
    

### Core Components of FluxCD

Before we dive into the hands-on part, let's briefly discuss the core components of FluxCD:

1. **Flux** is the primary operator (control plane), continuously monitoring the Git repository and applying changes to the Kubernetes cluster.
    
2. The **Helm Controller** is a component of FluxCD that allows you to manage Helm charts in a GitOps fashion.
    
3. The **Kustomize Controller** supports managing Kubernetes manifests using Kustomize, enabling you to customize your deployments.
    
4. The **Notification Controller** handles notifications and alerts for your deployments.
    
5. The **Source Controller** monitors the source of the Kubernetes manifests (Git, Helm repositories, etc.) and makes them available for the other controllers.
    

### Creating a GitHub Personal Access Token (PAT)

Since FluxCD uses Git as the single source of truth for deployments, the apparent prerequisite is a Git hosting service. Flux CLI creates a new GitHub repository for you, but it needs a personal access token (PAT) to do so.

Create a new PAT by going to the following GitHub link: [https://github.com/settings/tokens/new](https://github.com/settings/tokens/new)

Select the checkmark box next to "repo," as shown below, then click "Generate Token." **Copy the PAT**, as you will use it for the next step!

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1724434309458/46b3a0f0-818e-48ee-9e9e-50e6b9dfb5fe.gif align="center")

## Installing FluxCD

FluxCD can be installed using the `flux` CLI tool. Here's how you can set it up on your Kubernetes cluster. For free access to a cluster, head to [Killercoda.com](https://killercoda.com/chadmcrowell/course/cka/single-node) and follow along!

1. **Install the Flux CLI:**
    
    ```bash
    curl -s https://fluxcd.io/install.sh | sudo bash
    ```
    
2. **Bootstrap FluxCD:**
    
    The bootstrap command installs the FluxCD components and configures them to reconcile your cluster state with a Git repository (replace `<github-username>` with your [GitHub](https://github.com) username).
    
    ```bash
    flux bootstrap github \
      --owner=<github-username> \
      --repository=my-flux-repo \
      --branch=main \
      --path=./clusters/my-cluster \
      --personal=true \
      --private=false
    ```
    
3. **Verify the Installation:**
    
    After bootstrapping, you can verify that FluxCD is running correctly using the command `flux check --pre` which will check the health of the Flux components in your Kubernetes cluster.
    
    ```bash
    flux check --pre
    ```
    
4. **Clone the Repo:**  
    After running the `flux bootstrap github` command, you should clone the GitHub repository that Flux created during the bootstrap process. This allows you to add your application files (e.g., Kubernetes manifests, Helm charts, or Kustomize files) to the repository. These files define the desired state of your applications, which Flux will then monitor and apply to your Kubernetes cluster (replace `<github-username>` with your [GitHub](https://github.com) username).
    
    ```bash
    git clone https://github.com/<github-username>/my-flux-repo.git
    ```
    
5. **Create your Application Directory**  
    Inside the cloned repository, you will see the directory structure created by Flux. Typically, a specific directory is created to store the application manifests, helm charts or Kustomize configurations (e.g. `apps/my-app`). Create the directory where your application files will go.
    
    ```bash
    mkdir -p my-flux-repo/apps/my-app
    ```
    
6. **(Optional) Organize Your Repository**  
    For larger projects, you can structure your repository into separate directories for staging, production, and other environments. The following is an example of this organizational pattern, which allows you to define shared configurations in the base directory and customize them for each specific environment in the overlays directory, without duplicating code.
    
    ```plaintext
    apps/
    ├── my-app/
    │   ├── base/
    │   │   ├── deployment.yaml
    │   │   └── service.yaml
    │   ├── overlays/
    │       ├── staging/
    │       └── production/
    clusters/
    ├── staging/
    │   └── kustomization.yaml
    └── production/
        └── kustomization.yaml
    ```
    

## Building a Sample Microservices Application

In this example, we'll create a simple microservices-based application consisting of two services:

1. **Frontend Service**: A simple web server built using Node.js that serves static content.
    
2. **Backend Service**: A Python-based API service that provides some data to the frontend.
    

For most Kubernetes-based setups using FluxCD, the best practice is to store application code and deployment manifests in separate repositories. This adheres to GitOps principles, where your Git repository serves as the single source of truth for your cluster's desired state. Therefore, we’ll create a new application repository that contains our application code.

```bash
mkdir src && cd src
```

#### **Frontend Service**

The frontend service will be a basic Node.js application that serves an HTML page. Here's the code:

```bash
cat << EOF > index.js
// index.js
const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
  res.send('<h1>Welcome to the Frontend Service</h1>');
});

app.listen(port, () => {
  console.log('Frontend service is running on http://localhost:${port}');
});
EOF
```

Create a Dockerfile for the frontend service:

```bash
cat << EOF > JS-Dockerfile
# JS-Dockerfile
FROM node:14-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

EXPOSE 3000
CMD ["node", "index.js"]
EOF
```

#### Backend Service

The backend service will be a simple Python Flask application that returns some JSON data. Here's the code:

```bash
cat << EOF > app.py
# app.py
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/api/data', methods=['GET'])
def get_data():
    data = {
        'message': 'Hello from the Backend Service!'
    }
    return jsonify(data)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
EOF
```

Create a Dockerfile for the backend service:

```bash
cat << EOF > Py-Dockerfile
# Dockerfile
FROM python:3.8-slim

WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000
CMD ["python", "app.py"]
EOF
```

Create the `requirements.txt` file:

```bash
echo "flask" > requirements.txt
```

create a `package.json` file:

```bash
# package.json
cat << EOF > package.json
{
  "name": "flux-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "express": "^4.17.1"
  },
  "scripts": {
    "test": ""
  },
  "author": "",
  "license": "ISC"
}
EOF
```

#### **Build the Container Images**

We'll build the container images for both the front and backend apps using the Docker CLI. Use the following commands to build and tag the images using the two separate `Dockerfile` files named `JS-Dockerfile` and `Py-Dockerfile`:

```bash
docker build -f JS-Dockerfile -t localhost:5000/my-frontend .
```

```bash
docker build -f Py-Dockerfile -t localhost:5000/my-backend .
```

Create a container registry locally:

```bash
docker run --name local-registry -d -p 5000:5000 registry
```

Push the container images to the local registry:

```bash
docker push localhost:5000/my-backend
docker push localhost:5000/my-frontend
```

## Creating Kubernetes Manifests

Next, we'll create Kubernetes manifests for deploying these two services, placing them in the repo that we created and cloned at the beginning, in `my-flux-repo/apps/my-app`. Use your favorite text editor to create these files in the `my-app` directory.

```bash
cd $HOME/my-flux-repo/apps/my-app
```

#### Frontend Deployment Manifest

```yaml
# frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: default
  labels:
    app: frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: localhost:5000/my-frontend:latest
        ports:
        - containerPort: 3000
```

#### Frontend Service Manifest

```yaml
# frontend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
  namespace: default
spec:
  selector:
    app: frontend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
```

#### Backend Deployment Manifest

```yaml
# backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  namespace: default
  labels:
    app: backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: backend
        image: localhost:5000/my-backend:latest
        ports:
        - containerPort: 5000
```

#### Backend Service Manifest

```yaml
# backend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: backend-service
  namespace: default
spec:
  selector:
    app: backend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 5000
```

## Setting Up FluxCD to Deploy the Application

Now that we have our application and Kubernetes manifests ready, let's set up FluxCD to deploy them.

#### Step 1: Create a Kustomization File

Also in the `my-app` directory, create a `kustomization.yaml` file. This file tells FluxCD to apply the resources defined in the specified YAML files.

The `kustomization.yaml` file serves as a configuration file for [**Kustomize**](https://github.com/kubernetes-sigs/kustomize), a Kubernetes-native configuration management tool. It defines how Kubernetes manifests in that directory should be aggregated, customized, or patched before being applied to your cluster.

In a GitOps setup, such as with FluxCD, this file helps manage and deploy the application's resources by describing how the various Kubernetes YAML files in the `my-app` directory (like `frontend-deployment.yaml`, `frontend-service.yaml`, etc.) should be combined and customized.

```yaml
# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - frontend-deployment.yaml
  - frontend-service.yaml
  - backend-deployment.yaml
  - backend-service.yaml
```

2. **Link the Application Directory to FluxCD**  
    In the `clusters/my-cluster/flux-system/kustomization.yaml` file (already created from the bootstrap process), add a reference to your application's `kustomization.yaml` file. This tells FluxCD to apply the resources defined in `apps/my-app`. Edit the `clusters/my-cluster/flux-system/kustomization.yaml` file and add the `path` to your application directory.
    

```yaml
cd $HOME/my-flux-repo/clusters/my-cluster/flux-system
```

```yaml
# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - gotk-components.yaml
  - gotk-sync.yaml
  - ../../../apps/my-app/ # add this line
```

### **What Happens When FluxCD Reconciles It?**

1. **FluxCD Detects Changes**:
    
    * If you make a change in the Git repository (e.g., update `kustomization.yaml` or a resource file), FluxCD detects the change.
        
2. **FluxCD Applies the Kustomization**:
    
    * FluxCD reads the `kustomization.yaml` file in the `apps/my-app` directory.
        
    * It processes the listed resources, applies the customizations (e.g., patches, labels), and reconciles the desired state with the cluster.
        
3. **Resources Are Deployed**:
    
    * The aggregated and customized manifests are applied to the cluster, ensuring the application is deployed in the desired configuration.
        

### Commit and Push Changes

After updating the `kustomization.yaml`, commit and push the changes to your Git repository.

```bash
cd $HOME/my-flux-repo

git config --global user.email "you@example.com"

git config --global user.name "Your Name"

git add .; git commit -m "add my-app to cluster configuration"; git push origin main
```

**NOTE:** When prompted for your GitHub password, paste in your GitHub PAT (the one we generated at the beginning).

### Verify FluxCD Reconciliation

Wait for FluxCD to reconcile the changes or manually trigger it. Check the logs for updates:

* **Trigger Reconciliation**:
    
    ```bash
    flux reconcile kustomization flux-system -n flux-system
    ```
    
* **Check Logs**:
    
    ```bash
    kubectl logs -n flux-system deploy/kustomize-controller
    ```
    
* **Check Kustomization Status**:
    
    ```bash
    flux get kustomizations -n flux-system
    ```
    
* Check that the app was created:
    
    ```bash
    kubectl get all
    ```
    

## Summary

In this guide, we walked through the end-to-end process of setting up a GitOps workflow using [**FluxCD**](https://fluxcd.io/)**,** from bootstrapping a Flux-enabled cluster to deploying a sample microservices application. Along the way, you learned how FluxCD simplifies the continuous delivery process through reconciliation, Git-based change tracking, and modular infrastructure design.

### Key Takeaways

* **Git as the Source of Truth**: With FluxCD, your Git repository drives all changes in your Kubernetes cluster, ensuring consistency and auditability.
    
* **Modular Design**: You can organize your manifests using `Kustomize`, separating base configurations from overlays and manage multiple environments efficiently.
    
* **Declarative Deployment**: All resources, from Deployments to Services, are declaratively described, version-controlled, and automatically applied via FluxCD.
    
* **End-to-End Automation**: FluxCD monitors changes in Git and continuously reconciles your desired state with the actual state of your cluster, eliminating manual drift correction.
    
* **Hands-On Value**: By deploying a working microservices application using Node.js and Flask, you’ve applied GitOps principles in a real-world scenario.
    

While GitOps can initially seem overwhelming, especially with the many moving parts of Kubernetes, CI/CD pipelines, and declarative tooling, **FluxCD** makes it approachable, scalable, and production-ready. The hands-on walkthrough you completed today is just the beginning.

Whether you’re just getting started or expanding GitOps across multiple environments, FluxCD provides a robust and secure foundation to manage your workloads the GitOps way.

If this guide helped demystify FluxCD, share it with your team or community! And if you’re ready for more advanced topics, like multi-tenancy, pull-based secrets management, and progressive delivery, stay tuned for our next post!
