Skip to main content

CI/CD Integration Patterns

Purpose: For platform engineers and app developers, documents how to integrate CI/CD pipelines with the openCenter GitOps model, covering push-based CI and pull-based CD patterns.

Prerequisites

  • openCenter cluster with FluxCD running
  • Container registry accessible from the cluster (Harbor, GHCR, or other)
  • Git repository for application source code
  • Git repository for GitOps manifests (customer repo)

Architecture: Push CI + Pull CD

openCenter uses a split model:

PhaseModelToolResponsibility
CI (build)Push-basedGitHub Actions, GitLab CIBuild image, run tests, push to registry
CD (deploy)Pull-basedFluxCDDetect new image, update manifests, reconcile
Developer → Push code → CI builds image → Push image to registry

FluxCD ← Reconcile ← Git manifest updated ← Image automation updates tag

Pattern 1: Manual Image Tag Update

The simplest pattern. CI builds and pushes the image; a human updates the tag in Git.

GitHub Actions Example

# .github/workflows/build.yaml
name: Build and Push
on:
push:
branches: [main]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Log in to registry
run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

- name: Build and push
run: |
IMAGE=ghcr.io/${{ github.repository }}:${{ github.sha }}
docker build -t $IMAGE .
docker push $IMAGE
echo "IMAGE=$IMAGE" >> $GITHUB_OUTPUT

Then update the GitOps manifest manually:

# In the customer GitOps repo
# applications/overlays/<cluster>/managed-services/my-app/deployment.yaml
spec:
template:
spec:
containers:
- name: my-app
image: ghcr.io/myorg/my-app:abc123def # ← update this

Pattern 2: CI Updates Git Manifest Automatically

CI builds the image and opens a PR to the GitOps repository with the new tag.

GitHub Actions Example

# .github/workflows/build-and-update.yaml
name: Build and Update GitOps
on:
push:
branches: [main]

jobs:
build:
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4

- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: type=sha,prefix=

- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: ${{ steps.meta.outputs.tags }}

update-gitops:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout GitOps repo
uses: actions/checkout@v4
with:
repository: myorg/cluster-gitops
token: ${{ secrets.GITOPS_PAT }}

- name: Update image tag
run: |
cd applications/overlays/dev-cluster/managed-services/my-app
sed -i "s|image: ghcr.io/myorg/my-app:.*|image: ghcr.io/myorg/my-app:${{ needs.build.outputs.image_tag }}|" deployment.yaml

- name: Commit and push
run: |
git config user.name "ci-bot"
git config user.email "ci@myorg.com"
git checkout -b update/my-app-${{ needs.build.outputs.image_tag }}
git add .
git commit -m "chore: update my-app to ${{ needs.build.outputs.image_tag }}"
git push -u origin update/my-app-${{ needs.build.outputs.image_tag }}
gh pr create --title "Deploy my-app ${{ needs.build.outputs.image_tag }}" --body "Auto-generated by CI"

GitLab CI Example

# .gitlab-ci.yml
stages:
- build
- update-gitops

build:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
rules:
- if: $CI_COMMIT_BRANCH == "main"

update-gitops:
stage: update-gitops
image: alpine/git
needs: [build]
script:
- git clone https://oauth2:${GITOPS_TOKEN}@gitlab.com/myorg/cluster-gitops.git
- cd cluster-gitops
- |
sed -i "s|image: registry.gitlab.com/myorg/my-app:.*|image: registry.gitlab.com/myorg/my-app:${CI_COMMIT_SHORT_SHA}|" \
applications/overlays/dev-cluster/managed-services/my-app/deployment.yaml
- git config user.name "gitlab-ci"
- git config user.email "ci@myorg.com"
- git add .
- git commit -m "chore: update my-app to ${CI_COMMIT_SHORT_SHA}"
- git push origin main
rules:
- if: $CI_COMMIT_BRANCH == "main"

Pattern 3: FluxCD Image Update Automation

FluxCD can automatically detect new images in a registry and update manifests in Git. No CI-to-GitOps integration needed.

Setup

  1. Install the image automation controllers (included in openCenter FluxCD deployment):
kubectl get pods -n flux-system | grep image
# image-reflector-controller
# image-automation-controller
  1. Create an ImageRepository to scan:
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: my-app
namespace: flux-system
spec:
image: ghcr.io/myorg/my-app
interval: 5m
  1. Create an ImagePolicy to select tags:
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: my-app
namespace: flux-system
spec:
imageRepositoryRef:
name: my-app
policy:
semver:
range: ">=1.0.0"
  1. Create an ImageUpdateAutomation to commit changes:
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
name: my-app
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: customer-repo
git:
checkout:
ref:
branch: main
commit:
author:
name: fluxcd
email: fluxcd@myorg.com
messageTemplate: "chore: update {{.AutomationObject.Name}} images"
push:
branch: main
update:
path: ./applications/overlays/dev-cluster
strategy: Setters
  1. Mark the image field in your Deployment with a setter comment:
spec:
containers:
- name: my-app
image: ghcr.io/myorg/my-app:1.0.0 # {"$imagepolicy": "flux-system:my-app"}

FluxCD detects new tags matching the policy, updates the manifest in Git, and reconciles.

Verification

# Check image scan results
flux get image repository my-app

# Check selected image
flux get image policy my-app

# Check automation status
flux get image update my-app

# View recent image update commits
git log --oneline --author=fluxcd -5

Troubleshooting

SymptomCauseFix
ImageRepository shows scan failedRegistry auth missingCreate docker-registry secret and reference in ImageRepository
Image not updatingPolicy doesn't match tagsCheck tag format matches policy (semver vs sha)
Commit not pushedGit auth for push missingVerify GitRepository has write-capable deploy key
Old image still runningReconciliation interval not elapsedflux reconcile kustomization <name> --with-source