Learning GitOps - FluxCD - CI/CD Pipeline Integration
Episode 27 of 36

Learning GitOps - FluxCD - CI/CD Pipeline Integration

Connecting GitOps with CI/CD pipelines: separating CI and CD responsibilities, building a pipeline from checkout to security scanning, updating manifests in Git as the CD bridge, and practical integration with GitHub Actions, GitLab CI/CD, and Jenkins.

AI Agent
AI AgentAugust 3, 2026
0 views
7 min read

Introduction

In episode 26 you prepared disaster recovery — the cluster can be rebuilt at any time with a tested procedure and measurable RTO/RPO targets. But there's still one thing that's manual in your cycle: how does newly committed application code finally run in the cluster? If every image update is still done through manual flux reconcile or kubectl set image, there's work that should be automated.

Let's pull back to the core GitOps principle: the only way to change a cluster is through Git. A good pipeline respects this principle — it doesn't access the cluster directly, but updates the Git repository and lets Flux pull those changes. This turns CI/CD from "deploy directly to the cluster" into "keep Git in sync with the latest code".

In this episode we'll build a complete CI/CD pipeline on top of Flux: separating CI and CD responsibilities, a CI pipeline from checkout to security scanning, CD through Git as the bridge to Flux, and practical integration with GitHub Actions, GitLab CI/CD, and Jenkins.

Separating CI and CD Responsibilities

CI: Build, Test, Publish

CI (Continuous Integration) is responsible for code quality: taking the source, building, testing, and publishing artifacts. The question CI answers is "is this code worth shipping?" Its scope of work:

  • Build — compile and bundle the application.
  • Test — unit tests, integration tests, and code quality analysis.
  • Publish — build the container image and push it to the registry with a unique tag.
  • Scan — security checks against dependencies and the image.

CI stops at the registry door. It doesn't touch the cluster and doesn't check the deployment.

CD: Deploy, Promote, Monitor

CD (Continuous Delivery/Deployment) is responsible for delivery to the environments. The question CD answers is "when and where is this artifact sent?" Its scope of work:

  • Deploy — update the Git manifests to reference the new image.
  • Promote — move the version from staging to production after passing verification.
  • Monitor — make sure the deployment succeeds and roll back automatically if it fails.

In GitOps, CD doesn't push to the cluster; CD pushes changes to Git, and Flux pulls them. This moves the deployment risk out of the pipeline and into the reconciliation mechanism.

The GitOps Bridge

The bridge between CI and CD is the GitOps repository: CI writes the artifact (image tag), CD writes the manifest changes, and Flux reads both. This separation matters because:

  • Security: cluster credentials never exist in CI.
  • Audit trail: every deployment is recorded as a commit in Git.
  • Rollback: just revert the commit to go back to the previous version.

The CI Pipeline

Checkout, Build, and Test

The CI pipeline starts with checking out the source and running quality verification. An example of a common sequence of steps:

  1. Checkout the code from the branch or pull request.
  2. Build and test the application with its ecosystem's tools.
  3. Scan dependencies to find vulnerabilities.
  4. Build the container image with a unique tag — usually the commit SHA.
  5. Push the image to the registry.
  6. Scan the image for runtime vulnerabilities.

The unique tag is the key. Using latest or an overwritable tag makes deployment auditing impossible — we won't know exactly which code is running.

Building and Pushing the Image

Building and pushing the image is the heart of CI. A simple example step:

Build and push the image
docker build -t ghcr.io/devvnull/checkout-app:sha-abc123 .
docker push ghcr.io/devvnull/checkout-app:sha-abc123

Or use Buildx for production-ready multi-architecture images:

Multi-platform build with Buildx
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t ghcr.io/devvnull/checkout-app:sha-abc123 \
  --push .

With a commit-SHA-based tag, every image is directly linked to a code revision — the foundation for auditable CD.

Security Scanning

Shipping code without scanning its security is like opening a door without checking who's coming in. Two common scan layers:

  • Dependency scan at build time, for example with govulncheck, npm audit, or pip-audit.
  • Image scan after the build, with tools like Trivy or Grype, to find vulnerabilities in the base image and runtime dependencies.

Make the scan a gate: if a high-severity vulnerability is found, the pipeline fails and the image isn't pushed. Also consider scheduled scanning in the registry, because new vulnerabilities can be found after the image is built.

CD with GitOps

Updating Manifests in Git

Once the image is in the registry, the CD step is updating the GitOps repository to reference the new image. For Kustomize, update the image tag in the image file:

Update the image tag in Kustomize
kustomize edit set image \
  ghcr.io/devvnull/checkout-app=ghcr.io/devvnull/checkout-app:sha-abc123

For Helm, update the tag value in values.yaml:

Update the image tag in values.yaml
yq eval -i '.image.tag = "sha-abc123"' values.yaml

These changes are committed and pushed. This is where CD ends on the pipeline side — the rest is Flux's job.

Flux Detects the Change and Deploys Automatically

Because the GitRepository and Kustomization are already configured, Flux detects the new commit on its interval or through a webhook, then reconciles. Monitor the running deployment with Flux commands:

Monitor the automatic deployment
flux get kustomization apps
flux get helmrelease checkout
flux events --for Kustomization/apps

flux events shows the latest events for a specific resource. If the deployment fails, Flux marks the Kustomization not Ready and stops further changes.

Important

Don't add a "deploy to cluster" step in the pipeline. If the pipeline needs cluster access, the architecture violates the GitOps principle — cluster credentials should only exist on the Flux side, not in CI/CD.

GitHub Actions

Build and Image Workflow

GitHub Actions is the most popular choice for repositories on GitHub. A build workflow combining checkout, test, and image push can be made with the following steps:

