My GitOps Workflow Automated CD for Kubernetes with Flux2 and GitLab

Feb 28, 2024

I was tired of manually running kubectl apply and having my Kubernetes cluster state drift from what was defined in my Git repository. I wanted a true GitOps workflow, where Git is the single source of truth and every change is automatically and declaratively deployed. I found my solution with Flux2. It has completely transformed how I manage Kubernetes deployments. Here’s my guide to how I set it up with GitLab.

Why I Chose Flux2

I had used Flux v1 before, but I found its monolithic design and inefficient image scanning to be a bottleneck. Flux2 is a huge improvement. It has a modular, controller-based architecture that’s much more powerful. The Source Controller watches my Git repo, the Kustomize and Helm controllers apply my manifests, and the Image Automation controllers handle automatic updates. This separation of concerns is what makes Flux2 so robust.

My Setup Process

Step 1: Bootstrap Flux2

The first thing I do is install the flux CLI and set up a GitLab personal access token with api and write_repository scopes.

Then, the magic happens with the flux bootstrap gitlab command. This single command installs all the Flux components into my cluster, creates a deploy key in my GitLab repo, and commits the initial Flux configuration. I make sure to include the image automation components, as they are key to my automated image update workflow.

export GITLAB_TOKEN=<your-gitlab-personal-access-token>

flux bootstrap gitlab \
  --owner=my-group/my-project \
  --repository=cluster-config \
  --branch=main \
  --path=./flux-system \
  --components-extra=image-reflector-controller,image-automation-controller \
  --interval=30s

After this runs, I have a flux-system directory in my cluster-config repository, and the Flux controllers are running in my cluster.

Step 2: Structure the Git Repository

I organize my repository with a clear structure:

.
├── charts/         # My application Helm charts
├── flux-system/    # Flux's own configuration
└── releases/       # My application release definitions

Step 3: Configure Flux to Manage Releases

I tell Flux to manage all the application releases defined in my releases directory. I do this by creating a flux-system/releases.yaml file. This file defines two key things:

  1. A Kustomization that tells Flux to watch the ./releases path in my Git repo.
  2. An ImageUpdateAutomation that tells Flux how to commit changes back to the repo when it finds a new container image.
# flux-system/releases.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
kind: Kustomization
metadata:
  name: app-releases
  namespace: flux-system
spec:
  interval: 30s
  path: ./releases # Watch this directory
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: app-image-update
  namespace: flux-system
spec:
  interval: 30s
  sourceRef:
    kind: GitRepository
    name: flux-system
  git:
    commit:
      author:
        email: flux-bot@example.com
        name: flux-bot
      messageTemplate: "Automated image update for {{ .AutomationObject }}"
    push:
      branch: main
  update:
    path: ./releases
    strategy: Setters # Use the $imagepolicy comments

Step 4: Deploying an Application

To add a new application, I’ve created a template. This template defines three resources:

  1. ImageRepository: Tells Flux where to find my container image.
  2. ImagePolicy: Defines my versioning strategy. I use Semantic Versioning (semver) for production.
  3. HelmRelease: Defines the application deployment itself.

Here’s the crucial part of my HelmRelease template, where I use a special comment to tell Flux how to update the image tag:

# templates/release-template-semver.yaml
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: REPO_NAME
  namespace: production-PROJECT_NAME
spec:
  chart:
    spec:
      chart: ./charts/PROJECT_NAME/REPO_NAME
      sourceRef:
        kind: GitRepository
        name: flux-system
  interval: 30s
  values:
    image:
      repository: "gcr.io/my-project/PROJECT_NAME/REPO_NAME" # {"$imagepolicy": "flux-system:PROJECT_NAME-REPO_NAME:name"}
      tag: "1.0.0" # {"$imagepolicy": "flux-system:PROJECT_NAME-REPO_NAME:tag"}

When I want to deploy a new app, I just fill out this template, commit it to the releases/production directory, and push. Flux takes care of the rest.

My Workflow in Action

Once this is all set up, my workflow is a thing of beauty. It looks like this:

sequenceDiagram
    participant Dev as Developer
    participant Git as GitLab
    participant Flux as Flux Controllers
    participant K8s as Kubernetes
    participant Registry as Container Registry

    Dev->>Git: 1. Push code changes
    Note over Git: CI/CD builds image

    Git->>Registry: 2. Push image myapp:1.2.1

    Registry-->>Flux: 3. Image Reflector scans for new tags

    Flux->>Git: 4. Image Automation commits manifest update (tag: 1.2.1)

    Git-->>Flux: 5. Source Controller detects new commit

    Flux->>K8s: 6. Helm Controller applies updated release

    K8s->>K8s: 7. Rolling update to v1.2.1

A developer pushes code, our CI pipeline builds and pushes a new container image with a SemVer tag (e.g., 1.2.1). The Flux Image Reflector sees the new tag. The Image Automation controller then automatically commits a change to my HelmRelease manifest in GitLab, updating the image tag to 1.2.1. The Flux Source Controller sees this new commit, and the Helm Controller applies the upgrade to my cluster. It’s a fully automated, hands-off continuous deployment.

My Final Thoughts

My key takeaway from implementing Flux2 is that it truly delivers on the promise of GitOps. By making Git the source of truth, every change is auditable and reversible through a simple git revert. My advice is to use separate branches or repos for different environments, use SemVer for production image policies, and set up notifications so you know when a sync fails. This declarative, automated approach has made my deployments faster, safer, and completely transparent.

El Muhammad's Portfolio

© 2025 Aria

Instagram YouTube TikTok 𝕏 GitHub