---
title: "Validate and preview changes in CI"
description: "Run validate, diff and apply --dry-run in a CI pipeline so every pull request shows the exact Kafka changes before anything is applied."
url: https://monedula.dev/flock/docs/gitops/how-to/validate-and-preview-in-ci/
---
On every pull request, `validate`, `diff` and `apply --dry-run` fail the build on a broken manifest and show reviewers the exact operations a merge would run against Kafka. None of them changes the cluster.

## Before you start

- A way to run the CLI on your CI runner. [Install monedula-gitops](/flock/docs/gitops/how-to/install/) lists the methods, including `go install` and the Docker image `ghcr.io/monedula-dev/monedula-gitops`.
- Your manifests and a cluster config file in the repository, `manifests/` and `cluster.yaml` on this page.
- For the preview steps, CI secrets that provide the credentials your cluster config reads with `valueFrom.env`, for a principal with describe rights on the kinds your manifests declare ([Least-privilege connecting principal](/flock/docs/gitops/reference/connecting-principal-acls/)).

## Steps

### 1. Validate the manifests

`validate` parses and checks the manifests without contacting any cluster, so it needs no credentials:

```bash
monedula-gitops validate -f ./manifests
```

A malformed manifest fails with exit code 2 and an error that names the resource and the rule. For example, this topic asks for zero partitions:

```yaml
apiVersion: gitops.monedula.dev/v1alpha1
kind: KafkaTopic
metadata:
  name: bad-topic
spec:
  clusterRef:
    name: shared
  topicName: bad.topic
  partitions: 0
```

`validate` rejects it with:

```text
KafkaTopic bad-topic: spec.partitions must be >= 1
```

Add the cluster config to also check that every `clusterRef` names a cluster defined in it:

```bash
monedula-gitops validate -f ./manifests --cluster-config-file ./cluster.yaml
```

If your manifests sit in subdirectories, add `-R` to read them recursively.

### 2. Preview the changes

`diff` compares the manifests with the live cluster and lists the operations needed to bring the cluster in line:

```bash
monedula-gitops diff -f ./manifests --cluster-config-file ./cluster.yaml
```

Each planned operation shows its risk and whether it needs approval, as a partition increase does. When the cluster already matches the manifests, `diff` prints `No changes`. It exits 0 with or without drift and 2 on a configuration or connection error, so it never fails the build on what it finds.

### 3. Render the plan as a document

`apply --dry-run` renders the plan that `apply` would execute, without running it. With `-o json` or `-o yaml`, the plan is a deterministic document that you can keep as a build artifact or feed to other tools:

```bash
monedula-gitops apply -f ./manifests --cluster-config-file ./cluster.yaml --dry-run -o json
```

Each entry in its `changes` list names the action, the resource and its target, the risk, and `requiresApproval`. `apply --dry-run` exits 0 even when the plan contains operations that need approval; the real `apply` reports those as `Blocked` and exits 3 until you [approve them](/flock/docs/gitops/concepts/safety-gates/).

### 4. Add the steps to your pipeline

This GitHub Actions job runs the three commands on every pull request. Adapt the paths, the install step and the secret names, which must match the `valueFrom.env` names in your cluster config:

```yaml
name: kafka-preview
on:
  pull_request:
jobs:
  preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.26'
      - name: Install monedula-gitops
        run: go install github.com/monedula-dev/monedula-gitops/cmd/monedula-gitops@latest
      - name: Validate
        run: monedula-gitops validate -f ./manifests --cluster-config-file ./cluster.yaml
      - name: Preview
        env:
          KAFKA_USER: ${{ secrets.KAFKA_USER }}
          KAFKA_PASSWORD: ${{ secrets.KAFKA_PASSWORD }}
        run: |
          monedula-gitops diff -f ./manifests --cluster-config-file ./cluster.yaml
          monedula-gitops apply -f ./manifests --cluster-config-file ./cluster.yaml --dry-run -o json > plan.json
```

- `go install` builds the CLI with Go 1.26 or newer. To build a fixed release, replace `@latest` with its tag, such as `@v1.0.0`.
- The `Validate` step runs without credentials. Only the `Preview` step receives them.
- The diff appears in the job log. Save `plan.json` as a build artifact if reviewers or other jobs need the document.

On a pull request that changes a manifest, the job log of the `Preview` step lists the planned operations, and the `Validate` step fails the build if a manifest is invalid. The job that applies after the merge runs the same `apply` command without `--dry-run`.

## Related

- The [validate](/flock/docs/gitops/reference/cli/validate/), [diff](/flock/docs/gitops/reference/cli/diff/) and [apply](/flock/docs/gitops/reference/cli/apply/) command references list every flag.
- [Promote changes across environments](/flock/docs/gitops/how-to/promote-across-environments/) runs the same steps per environment.
- Runnable versions: [scenarios/02-invalid-manifest](https://github.com/monedula-dev/monedula-gitops/tree/main/scenarios/02-invalid-manifest) and [scenarios/17-diff-dry-run](https://github.com/monedula-dev/monedula-gitops/tree/main/scenarios/17-diff-dry-run)