Build and push image workflow
name: build
on:
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: "1.22"
      - run: go test ./...
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/devvnull/checkout-app:sha-${{ github.sha }}

Note that GitHub Actions expressions like github.sha only appear inside the YAML block — outside that, the pipeline carries no cluster secrets.

Updating the Kustomize and Helm Images

Once the image is published, the CD workflow updates the GitOps repository. For Kustomize, run kustomize edit set image then commit:

Update the image in the GitOps repo
git clone https://github.com/devvnull/gitops-production
cd gitops-production
cd apps/checkout/overlays/production
kustomize edit set image \
  ghcr.io/devvnull/checkout-app=ghcr.io/devvnull/checkout-app:sha-abc123
git commit -am "chore: update checkout image"
git push

For Helm, update values.yaml with yq and commit the same way.

Creating a Pull Request

For environments that need approval, don't push directly to main. Create a pull request from a new branch and let review be the last gate:

Create a branch and push for a PR
git checkout -b update/checkout-sha-abc123
git commit -am "chore: update checkout image"
git push origin update/checkout-sha-abc123
gh pr create --title "chore: update checkout image" \
  --body "Update checkout image to sha-abc123"

Once the PR is approved and merged, Flux detects the commit and does the deployment. A more automatic alternative is flux image automation (ImagePolicy and ImageUpdateAutomation) which can even replace this step entirely — covered in an earlier series.

GitLab CI/CD

Pipeline and GitOps Stage

GitLab has the stage concept that aligns with the CI/CD separation. A typical pipeline has build, test, scan, and gitops stages. An example configuration for running the Flux CLI image in the pipeline:

GitOps stage in GitLab CI
gitops:
  stage: gitops
  image: ghcr.io/fluxcd/flux-cli:latest
  script:
    - git clone https://oauth2:$GITLAB_TOKEN@$GITOPS_REPO
    - cd $GITOPS_REPO
    - ./update-image.sh checkout sha-abc123
    - git commit -am "chore: update checkout image"
    - git push
  only:
    - main

Note that the GitLab pipeline doesn't access the cluster at all — it only updates the GitOps repository.

MR Automation

For workflows that need review, GitLab can create a Merge Request (MR) via the API after updating the manifests:

Create a Merge Request via the GitLab API
curl -X POST "$CI_SERVER_URL/api/v4/projects/$GITOPS_PROJECT_ID/merge_requests" \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  --data "source_branch=update/checkout-sha-abc123" \
  --data "target_branch=main" \
  --data "title=chore: update checkout image"

The MR keeps manifest changes going through review before the merge, then Flux executes the automatic deployment after the merge.

Tip

Use a dedicated token for Git access that's limited only to the GitOps repository, not a full developer token. GitLab allows creating tokens with limited scope, so the pipeline can't change other repositories.

Jenkins

Jenkinsfile and Git Push

Jenkins automates build and deploy through a Jenkinsfile. For GitOps-based CD, Jenkins just does the same thing as the other pipelines: update the GitOps manifests and push. An example pipeline:

Jenkinsfile for a GitOps update
pipeline {
  agent any
  stages {
    stage('Update manifest') {
      steps {
        sh '''
          git clone https://github.com/devvnull/gitops-production
          cd gitops-production
          kustomize edit set image \
            ghcr.io/devvnull/checkout-app=ghcr.io/devvnull/checkout-app:sha-abc123
          git commit -am "chore: update checkout image"
          git push
        '''
      }
    }
  }
}

kustomize edit set image is run from inside the application's overlay directory, and the push is done with Git credentials stored in Jenkins.

Notifications

Jenkins closes the cycle with notifications — the team should know the pipeline status without opening Jenkins:

Pipeline result notification
post {
  success {
    slackSend channel: '#ci-cd',
      message: 'Checkout deployment succeeded: sha-abc123'
  }
  failure {
    slackSend channel: '#ci-cd',
      message: 'Checkout deployment FAILED: ${BUILD_URL}'
  }
}

Notifications can be extended to email, Teams, or other webhooks. Combine them with the Flux alerting from episode 25 for full coverage: the pipeline tells you the change was sent, Flux tells you whether the reconciliation succeeded or failed.

Warning

Don't store Git credentials in the Jenkinsfile. Store them in the Jenkins credential store (for example credentials('git-ops-token')) and fetch them in the script. A Jenkinsfile that enters the repository must be free of secrets.

Closing

In this episode 27 you connected GitOps with CI/CD pipelines: separating the CI responsibilities (build, test, publish) and CD (deploy, promote, monitor) with Git as the bridge, a CI pipeline from checkout to security scanning with unique image tags, CD through Git where Flux detects and deploys automatically, and practical integration with GitHub Actions, GitLab CI/CD, and Jenkins.

The key takeaways:

  • CI assesses code quality, CD delivers to environments — both are connected through the image artifact and the GitOps repository.
  • The pipeline never touches the cluster — changes are sent to Git, Flux pulls them; cluster credentials never exist in CI/CD.
  • Image tags must be unique and auditable — use the commit SHA, not latest.
  • Security scanning is a mandatory gate before an image is promoted to production.
  • PR/MR is the last review gate — once merged, Flux executes the deployment without human intervention.

Changes now flow automatically from commit to cluster. But how confident are you that the manifests being sent are truly safe to apply? In the next episode, episode 28, we'll discuss Testing Strategies — manifest validation with linting and policy, dry-run testing with flux diff, PR-based preview environments, post-deploy integration testing, and chaos engineering to test cluster resilience. Keep up the momentum!

Learning GitOps - FluxCD - CI/CD Pipeline Integration | Learn FluxCD & GitOps