Beyond Syncing: Building a 2026 GitOps Engine for Kubernetes
Stop treating GitOps as just a fancy 'kubectl apply'. Learn how to build a fully automated deployment engine using Flux, Crossplane, and Kyverno to eliminate manual intervention in production.

The 2:00 AM PagerDuty Call
You’ve been there. It’s 2:00 AM, and production is down because a developer manually patched a deployment with kubectl edit. The change wasn't documented, it wasn't in Git, and the next automated sync wiped it out, triggering a crash loop. Or worse, the manual patch was the fix, but the CI/CD pipeline overwrote it with a broken build.
In 2026, if you are still manually triggering pipelines or, god forbid, running kubectl commands against production, you aren't doing GitOps; you're doing 'Git-adjacent' manual labor. True GitOps automation means the cluster state is a pure reflection of your version-controlled intent, but getting there requires more than just installing an operator. It requires a hardened workflow that handles image updates, policy enforcement, and infrastructure drift without human hands.
Why Your Current GitOps Setup Is Scaling Poorly
Most teams start with a basic 'Pull' model: Flux or ArgoCD watches a Git repo and applies YAML. This works for three microservices. It fails at thirty. The bottleneck is usually the 'Image Update' cycle. If your CI pipeline has to commit a new tag back to Git for every build, you end up with a polluted Git history and constant merge conflicts in your manifest repo.
Furthermore, the 'Git' in GitOps is evolving. We are moving away from raw Git repositories as the primary source of truth for deployments, shifting instead toward OCI (Open Container Initiative) artifacts. This allows us to treat our Kubernetes manifests exactly like our container images—versioned, signed, and stored in a registry.
Section 1: The OCI-First Workflow
In my current production environment, we've moved away from Flux watching Git branches. Instead, we use Flux to watch an OCI registry. This decouples the source code from the deployment artifact and significantly speeds up reconciliation.
When a CI build finishes, it doesn't just build a Docker image. It packages the Kustomize or Helm charts into an OCI artifact and pushes it to the registry. Flux then pulls this artifact. This eliminates the 'write-back' problem where CI needs write access to your Git manifests.
Section 2: Automating Image Promotions
To automate the promotion from staging to production, we use Flux’s ImagePolicy and ImageUpdateAutomation. This allows the cluster to 'self-update' when a new image version that matches a SemVer range is detected in the registry.
Here is a production-grade configuration for an automated image policy that targets a 1.2.x release line:
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: payment-service
namespace: flux-system
spec:
image: ghcr.io/kaval-labs/payment-api
interval: 5m
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: payment-service-policy
namespace: flux-system
spec:
imageRepositoryRef:
name: payment-service
policy:
semver:
range: '^1.2.x'
This configuration ensures that any image tagged 1.2.1, 1.2.2, etc., is automatically picked up. But how do we get this back into Git to maintain the source of truth? We use the ImageUpdateAutomation controller to commit the change back to a dedicated 'delivery' branch.
Section 3: Policy as Code is Not Optional
GitOps without policy enforcement is a recipe for a cluster-wide security breach. If anyone can merge a PR that sets privileged: true or mounts a host path, your GitOps controller will faithfully execute that attack for you.
We use Kyverno to validate every manifest before it hits the cluster. In 2026, we've moved to 'Shift-Left' validation where the same Kyverno policies used in the cluster are run in the GitHub Action using the kyverno cli.
Section 4: The CI-to-Registry Pipeline
Your GitHub Action should not just be a bash script. It needs to handle signing and attestation. We use Cosign to sign our OCI artifacts. If the signature doesn't match, Flux is configured to reject the sync. This prevents 'shadow' deployments from unauthorized registries.
Here is the workflow we use to package and push manifests as OCI artifacts:
name: Publish Manifests
on:
push:
branches: [ main ]
paths: [ 'k8s/**' ]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push Manifest OCI Artifact
run: |
flux push artifact oci://ghcr.io/kaval-labs/manifests/payment-api:${{ github.sha }} \
--path="./k8s" \
--source="${{ github.event.repository.html_url }}" \
--revision="${{ github.ref_name }}/${{ github.sha }}"
- name: Sign Artifact
run: |
cosign sign --yes ghcr.io/kaval-labs/manifests/payment-api:${{ github.sha }}
The Gotchas: What Usually Breaks
- The Reconciliation Loop of Death: If you have a tool inside the cluster (like an HPA or a service mesh injector) modifying a field that Flux is also trying to manage, the two will fight forever. Flux will apply a change, the injector will change it back, and Flux will apply it again. Always use
ignorePathsin your Kustomization to ignore fields managed by in-cluster controllers. - Secret Management: Never, ever store raw secrets in Git. Even encrypted secrets (like SOPS) can be a headache for rotation. In 2026, the industry standard is the External Secrets Operator (ESO). Your GitOps repo should only contain a
SecretStorereference to AWS Secrets Manager or HashiCorp Vault. Let ESO do the heavy lifting of syncing the actual values. - Health Check Delays: By default, Flux might report a 'Successful' sync as soon as the API server accepts the YAML. This is useless. You must define
wait: trueandtimeoutin yourKustomizationresources. This forces Flux to wait for the pods to be ready and the probes to pass before marking the sync as complete. Without this, your automated promotions will fly blind.
Takeaway
GitOps is not a product you buy; it is a discipline you enforce. If you want to stop firefighting and start building, move your manifests to OCI artifacts today. Stop letting your CI workers have cluster-admin access. Push to a registry, sign your artifacts, and let Flux pull the state.
Start by migrating one non-critical service to an OCI-based Flux sync. Once you see how much faster and cleaner your Git history becomes without those 'chore: update image tag' commits, you'll never go back.