“How CI/CD and Flux Split Responsibilities in a DocOps Project” examines a delivery model: a pipeline builds and publishes a container image, updates its version in a separate GitOps repository, and Flux brings Kubernetes to the recorded state.
Now consider a condition under which checks do not block deployment. The GitOps repository has CI, but automation pushes its change directly to the branch watched by Flux. Validation exists, but it does not precede state publication.
Examining this model does not require an incident. The order of events is enough. The subject here is the mechanism, not the access permissions or change history of a particular team.
One commit starts two independent processes
If a pipeline changes an image tag in the GitOps repository and pushes directly to the main branch, a single push can start two processes:
commit in the main branch of the GitOps repository
├── starts the GitOps CI pipeline
└── becomes visible to Flux
GitLab begins running its checks. Independently, Flux retrieves the new Git revision and starts reconciliation.
In the Flux model, a GitRepository resource retrieves the configured branch and produces an artifact from its current revision. A Kustomization reacts to that artifact, renders the manifests, and applies them to Kubernetes.
Without an additional connection, the GitLab pipeline status is not part of this sequence. There is no causal dependency between CI and Flux: neither waits for the result of the other.
A gate is a dependency, not a delay
At first, the risk can appear small. CI usually runs quickly, while Flux retrieves changes on a configured interval. The pipeline might finish first during almost every routine deployment.
A deployment gate cannot depend on the word “usually.”
Increasing the Flux interval does not solve the problem either. A pipeline may occasionally take longer, reconciliation can be triggered manually, and a webhook can deliver a change to Flux almost immediately.
Without an explicit dependency between validation and commit publication, the order remains accidental.
A real admission boundary must enforce this sequence:
configuration change
→ required validation
→ successful result
→ publication to the watched branch
→ Flux reconciliation
With a direct push, the sequence is different:
publication to the watched branch
→ validation and reconciliation run in parallel
CI validates state that has already been published to Flux.
A failed pipeline does not roll anything back
If an automated commit contains an error, the GitOps CI pipeline can detect it and fail.
A failed status alone does not remove the commit from the main branch. It does not restore the previous image tag, cancel reconciliation that has already started, or tell Flux that the revision must not be applied.
By that time, Flux may already have retrieved the commit and started processing the manifests. If Flux itself or the Kubernetes API rejects the change, that is another protection layer. GitOps CI still has not blocked anything.
The checks remain useful. They can detect an error and notify an engineer. In this design, however, CI acts as a detector after publication rather than as a pre-deployment admission step.
Moving validation before Flux
Instead of pushing directly to the watched branch, automation can prepare a change separately:
- Create a temporary branch in the GitOps repository
- Update the tag of an already built image
- Push the temporary branch to GitLab
- Open a Merge Request into the main branch
- Permit merging only after successful required checks
Branch naming and retry behavior need their own contract. Different releases must not accidentally overwrite each other, and a retry must not create uncontrolled duplicate Merge Requests. A particular branch-name format is not part of the deployment-gate principle.
The resulting sequence is:
prepare a new version
→ temporary branch in the GitOps repository
→ image-tag update
→ Merge Request
→ required GitOps MR pipeline
→ successful checks and other merge conditions
→ merge into the main branch
→ Flux retrieves the new commit
The change becomes visible to Flux only after the merge. This acts as a gate only when GitLab actually requires successful checks before merging.
Auto-merge does not replace required checks
Automatic merging can remove the need to wait for CI manually. Enabling auto-merge alone does not prove that the intended checks are mandatory: admission conditions must be configured and verified separately.
The order then becomes:
GitOps CI succeeds
→ the remaining merge conditions are met
→ GitLab performs the merge
→ the commit appears in the main branch
→ Flux retrieves the new revision
Creating a Merge Request, enabling auto-merge, and completing the merge are different outcomes. A pipeline that only prepares a change must not present that as a completed merge or deployment.
The important relationship is:
successful required GitOps CI
→ merge
→ publication of state for Flux
Failure should keep the change outside the main branch
When required CI fails, the change should remain in the temporary branch and the open Merge Request. Flux continues reading the previous state from the main branch.
A conflict should also stop automatic merging. Continuing requires a separate decision, not hidden conflict resolution or history rewriting merely to obtain a green result.
This is an important property of the process. Automation failures should not be hidden by automatic Git history repair.
A Merge Request is not only for manual approval
A Merge Request in this chain does not necessarily mean that a person must approve every image update manually. A routine change can use auto-merge after the configured conditions have been satisfied.
The Merge Request acts as a technical boundary:
temporary branch
→ validation
→ main branch
The important change is not the introduction of another GitLab object. It is the moment when desired state is published.
Flux should not be taught to wait for GitLab
Another option is to make Flux query the GitLab pipeline status before reconciliation.
That would couple the delivery mechanism to a specific CI system and introduce another failure point. The design would need to define which component retrieves the status, where credentials are stored, which pipeline is mandatory, and what happens when GitLab is unavailable.
It would also introduce another race: the status of one revision might be retrieved after a newer revision has already appeared.
Changing the publication boundary is simpler. The watched branch should be a source of state that has already passed the intended process.
Flux can then continue doing its normal job: reading Git and bringing Kubernetes to the recorded state.
The guarantee depends on every way to change the branch
This design provides prior validation only for changes that follow it. Direct pushes, manual merges, and administrative exceptions need separate verification. Using Merge Requests in automation does not establish that every path into the watched branch is protected.
This is a condition of the model, not a statement about existing bypasses in any organization. Routine and emergency procedures need explicit admission rules and authority.
Verification should cover more than a successful release. Failed CI, conflicts, retries, and attempts to change the watched branch outside the intended path also matter.
What this gate actually proves
This article is about event order: when a change is validated and when it becomes visible to Flux.
It does not evaluate the completeness of the specific checks inside GitOps CI. Their scope and quality are a separate subject.
Even a perfect check is not a gate when it starts after a direct push to the watched branch. Conversely, the correct event order does not prove that the checks cover every possible error.
These are different tasks:
placement of validation in the process
≠
completeness of validation
A gate does not confirm deployment success
A correctly placed admission boundary solves only one problem: it prevents a change from the intended delivery process from entering the watched branch before required GitOps CI succeeds.
It does not prove that Kubernetes pulled the image, completed the Deployment rollout, and started a working application.
Confirming the deployment result requires separate observation of Flux, Kubernetes, and the application itself. That is the next boundary in the process, not the responsibility of GitOps CI.
Checking your own GitOps design
Four questions are enough to examine the process:
- Which branch, tag, or other Git reference does Flux watch
- Who can change that reference, and by which path
- Which checks must finish before it changes
- Can a change enter that reference without those checks
If a successful CI result does not determine whether a change enters the watched reference, that CI is not a deployment gate.
Conclusion
With direct pushes to the watched branch, a separate CI pipeline can create the appearance of pre-deployment validation. The event order is different: the commit is published first, and CI and Flux only then start working with it independently.
A temporary branch, a Merge Request, and required checks change the order:
change
→ GitOps CI
→ permitted merge
→ Flux
A deployment gate is not defined by the presence of CI or by pipeline speed. It is defined by event order and access rules: validation first, publication of state for Flux second.
