Migrating to FluxCD Operator with UI

Migrating to FluxCD Operator with UI
Page content

FluxCD Operator brings a simplified management experience and a native web UI for monitoring your GitOps workflows. This guide walks you through migrating an existing FluxCD installation to the Operator pattern with UI enabled, while maintaining GitLab as your Git source.

What is FluxCD Operator?

FluxCD Operator is a new way to manage Flux instances on Kubernetes. Instead of installing Flux components directly, you deploy a FluxInstance custom resource that manages the entire Flux stack.

┌─────────────────────────────────────────────────────────┐
│              FluxCD Operator                            │
│  ┌─────────────────────────────────────────────────┐    │
│  │           FluxInstance CR                       │    │
│  │  - Manages Flux components                      │    │
│  │  - Handles upgrades automatically               │    │
│  │  - Provides web UI (Flux Web UI)                │    │
│  └─────────────────────────────────────────────────┘    │
│         │              │              │                 │
│         ▼              ▼              ▼                 │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐               │
│  │  Source  │  │ Kustomize│  │  Helm    │               │
│  │Controller│  │Controller│  │Controller│               │
│  └──────────┘  └──────────┘  └──────────┘               │
└─────────────────────────────────────────────────────────┘
┌─────────────────┐
│    GitLab       │
(Git Source)└─────────────────┘

Benefits of Migrating

Feature Traditional Flux Flux Operator
Installation Flux CLI (flux install) Kubernetes manifests / Helm
Upgrades Manual (flux upgrade) Automatic via CR updates
Configuration CLI flags / bootstrap Declarative (FluxInstance CR)
Web UI Third-party (Weave GitOps) Native Flux Web UI
Multi-tenancy Complex Built-in support
Observability Manual setup Integrated dashboards

Prerequisites

Component Version Notes
Kubernetes 1.25+ Tested on 1.28, 1.29
Existing Flux v2.0+ v1.x requires additional migration steps
GitLab 15.0+ Self-managed or GitLab.com
kubectl Latest Configured with cluster access
flux CLI v2.2+ For verification and troubleshooting

Verify Current Flux Installation

# Check Flux components
flux check --pre

# List existing Flux resources
kubectl get GitRepository -A
kubectl get Kustomization -A
kubectl get HelmRelease -A
kubectl get HelmRepository -A

# Check Flux version
flux --version

Step 1: Backup Existing Flux Configuration

Before making any changes, export all existing Flux resources:

# Create backup directory
mkdir -p ~/flux-backup/$(date +%Y%m%d-%H%M%S)
BACKUP_DIR=~/flux-backup/$(date +%Y%m%d-%H%M%S)

# Export all Flux resources
kubectl get GitRepository -A -o yaml > $BACKUP_DIR/gitrepositories.yaml
kubectl get Kustomization -A -o yaml > $BACKUP_DIR/kustomizations.yaml
kubectl get HelmRelease -A -o yaml > $BACKUP_DIR/helmreleases.yaml
kubectl get HelmRepository -A -o yaml > $BACKUP_DIR/helmrepositories.yaml
kubectl get ImageRepository -A -o yaml > $BACKUP_DIR/imagerepositories.yaml
kubectl get ImagePolicy -A -o yaml > $BACKUP_DIR/imagepolicies.yaml
kubectl get ImageUpdateAutomation -A -o yaml > $BACKUP_DIR/imageupdateautomations.yaml

# Export Flux secrets (GitLab credentials)
kubectl get secret -n flux-system -o yaml > $BACKUP_DIR/flux-secrets.yaml

# Export existing flux-system namespace
kubectl get namespace flux-system -o yaml > $BACKUP_DIR/namespace.yaml

echo "Backup completed: $BACKUP_DIR"

Export GitLab Webhook Configuration

# Get existing webhook secrets
kubectl get secret -n flux-system webhook-secret -o yaml > $BACKUP_DIR/webhook-secret.yaml

# Document existing webhook URLs
kubectl get GitRepository -A -o jsonpath='{range .items[*]}{.metadata.name}: {.spec.url}{"\n"}{end}' > $BACKUP_DIR/git-urls.txt

Step 2: Upgrade to latest fux and object versions

git clone https://gitlab.mydomain.intra/k8s/fluxcd-system.git
cd fluxcd-system

# upgrade to 2.6
flux migrate -v 2.6 -f . --dry-run
flux migrate -v 2.6 -f .

git commit -am "Migrate to Flux v2.6 APIs"
git push

flux reconcile ks flux-system --with-source

# migrate CRDs
flux migrate

flux check

# upgrade flux components
flux install --export > clusters/k8s-system/flux-system/gotk-components.yaml

git commit -am "Upgrade to Flux v2.8"
git push

