# kaf: a kubectl-style CLI for Apache Kafka

> birdayz/kaf wraps Kafka administration and message inspection in a single Go binary with a kubectl-like command tree. It is convenient for ad hoc topic and consumer-group work, and it is not a replacement for a full management platform.

**birdayz/kaf** — Modern CLI for Apache Kafka, written in Go.

- Repository: https://github.com/birdayz/kaf
- Stars: 2,442 · Forks: 163
- Language: Go
- License: Apache-2.0
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/birdayz-kaf

## The gap kaf fills between raw Kafka tools and a web console

Kafka ships with shell scripts, and most managed platforms ship a web UI. Both leave a hole. The scripts are awkward to compose, and the UI is hard to script against. kaf sits in between: a single Go binary that exposes Kafka operations as a command tree, with the README describing it as a "Kafka CLI inspired by kubectl & docker".

The target user is an engineer who already knows Kafka concepts and wants to answer questions like which consumer group is lagging, what a topic's partition layout looks like, or what is actually on a topic, without leaving the terminal. The README's own examples are all of that shape: `kaf topics`, `kaf topic describe mqtt.messages.incoming`, `kaf groups`, `kaf group describe dispatcher`. It is an operator's tool, not a library. If you need to embed Kafka access in an application, the underlying Sarama client is the right layer, and kaf adds nothing there.

## How kaf talks to a cluster: clusters, config files and Sarama

kaf keeps a local notion of clusters. `kaf config add-cluster local -b localhost:9092` registers a broker address under a name, and `kaf config select-cluster` picks one from a dropdown list. Once a cluster is selected, commands such as `kaf node ls` and `kaf topics` operate against it without repeating the bootstrap address.

The README points to the examples folder for configuration, and that folder is where the real story is: basic.yaml, sasl_plaintext.yaml, sasl_ssl.yaml, sasl_ssl_custom_ca.yaml, sasl_ssl_insecure.yaml, sasl_ssl_oauth.yaml, sasl_ssl_oauth_token.yaml, sasl_ssl_scram.yaml, sasl_v1_handshake.yaml, aws_msk_iam.yaml, ssl_keys.yaml and schema_registry_basic_auth.yaml. That list is the honest scope statement for authentication. Plaintext, SASL variants, TLS with custom CAs, OAuth and AWS MSK IAM are all covered by example files, and the go.mod confirms the dependencies that back them: IBM/sarama for the protocol, aws-msk-iam-sasl-signer-go for MSK, xdg/scram and golang.org/x/oauth2 for the auth paths.

The binary is built with Cobra, which is why the command structure feels like kubectl and why shell completion is generated rather than hand-written. There is no daemon, no cache and no state beyond the config file. Every invocation opens a connection, does the work and exits. That is a deliberate simplification and it shapes the limitations later in this article.

## Installing kaf and inspecting a first topic

The README lists four installation routes. The Go route builds from source and requires a Go toolchain:

```bash
go install github.com/birdayz/kaf/cmd/kaf@latest
```

There is also a download script that places the binary in a directory you choose, an AUR package for Arch Linux, and a Homebrew tap:

```bash
brew tap birdayz/kaf
brew install kaf
```

Confirm the binary is on your PATH before going further. The README gives this check:

```bash
kaf --version
```

Now register a broker. The README's example assumes an unauthenticated Kafka on the default port:

```bash
kaf config add-cluster local -b localhost:9092
```

If you want a throwaway broker to try this against, the repository ships a docker-compose.yaml with Zookeeper and a Confluent Kafka image exposing 9092 on the host, and the Makefile wraps it as `make run-kafka`. After registering the cluster, list topics and describe one:

```bash
kaf topics
kaf topic describe mqtt.messages.incoming
```

For a first real use, produce and then consume a message. The README pipes stdin into a topic and reads it back as JSON, one record per line:

```bash
echo test | kaf produce mqtt.messages.incoming
kaf consume mqtt.messages.incoming --output json-each-row
```

The consume output is a JSON object per line with topic, partition, offset, timestamp, headers, key and payload fields, which is what makes the next section possible.

