🚀 Executive Summary
TL;DR: Many organizations mistakenly integrate source code validation tools like linters and SAST into their GitOps platforms, leading to deployment delays and operational issues. The recommended solution is to strictly separate concerns: CI pipelines handle all source code validation, testing, and image building, while GitOps platforms are reserved exclusively for state reconciliation and deployment synchronization.
🎯 Key Takeaways
- GitOps platforms (e.g., ArgoCD, Flux) are state synchronizers for deployment, not build servers or task runners for source code validation.
- Continuous Integration (CI) pipelines (e.g., GitHub Actions, GitLab CI) are the correct place for linting, SAST, unit tests, and image building, ideally blocking merges on PRs.
- For high-compliance environments, cryptographically sign container images in CI after validation using tools like Sigstore Cosign, and enforce signature checks via Kubernetes admission controllers (e.g., Kyverno, OPA Gatekeeper).
SEO Summary: Confused about where code validation belongs in a modern DevOps stack? I break down why linting, SAST, and testing belong strictly in your CI pipeline, while GitOps platforms should be reserved purely for state reconciliation.
The Great Divide: Where Source Code Validation Actually Belongs (CI/CD vs. GitOps)
I still get stress migraines thinking about the “Great Outage of ’22.” I was brought in to consult for a mid-sized fintech firm, and their lead dev had somehow convinced the team that because they were “doing GitOps,” everything—and I mean everything—needed to happen in ArgoCD. They had rigged up a monstrous web of custom sync hooks to run SonarQube, massive integration test suites, and security scans right as the cluster attempted to deploy a new release to prod-k8s-core-01. The result? A simple emergency hotfix took 45 minutes to deploy, ArgoCD OOM-killed itself constantly because it was holding state for hundreds of running tests, and we were flying blind during a Sev-1 incident. I literally had to rip out the sync hooks with my bare hands via kubectl just to get the pods to roll over and restore service.
The “Why”: Mixing Up the “I” and the “D” in CI/CD
If you are reading this, you probably stumbled onto the same Reddit thread I did this morning, where a junior engineer was asking if they should run Trivy and ESLint inside their GitOps platform. Let me save you a year of operational pain: GitOps is a deployment methodology, not a build server.
The root cause of this confusion comes from the buzzword soup vendors feed us. People think “GitOps” replaces “CI/CD.” It does not. Continuous Integration (CI) is where you validate, compile, test, and package source code. Your CI tool (GitHub Actions, GitLab CI, Jenkins) is fundamentally a task runner designed for dynamic workloads. GitOps (ArgoCD, Flux) is a state synchronizer. When you force a state synchronizer to wait for a 15-minute integration test to pass before it pulls a manifest, you completely break the reconciliation loop. You are effectively trying to hammer a nail with a microscope.
Pro Tip: If your deployment tool is compiling code or running linting suites, you have accidentally built a CI system with a terrible UI. Stop it immediately. Let task runners run tasks, and let state managers manage state.
How We Actually Fix This
Alright, so how do we properly split the validation logic from the deployment logic? Here is the playbook we use in the trenches at TechResolve.
1. The Quick Fix: Shift-Left with Pre-Commit Hooks
Before you even touch a CI pipeline, stop the garbage from entering the repo in the first place. This is the fastest, hackiest, but most effective way to save CI minutes and keep developers from pushing simple syntax errors that clog up the system. We enforce pre-commit hooks locally on the developers’ machines.
Drop a configuration like this into your repository to enforce basic YAML validation, trailing whitespace checks, and local linting before a commit is even allowed to execute:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: check-yaml
- id: detect-private-key
- repo: https://github.com/bridgecrewio/checkov
rev: 2.3.170
hooks:
- id: checkov
args: [-d, .]
Yes, developers can bypass this with a simple --no-verify flag, which makes it an honor-system hack. But for well-meaning teams, it immediately stops the bleeding of bad commits triggering endless, easily preventable pipeline failures.
2. The Permanent Fix: The Strict CI Validation Pipeline
This is the industry standard and how you should be operating. Your Git repository should be hooked up to a dedicated CI task runner. Source code validation happens on the Pull Request (PR) against your main branch. If validation fails, the merge is hard-blocked. Period.
In this model, your pipeline looks exactly like this:
- Step 1: A developer opens a PR for a new feature.
- Step 2: CI kicks off and runs ESLint, Unit Tests, and SAST (e.g., SonarQube).
- Step 3: If the pipeline is green, the code is allowed to merge.
- Step 4: Post-merge CI builds the Docker image, tags it, pushes it to the registry, and updates the manifest repository with the new image tag.
- Step 5: GitOps detects the manifest change and quietly syncs the new deployment to
prod-app-nodes-01.
| Tool Category | What belongs here? | Industry Examples |
| CI Pipeline | Linting, SAST, Unit Tests, Image Building | GitHub Actions, GitLab CI, CircleCI |
| GitOps Platform | State Reconciliation, Health Checks, Drift Detection | ArgoCD, FluxCD |
3. The ‘Nuclear’ Option: Artifact Signing and Admission Control
When you are dealing with strict compliance environments (like we do with our healthcare and federal clients), you cannot just trust that the CI pipeline did its job. You need cryptographically verifiable proof before your GitOps controller is allowed to spin up a pod.
In this scenario, we use Sigstore’s Cosign to sign the container images in the CI pipeline only after they pass strict vulnerability scans and unit tests. Then, we implement a policy engine like Kyverno or OPA Gatekeeper inside the Kubernetes cluster itself.
# CI Step: Cryptographically sign the image ONLY after the Trivy scan passes
cosign sign --key k8s://techresolve-sec/cosign-keys registry.internal/billing-api:v2.4.1
When the GitOps tool tries to deploy the resource, the Kubernetes admission controller intercepts the request and rejects it unless the image carries the valid cryptographic signature proving it passed CI validation. It is heavy, it requires strict key management, and developers will absolutely complain about pipeline complexity. But if you need an ironclad guarantee that unvalidated code never runs on your infrastructure, this is how you lock it down.
Final Thoughts
Look, I get the temptation to put everything under one pane of glass. When you first spin up ArgoCD or Flux, it looks like absolute magic, and you want to use it for everything. But separation of concerns exists for a reason in software engineering. Let your CI servers do the heavy, messy, iterative work of validating source code, and let your GitOps controllers do the quiet, elegant work of keeping your clusters in sync.
Now, if you will excuse me, I need to go figure out why redis-cache-03 keeps throwing random memory eviction errors. Keep pushing code—just make sure you validate it in CI first!
🤖 Frequently Asked Questions
âť“ Where should source code validation tools like SonarQube or ESLint be run in a DevOps pipeline?
Source code validation tools such as SonarQube, ESLint, Trivy, and unit tests should be executed within your Continuous Integration (CI) pipeline (e.g., GitHub Actions, GitLab CI) as part of the build and test phase, not within your GitOps platform.
âť“ How does this approach compare to running validation directly within a GitOps platform?
Running validation in CI maintains separation of concerns, allowing CI tools to handle dynamic workloads like testing and compilation, while GitOps platforms (ArgoCD, Flux) focus solely on state reconciliation. Mixing them breaks the reconciliation loop, causes performance issues, and complicates deployments.
âť“ What is a common pitfall when implementing source code validation in a GitOps environment?
A common pitfall is attempting to run extensive validation tasks like SAST or integration tests as custom sync hooks within GitOps platforms like ArgoCD. This overloads the GitOps tool, causes OOM errors, slows deployments, and fundamentally misuses a state synchronizer as a build server.
Leave a Reply