# upgrade to 2.8
flux migrate -v 2.8 -f . --dry-run
flux migrate -v 2.8 -f .

git commit -am "Migrate to Flux v2.8 APIs"
git push

flux reconcile ks flux-system --with-source

flux check

Step 3: Install FluxCD Operator

Add Flux Operator Helm Repository

helm install flux-operator oci://ghcr.io/controlplaneio-fluxcd/charts/flux-operator \
  --namespace flux-system

Verify Operator Installation

# Check operator pods
kubectl get pods -n flux-system

# Expected output:
# NAME                              READY   STATUS    RESTARTS   AGE
# flux-operator-xxxxxxxxxx-xxxxx    1/1     Running   0          2m

# Check operator deployment
kubectl get deployment -n flux-system

Step 4: Create FluxInstance with Operator

Create flux-instance.yaml:

apiVersion: fluxcd.controlplane.io/v1
kind: FluxInstance
metadata:
  name: flux
  namespace: flux-system
  annotations:
    fluxcd.controlplane.io/reconcileEvery: "1h"
    fluxcd.controlplane.io/reconcileArtifactEvery: "10m"
    fluxcd.controlplane.io/reconcileTimeout: "5m"
spec:
  distribution:
    version: "2.x"
    registry: "ghcr.io/fluxcd"
    artifact: "oci://ghcr.io/controlplaneio-fluxcd/flux-operator-manifests"
  components:
    - source-controller
    - kustomize-controller
    - helm-controller
    - notification-controller
    - image-reflector-controller
    - image-automation-controller
  cluster:
    type: kubernetes
    size: medium
    multitenant: false
    networkPolicy: true
    domain: "cluster.local"
  sync:
    kind: GitRepository
    name: flux-system
    url: "https://gitlab.mydomain.intra/k8s/fluxcd-system.git"
    ref: "refs/heads/main"
    path: "clusters/k8s-system"
    pullSecret: "flux-system"
kubectl apply -f flux-instance.yaml

kubectl get FluxInstance flux                
NAME   AGE   READY   STATUS                          REVISION
flux   65m   True    Reconciliation finished in 8s   v2.8.5@sha256:20fa97fb1f001dd254d0e7ef9e1be6d2e1f10f972e731c087a8121aef80c0cc2

Once the resource is reconciled, the operator will take over the management of the Flux components, the Flux GitRepository and Kustomization.

Running the trace command should result in a “Not managed by Flux” message:

$ flux trace kustomization flux-system
failed to trace Kustomization/flux-system in namespace flux-system: object not managed by Flux

Step 5: Cleanup the repository

To finalize the migration, remove the Flux manifests from the Git repository:

cd flux-system

rm -f clusters/k8s-system/flux-system/gotk-components.yaml
mv flux-instance.yaml clusters/k8s-system/flux-system/flux-instance.yaml

git commit -ma "add: FluxInstance resource"
git push

flux reconcile ks flux-system --with-source

Commit and push the changes to the repository.

Stap 6: Monitoring

Create monitoring.yaml:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: flux-operator
  namespace: flux-system
  labels:
    release: kube-prometheus-stack
spec:
  namespaceSelector:
    matchNames:
      - flux-system
  selector:
    matchLabels:
      app.kubernetes.io/name: flux-operator
  endpoints:
    - targetPort: 8080
      path: /metrics
      interval: 30s
cd flux-system

nano clusters/k8s-system/flux-system/monitoring.yaml

git commit -ma "add: FluxInstance monitoring"
git push

flux reconcile ks flux-system --with-source

It is recommended to change the reporting interval to 30s when using the Prometheus metrics exported by the operator:

helm upgrade flux-operator oci://ghcr.io/controlplaneio-fluxcd/charts/flux-operator \
  --namespace flux-system \
  --set reporting.interval=30s

Step 7: Access Flux Web UI

Enable Web UI

Create values.yaml

reporting:
  interval: 30s
web:
  enabled: true
  config:
    baseURL: https://flux.example.com
    authentication:
      type: OAuth2
      oauth2:
        provider: OIDC
        clientID: <DEX-CLIENT-ID>
        clientSecret: <DEX-CLIENT-SECRET>
        issuerURL: https://dex.example.com
  ingress:
    enabled: true
    className: nginx
    annotations:
      cert-manager.io/cluster-issuer: letsencrypt-prod
    hosts:
      - host: flux.example.com
        paths:
          - path: /
            pathType: Prefix
    tls:
      - hosts:
          - flux.example.com
        secretName: flux-web-tls
helm upgrade flux-operator oci://ghcr.io/controlplaneio-fluxcd/charts/flux-operator \
  --namespace flux-system \
  -f values.yaml

Port Forward (Testing)

kubectl port-forward svc/flux-operator -n flux-system 8080:9080

Then access: http://localhost:8080