## Piping topics together and the overwrite trap in kaf produce

The most interesting design decision in kaf is that produce and consume share an output format. `kaf consume topic-a --output json-each-row -f | kaf produce topic-b --input json-each-row` moves records from one topic to another through a shell pipe. The `-f` flag on consume is what keeps the stream open rather than exiting after the current end of the topic, so the pipe stays alive as new records arrive.

The README is explicit about a sharp edge here, and it deserves to be quoted rather than paraphrased: "kaf produce will overwrite key, partition and all the headers and of input messages if provided". In other words, if you pass `--key`, `--header` or a partition selector while using `--input json-each-row`, the flags win and the fields carried in the JSON are discarded. For a straight topic-to-topic copy that is fine. For a transform that is supposed to preserve keys, it is a silent data change. The README also notes that the round trip is not byte-identical in its own example: the produced payload is `{"data":1}` and the consumed payload comes back as `{data:1}`.

Filtering is available on the consume side: `kaf consume mqtt.messages.incoming --header h1:hv1` selects records by header before they reach your terminal or your pipe. There is no equivalent filter documented on the produce side.

## Offset reset with kaf group commit, and where it can hurt

Consumer group offset management is the part of kaf that most obviously replaces a pile of shell scripts. The README gives three forms:

```bash
kaf group commit dispatcher -t mqtt.messages.incoming --offset latest --all-partitions
kaf group commit dispatcher -t mqtt.messages.incoming --offset oldest --all-partitions
kaf group commit dispatcher -t mqtt.messages.incoming --offset 1001 --partition 0
```

The first two move every partition to the end or the beginning of the topic. The third targets one partition and one absolute offset. This is a destructive operation in the ordinary sense: a running consumer that has committed past the target offset will not be rewound, and a consumer that has not will skip records. Nothing in the README describes a dry-run mode, a confirmation prompt, or a way to preview which partitions would move. The command takes the group name and topic and executes.

That is the clearest limitation of kaf. It assumes you know what you are doing and that you have the permissions to do it. There is no audit log, no undo, and no server-side record of the change beyond whatever Kafka itself keeps. Used against a production consumer group by someone who has not checked the current committed offsets first, `kaf group commit --offset latest` will cause message loss from the application's point of view. Treat the offset subcommands as you would treat a direct write to a database table.

## When kaf is the wrong tool

kaf is a client, and that decides most of the boundaries. If you need continuous lag monitoring with alerting, kaf gives you a point-in-time answer and exits; something has to run it on a schedule and interpret the output. If you need topic creation with replication policies enforced centrally, kaf is not a control plane. If you need a graphical view for people who do not use a terminal, it is the wrong shape entirely.

The README does not document a Docker image for kaf itself, though the repository has a Dockerfile and the Makefile has a `docker-build` target that tags the result under `${DOCKER_REGISTRY}/${DOCKER_ORG}/${DOCKER_NAME}:${DOCKER_TAG}`. The Dockerfile builds a static binary and copies it into a scratch image running as user 1001 with `/bin/kaf` as the entrypoint. That is fine for running kaf inside a cluster, but the config file has to be mounted or generated, since the image contains nothing else.

One more boundary worth naming: the README documents schema registry configuration through an example file, but the examples list is the only description of what the schema registry integration actually does. If schema-aware production is part of your workflow, read examples/schema_registry_basic_auth.yaml and the pkg directory before assuming parity with a dedicated schema tool.

## Alternatives and the difference in approach

The closest comparison is kcat, formerly kafkacat. Both are command-line Kafka clients, but they diverge in how much of Kafka's object model they expose. kcat is built around producing and consuming with a compact flag set, and its metadata mode prints broker and topic information in a terse format. kaf is built around named subcommands that map to Kafka concepts: nodes, topics, groups, offsets. If your work is mostly moving bytes in and out of topics, kcat's model is smaller and closer to the metal. If your work is mostly inspecting and administrating, kaf's command tree is easier to remember and easier to complete.

