---
title: "Get started with the CLI"
description: "Install monedula-gitops, point it at a local Kafka cluster, and create your first topic with producer and consumer access, from diff to verify."
url: https://monedula.dev/flock/docs/gitops/tutorials/getting-started-cli/
---
The local stack authenticates clients with SASL/SCRAM-SHA-512 and protects Schema Registry with HTTP basic auth, the same authentication paths a real deployment uses.

## What you need

- Docker with the Compose plugin. Check with `docker compose version`.
- Go 1.26 or newer, to build the CLI. Check with `go version`.
- Git and a bash shell. The commands on this page use bash syntax.

## 1. Get the quickstart files

Clone the repository and change into the CLI quickstart directory:

```bash
git clone https://github.com/monedula-dev/monedula-gitops.git
cd monedula-gitops/quickstart/cli
```

Run every command in this tutorial from this directory. It contains:

```text
quickstart/cli/
  docker-compose.yml          # Kafka (SCRAM) + kafka-init + Schema Registry
  .env.example                # credentials/endpoints (copy to .env)
  clusters/dev.yaml           # KafkaCluster "local" (env-based secret refs)
  manifests/
    payments-orders.yaml      # KafkaTopic payments.orders (+ access + AVRO schema)
    billing-access.yaml       # KafkaAccessPolicy billing-access
    schemas/orders-value.avsc # AVRO value schema (referenced by the topic)
```

## 2. Configure the credentials

Copy the example environment file:

```bash
cp .env.example .env
```

The defaults in `.env` work as they are:

```bash
KAFKA_BOOTSTRAP=localhost:9092
KAFKA_USERNAME=admin
KAFKA_PASSWORD=admin-secret
SCHEMA_REGISTRY_URL=http://localhost:8081
SR_USERNAME=sr
SR_PASSWORD=sr-secret
```

`KAFKA_USERNAME` and `KAFKA_PASSWORD` are the SCRAM-SHA-512 super-user, and `SR_USERNAME` and `SR_PASSWORD` the Schema Registry basic-auth user.

<Callout type="note" title="Demo credentials">
These are fake demo values, committed only so the playground works out of the box. Never reuse them. In a real setup, the CLI reads credentials from environment variables or files that your secrets manager provides.
</Callout>

The CLI connects as the super-user so that it bypasses ACL enforcement: a principal without super-user rights can lock itself out once `apply` creates a topic's ACLs, as [Connect to a secured Kafka cluster](/flock/docs/gitops/how-to/connect-to-a-secured-cluster/) explains.

## 3. Start Kafka and Schema Registry

```bash
docker compose up -d
```

Wait for the services to become healthy:

```bash
docker compose ps
```

The `kafka-init` container creates the SCRAM users and must finish before Kafka accepts those credentials. Schema Registry starts only after `kafka-init` succeeds and Kafka is healthy. Follow their logs until things settle:

```bash
docker compose logs -f kafka-init schema-registry
```

`kafka-init` should exit 0, and `schema-registry` should log that it is listening on `0.0.0.0:8081`. Press Ctrl-C to stop following the logs.

## 4. Load the credentials into your shell

`clusters/dev.yaml` tells the CLI to read the credentials from environment variables. Load `.env` into the current shell:

```bash
set -a; source .env; set +a
```

Run the remaining steps in this same shell.

## 5. Build the CLI

Build the binary from the cloned source:

```bash
go build -o ../../bin/monedula-gitops ../../cmd/monedula-gitops
```

Put the `bin` directory on your `PATH` for this shell and check that the binary runs:

```bash
export PATH="$PWD/../../bin:$PATH"
monedula-gitops version
```

A build from source reports its version as `dev`.

## 6. Read the manifests

The cluster config, `clusters/dev.yaml`, says where Kafka and Schema Registry are and which environment variables hold the credentials:

```yaml
apiVersion: gitops.monedula.dev/v1alpha1
kind: KafkaCluster
metadata:
  name: local
spec:
  bootstrapServers: localhost:9092
  auth:
    mechanism: SCRAM-SHA-512
    scram:
      username:
        valueFrom:
          env: KAFKA_USERNAME
      password:
        valueFrom:
          env: KAFKA_PASSWORD
  schemaRegistry:
    endpoint: http://localhost:8081
    auth:
      type: basic
      username:
        valueFrom:
          env: SR_USERNAME
      password:
        valueFrom:
          env: SR_PASSWORD
```

The topic manifest, `manifests/payments-orders.yaml`, declares the topic, its configuration, its access and its schema:

