Liftbridge: Kafka-style message streams in Go, built on NATS
Kafka-style message streaming in Go. Built on NATS. Single binary, no JVM, no ZooKeeper.
At a glance
- What is it?
- Liftbridge is a Go streaming server that adds a durable, replicated log on top of NATS. This article covers the mechanism, a Docker-based first run, the trade-offs, and who should stay with Kafka or JetStream instead.
- Who is it for?
- Adopt Liftbridge if you already run NATS, want a log with consumer groups, and would rather ship one Go binary than operate Kafka. Do not adopt it if you need a large connector ecosystem, a managed cloud service, or a client library outside the Go and gRPC surface.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 23 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What Liftbridge adds to a plain NATS deployment
Core NATS is a publish-subscribe bus with at-most-once delivery. If a subscriber is offline when a message is published, the message is gone. Liftbridge keeps NATS as the transport but writes published messages into a replicated log, so a consumer can attach to a stream, read from an offset, and resume later. The README describes the project as a "Kafka-lite" solution designed with the Go community first in mind, and the stated goal is to be a simpler, lighter alternative to Kafka and Pulsar, or a way to add streaming semantics to an existing NATS deployment.
The audience is narrow and specific. If your services are written in Go, if you already run NATS Server, and if you need ordered, replayable streams rather than fire-and-forget pub-sub, Liftbridge fits without asking you to run a JVM or a separate coordination service. Kafka's canonical clients are Java and librdkafka; Liftbridge's canonical client is go-liftbridge, also in Go. That symmetry is the whole pitch. If your stack is not Go-centric, the pitch weakens considerably, because the client surface is smaller than what Kafka or Pulsar offer.
The mechanism: a Raft-replicated log sitting behind NATS subjects
The repository layout tells you most of the architecture. The go.mod file lists hashicorp/raft alongside hashicorp/raft-boltdb, so stream metadata and leadership are coordinated through Raft with a BoltDB-backed log store. It also lists nats-server and nats.go directly as dependencies, and a module named nats-on-a-log. The server/ directory holds the streaming implementation, and main.go is the entry point for the single binary.
Messages arrive over NATS, get appended to a stream, and are stored on disk; consumers read them back through a gRPC API defined in liftbridge-api. The Dockerfile exposes port 9292, which is the gRPC endpoint the bench commands target with --servers localhost:9292. Streams are partitioned, and each partition has a leader replica with followers, which is why the producer benchmark takes an --ack-policy flag. The README's benchmark table shows the difference that batching makes: roughly 30K msgs/sec synchronous versus about 139K msgs/sec with pub-batch=100 on a single node at replication factor 1, and about 241K msgs/sec with four publishers. The README also states NATS JetStream reaches roughly 220K msgs/sec under similar settings, which is a useful honesty check: Liftbridge is not claiming to beat JetStream on raw throughput, and in the four-publisher case it is in the same range.
One design consequence is worth naming. Because the log is Raft-coordinated, a stream's availability depends on a quorum of replicas. A single-node setup with replication factor 1 has no fault tolerance at all, which is exactly the configuration the published benchmarks use.
Installing Liftbridge with Docker and publishing a first stream
The README points to the quick-start page at liftbridge.io/docs/quick-start.html for getting started. What the repository itself gives you is a Dockerfile that builds the binary from source and runs it as a non-root user, plus a Makefile target for a local development cluster.
The container image exposes port 9292 and declares a volume for stream data. The entrypoint is the liftbridge binary with no arguments, so configuration comes from a file or flags you supply separately:
FROM alpine:latest
RUN addgroup -g 1001 -S liftbridge && adduser -u 1001 -S liftbridge -G liftbridge
RUN mkdir -p /tmp/liftbridge && chown liftbridge:liftbridge /tmp/liftbridge
COPY --chown=liftbridge:liftbridge --from=build-base /workspace/liftbridge /usr/local/bin/liftbridge
EXPOSE 9292
VOLUME "/tmp/liftbridge/liftbridge-default"
ENTRYPOINT ["liftbridge"]
USER liftbridgeFor a full local topology rather than a single process, the Makefile wraps a Compose file that brings up a development cluster including NATS:
make compose-upThe Makefile defines compose-up as changing into docker/dev-cluster/ and running docker-compose up --build. When it finishes, you should have a NATS server and Liftbridge instances running locally, and the teardown target is make compose-down, which also removes the locally built images.
To build the binary directly instead of using Docker, the Makefile's build target runs a static Go build with CGO disabled:
make buildThat produces a liftbridge binary in the repository root. Note the requirement in the README: Go 1.25.6 or newer, and NATS Server v2.10.0 or newer. Once the server is up, the README's own producer benchmark is the most concrete first exercise, because it creates the stream for you:
go run ./bench/producer \
--servers localhost:9292 \
--messages 100000 \
--message-size 256 \
--concurrent 4 \
--pub-batch 100 \
--ack-policy leader \
--create-streamExpect the run to print throughput and latency numbers when it completes. The matching consumer command reads the stream back by name, with --stream bench-stream and --expected 100000.
Where Liftbridge is the wrong tool
The client ecosystem is the first constraint. Kafka has clients in Java, Python, Go, Rust, C, and more, plus a large connector ecosystem. Liftbridge's canonical client is go-liftbridge, and the server exposes gRPC, so anything outside Go means either using the gRPC API directly or relying on a community client the README does not enumerate. If your producers are Python services and your team does not want to maintain a gRPC binding, this is friction you will feel on day one.
Operational maturity is the second. There is no managed Liftbridge offering described in the README. You run the binary, you run NATS, and you run the Raft quorum. Kafka has managed services and a decade of operational writing; Liftbridge asks you to own that yourself.
Release cadence is the third, and it is the one to check before you commit. The repository is not archived and the last push was on 2026-09-09, so the codebase is being touched. But the published release history jumps from v1.9.0 in September 2022 to v26.01.1 in January 2026, with v1.8.0 before that in March 2022. A gap of that shape between tagged releases means you should read CHANGELOG.md and the release notes yourself rather than assuming a steady patch stream. The README does not document a rollback procedure for a failed upgrade, so plan your own.
Finally, if your actual need is simple request-reply or ephemeral fan-out, Liftbridge is overhead. Plain NATS does that with less machinery. The durable log only earns its cost when you need replay, ordering, and consumer offsets.
Liftbridge and NATS JetStream: two answers to the same question
JetStream is the alternative that matters most, because it lives inside NATS Server, which Liftbridge already requires. The README's own benchmark table puts JetStream at roughly 220K msgs/sec against Liftbridge's roughly 241K msgs/sec with four publishers and pub-batch=100, so raw throughput is not the deciding factor.
The difference is architectural. JetStream is a subsystem of the NATS server you are already running, configured through NATS and reachable with any NATS client that supports the JetStream API. Liftbridge is a separate process with its own Raft group, its own storage directory, and its own gRPC API. That separation is the reason it exists: it gives you a stream abstraction with consumer groups and partitions without turning your NATS deployment into the storage layer, and it keeps the client story in Go. The cost is a second thing to deploy, monitor, and upgrade.
If you are starting fresh on NATS 2.10 or later and your clients are already NATS clients, JetStream is the lower-friction choice and you should probably take it. Liftbridge makes sense when you want the Kafka-shaped model (named streams, partitions, consumer groups, explicit offsets) with a Go-native client, or when you are already running Liftbridge and the migration cost outweighs the consolidation benefit.
Licence, upgrade cost, and what the repository does not tell you
Liftbridge is Apache-2.0, which permits commercial use, modification, and redistribution with the usual attribution and notice obligations and an explicit patent grant. That is the same licence family as Kafka and NATS. It is a permissive licence, not a copyleft one, so embedding the binary in a product does not by itself force you to publish your own source. This is a description of the licence text, not legal advice; if you are redistributing a modified build, have counsel read the NOTICE and attribution requirements.
The upgrade cost is where the repository is quiet. The Dockerfile bakes a version string into the binary via a linker flag (server.Version), so a running instance can report which build it is. What is not documented in the README is on-disk format compatibility between releases, or how to move a stream directory from one version to another. Given the release history, that is the thing to establish before you put Liftbridge in a path where an upgrade cannot be rolled back. The Makefile and skaffold.yaml show how to deploy, not how to migrate.
On build cost: the Makefile's default build target produces a static binary with CGO disabled, which is the artifact the Dockerfile ships. The liftbridge-dev target builds with CGO enabled and netgo tags instead, which suggests the development build differs from the release build in ways worth knowing if you are debugging. Both are one command; neither requires a JVM or a separate coordination service, which is the recurring theme of the project.
Editorial conclusion
Adopt Liftbridge if you already run NATS, want a log with consumer groups, and would rather ship one Go binary than operate Kafka. Do not adopt it if you need a large connector ecosystem, a managed cloud service, or a client library outside the Go and gRPC surface. Before committing, verify the release cadence in CHANGELOG.md, confirm your NATS Server version is at least v2.10.0, and check whether the stream retention and partition settings you need are documented for your version.
Frequently asked questions
Does Liftbridge require a JVM or ZooKeeper?
No. The README describes Liftbridge as a single binary with no JVM, and the dependency list in go.mod shows Raft with a BoltDB store for coordination rather than ZooKeeper. The only external requirement stated is NATS Server v2.10.0 or newer.
What Go and NATS versions does Liftbridge need?
The README lists Go 1.25.6 or newer and NATS Server v2.10.0 or newer under Requirements, and go.mod declares go 1.25.6. The Dockerfile builds from a golang:1.25-alpine base image.
Is Liftbridge a replacement for Kafka?
The README positions it as a simpler, lighter alternative to systems like Kafka and Pulsar, and describes the vision as a Kafka-lite solution for the Go community. The trade-off is a smaller client ecosystem: the canonical client is go-liftbridge, and the server exposes a gRPC API.
What port does Liftbridge listen on?
The Dockerfile exposes port 9292, and the README's producer and consumer benchmark commands both connect to localhost:9292, which is the gRPC endpoint. Stream data is stored in a volume the Dockerfile declares at /tmp/liftbridge/liftbridge-default.
How does Liftbridge throughput compare with NATS JetStream?
The README's benchmark table reports about 241K msgs/sec for Liftbridge with four publishers at pub-batch=100, replication factor 1, and states JetStream achieves roughly 220K msgs/sec under similar settings. The same table shows synchronous single-publisher Liftbridge at about 30K msgs/sec.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/liftbridge-io-liftbridge)