The second alternative is the broker vendor's own tooling, including the shell scripts shipped with Kafka and the web consoles that managed services provide. Those cover topic creation and configuration changes that kaf does not attempt. The trade-off is that they are tied to a distribution or a vendor, whereas kaf is a single binary that speaks the Kafka protocol through Sarama and can point at any cluster the config supports, including AWS MSK through the IAM signer in the dependency list.

A third option, for teams that want to stay in Go, is to write against Sarama directly. That gives full control and no CLI surface to learn, at the cost of writing and maintaining the tooling yourself. kaf is essentially that tooling, already written and released as v0.2.14 on 2026-02-27.

## Maintenance, licensing and upgrade cost

The repository is not archived, and the last push was on 2026-05-07. Releases are not on a fixed cadence: v0.2.11 in March 2025, v0.2.13 in April 2025, v0.2.14 in February 2026. Between v0.2.13 and v0.2.14 there is a gap of roughly ten months, so plan for periods without releases rather than assuming a steady stream of fixes.

Upgrades are cheap in the normal case. The binary is self-contained, the config lives in a file the user controls, and there is no server-side component to migrate. The main upgrade risk is the config format itself, since the examples folder is the only schema documentation and a new authentication method may require a new file rather than a flag. Keep your working config out of the repository's examples directory so that a `git pull` does not overwrite it.

Licensing is Apache-2.0, which is permissive and includes a patent grant. The dependencies are a mix: several are Apache-2.0, some are MIT, and the go.mod shows `github.com/Masterminds/sprig v2.22.0+incompatible`, where the `+incompatible` suffix signals a module that predates Go modules. If your organization runs automated licence scanning, expect the dependency report to be longer than the single licence line in the repository root. This is a description of what the files show, not legal advice; route anything consequential through your own review.

## Conclusion

Adopt kaf if you already have Kafka clusters and want fast, scriptable topic, group and offset operations from a terminal, especially where the JSON output can be piped into other tools. Skip it if you need a GUI, a long-running monitoring service, or a broker-side management layer, because kaf is a client with no server component. Before rolling it out, verify that your cluster's authentication method has a matching example file under examples/ and test offset reset commands against a non-production consumer group.

## FAQ

### How do I install kaf on macOS or Linux?

The README lists four routes: `go install github.com/birdayz/kaf/cmd/kaf@latest`, a download script that installs into a BINDIR you set, Homebrew via `brew tap birdayz/kaf` then `brew install kaf`, and the AUR package kaf-bin on Arch Linux. Run `kaf --version` afterwards to confirm the binary is on your PATH.

### How do I add a Kafka cluster to kaf?

Use `kaf config add-cluster local -b localhost:9092` to register a broker under a name, then `kaf config select-cluster` to choose it from a dropdown list. Authentication variants such as SASL, TLS with a custom CA, OAuth and AWS MSK IAM are documented as example files in the examples folder.

### Can kaf pipe messages from one Kafka topic to another?

Yes. The README gives `kaf consume topic-a --output json-each-row -f | kaf produce topic-b --input json-each-row`, where the `-f` flag keeps the consumer stream open. Be aware that the README warns kaf produce will overwrite key, partition and headers of input messages if those are provided as flags.

### How do I reset a consumer group offset with kaf?

The README documents `kaf group commit dispatcher -t mqtt.messages.incoming --offset latest --all-partitions` for moving all partitions to the end, `--offset oldest` for the beginning, and `--offset 1001 --partition 0` for a single partition at an absolute offset. The README does not describe a dry-run mode or confirmation prompt.

### Does kaf support shell autocompletion?

Yes, via the `kaf completion` subcommand. The README gives scripts for Bash on Linux and macOS, Zsh, Fish and Powershell, each writing the completion output to the location that shell expects.

## Sources

- [birdayz/kaf on GitHub](https://github.com/birdayz/kaf)
- [Issues](https://github.com/birdayz/kaf/issues)
- [License: Apache-2.0](https://github.com/birdayz/kaf/blob/master/LICENSE)
- [README](https://github.com/birdayz/kaf/blob/master/README.md)
- [Releases](https://github.com/birdayz/kaf/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/birdayz-kaf