```yaml
apiVersion: gitops.monedula.dev/v1alpha1
kind: KafkaTopic
metadata:
  name: orders
  namespace: payments
spec:
  clusterRef:
    name: local
  topicName: payments.orders
  partitions: 6
  config:
    retention.ms: "604800000"
  access:
    producers:
      - principal: "User:svc-checkout"
        operations:
          - Write
    consumers:
      - principal: "User:svc-fraud"
        group: fraud-orders
        topicOperations:
          - Read
        groupOperations:
          - Read
  schema:
    format: AVRO
    compatibility: BACKWARD
    valueSchema:
      valueFrom:
        file: ./schemas/orders-value.avsc
  reconciliation:
    mode: Enforce
  deletionPolicy: Orphan
```

`clusterRef.name: local` points at the `KafkaCluster` named `local` in `clusters/dev.yaml`. The `access` block becomes Kafka ACLs that let `User:svc-checkout` write to the topic and `User:svc-fraud` read it with the consumer group `fraud-orders`. The schema file path is resolved relative to the manifest.

The third file, `manifests/billing-access.yaml`, is a `KafkaAccessPolicy`. It grants `User:svc-billing` `Read`, `Write` and `Describe` on every topic whose name starts with the prefix `billing.`, which a topic's `access` block cannot express.

## 7. Validate the manifests

```bash
monedula-gitops validate -f manifests --cluster-config-file clusters/dev.yaml
```

`validate` parses the manifests, resolves the referenced Avro schema file and checks that every `clusterRef` names a cluster in the config, without contacting Kafka. It reports the resources as valid and exits 0.

## 8. Preview the changes

```bash
monedula-gitops apply -f manifests --cluster-config-file clusters/dev.yaml --dry-run
```

`--dry-run` lists the planned operations without changing anything: create operations for the topic, the schema and the ACLs.

## 9. Apply

```bash
monedula-gitops apply -f manifests --cluster-config-file clusters/dev.yaml
```

This creates the `payments.orders` topic, registers its Avro value schema and creates the ACLs.

## 10. Verify and diff

Check that the live cluster now matches the manifests:

```bash
monedula-gitops verify -f manifests --cluster-config-file clusters/dev.yaml
```

`verify` reports no drift and exits 0. On drift it would exit 1, which makes it a CI check.

Then show the difference between the live state and the manifests:

```bash
monedula-gitops diff -f manifests --cluster-config-file clusters/dev.yaml
```

Right after a clean apply, `diff` shows no pending operations. `diff` exits 0 with or without drift and 2 on error, so use `verify` when the exit code should reflect drift.

## 11. Look at the result in Kafka

The broker container has the Kafka tools and a client config with the SASL/SCRAM settings. Describe the topic that `apply` created:

```bash
docker compose exec kafka kafka-topics \
  --bootstrap-server localhost:9092 \
  --command-config /etc/kafka/secrets/client.properties \
  --describe --topic payments.orders
```

List the ACLs:

```bash
docker compose exec kafka kafka-acls \
  --bootstrap-server localhost:9092 \
  --command-config /etc/kafka/secrets/client.properties --list
```

The topic has 6 partitions, and the ACL list contains the grants for `User:svc-checkout`, `User:svc-fraud` and `User:svc-billing` from your two manifests.

## 12. Import the cluster back into manifests

`import cluster` works in the other direction: it reads the live cluster and writes manifests. It never changes Kafka.

```bash
monedula-gitops import cluster --cluster-config-file clusters/dev.yaml --output-dir ./imported
ls -R ./imported
```

The `KafkaTopic` and `KafkaAccessPolicy` manifests under `./imported` mirror what you applied. [Import an existing Kafka cluster](/flock/docs/gitops/how-to/import-an-existing-cluster/) uses this to bring an existing cluster under management.

## 13. Try a change that needs approval

A partition increase cannot be undone, so `apply` holds it back until you approve it.

1. In `manifests/payments-orders.yaml`, change `partitions: 6` to `partitions: 12`.
2. Apply without approval:

   ```bash
   monedula-gitops apply -f manifests --cluster-config-file clusters/dev.yaml
   ```

   The partition increase is reported as `Blocked` and the command exits 3: the plan is sound, it waits for your approval, and nothing changes.
3. Apply again with the approval flag:

   ```bash
   monedula-gitops apply -f manifests --cluster-config-file clusters/dev.yaml --allow-destructive
   ```

   This time the topic grows to 12 partitions.

Change the file back to `partitions: 6` before you run the tutorial again on a fresh stack.

## 14. Clean up

Stop the stack and remove its volumes:

<Callout type="warning">
`-v` deletes the Kafka data volume, including the `payments.orders` topic and its ACLs.
</Callout>

```bash
docker compose down -v
```

## Next steps

- [Get started with the operator](/flock/docs/gitops/tutorials/getting-started-operator/) runs the same model inside Kubernetes.
- [Topic-centric access](/flock/docs/gitops/concepts/topic-centric-access/) explains what the `access` block compiles to.
- [Safety gates](/flock/docs/gitops/concepts/safety-gates/) covers approvals, pruning and deletion.