Running Kafka Locally
You can't build intuition for replication and leader election by reading about them. This module gets a real multi-broker cluster running on your machine, and then breaks it on purpose.
Learning objectives
- Beginner: Bring up a local Kafka broker via Docker Compose and produce/consume a test message from the CLI.
- Intermediate: Explain why KRaft mode removed the need for a separate ZooKeeper cluster.
- Advanced: Deliberately kill the leader broker in a 3-broker local cluster and observe leader election and ISR behavior firsthand.
A container packages an application plus everything it needs to run (dependencies, runtime, configuration) into one portable unit that runs identically on your laptop and in production. A Docker image is the immutable, buildable template; a container is a running instance of an image. You'll use Docker here just to get a Kafka broker running without installing Kafka natively — full container-building coverage (Dockerfiles, building your own images) is in Module 19, once you have services worth containerizing.
▲ Pitfall
Don't confuse "Docker installed" with "Kafka running." Docker is just the container runtime — you still need a Kafka image and the correct broker configuration (see below) actually listening and reachable from your host.
◆ The problem
Kafka needs one authoritative source of truth for cluster metadata — which brokers exist, which partition replicas live where, who the current leader of each partition is — and a way to elect a new leader when a broker dies without two brokers disagreeing about who's in charge. For most of Kafka's history this job belonged to ZooKeeper, a separate coordination service you had to deploy, operate, and keep available alongside Kafka itself.
KRaft (Kafka Raft) removes that external dependency by having a subset of the Kafka brokers themselves run the Raft consensus protocol to agree on cluster metadata. Brokers can be configured with a controller role, a broker role, or both — in a local single-node setup it's common to run one process with both roles combined.
◆ Under the hood
Under KRaft, cluster metadata (topic configs, partition assignments, ISR membership) is itself stored as an event log — a __cluster_metadata internal topic replicated among the controller quorum via Raft. This is a deliberate design consistency: Kafka now uses "a replicated log" to manage its own metadata, the same core abstraction it uses for your application data. A controller is elected leader of this metadata log the same way a partition leader is elected for a regular topic (see §02.4).
services: kafka-broker: image: apache/kafka:latest container_name: kafka-broker ports: - "9092:9092" environment: KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: 'broker,controller' KAFKA_LISTENERS: 'PLAINTEXT://:9092,CONTROLLER://:9093' KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://localhost:9092' KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-broker:9093' KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER' KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true'
💻 Code example
services: kafka-broker: image: apache/kafka:latest container_name: kafka-broker ports: - "9092:9092" environment: KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: 'broker,controller' KAFKA_LISTENERS: 'PLAINTEXT://:9092,CONTROLLER://:9093' KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://localhost:9092' KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-broker:9093' KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER' KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true'
# create a topic with 3 partitions, replication factor 1 (single broker) kafka-topics.sh --create --topic library-events --partitions 3 --replication-factor 1 --bootstrap-server localhost:9092 # produce a message with no key kafka-console-producer.sh --topic library-events --bootstrap-server localhost:9092 # produce with a key (format: key:value) kafka-console-producer.sh --topic library-events --bootstrap-server localhost:9092 \ --property "parse.key=true" --property "key.separator=:" # consume from the beginning of the log kafka-console-consumer.sh --topic library-events --from-beginning --bootstrap-server localhost:9092 # consume showing keys and partitions — makes key→partition routing visible kafka-console-consumer.sh --topic library-events --from-beginning --bootstrap-server localhost:9092 \ --property print.key=true --property print.partition=true
Run the keyed producer with the same key several times, then consume with print.partition=true — you'll see every record with that key lands on the same partition number, which is the most convincing way to internalize §01.4's partitioning rule.
💻 Code example
# create a topic with 3 partitions, replication factor 1 (single broker) kafka-topics.sh --create --topic library-events --partitions 3 --replication-factor 1 --bootstrap-server localhost:9092 # produce a message with no key kafka-console-producer.sh --topic library-events --bootstrap-server localhost:9092 # produce with a key (format: key:value) kafka-console-producer.sh --topic library-events --bootstrap-server localhost:9092 \ --property "parse.key=true" --property "key.separator=:" # consume from the beginning of the log kafka-console-consumer.sh --topic library-events --from-beginning --bootstrap-server localhost:9092 # consume showing keys and partitions — makes key→partition routing visible kafka-console-consumer.sh --topic library-events --from-beginning --bootstrap-server localhost:9092 \ --property print.key=true --property print.partition=true
Everything so far ran with replication-factor 1 — one broker, zero fault tolerance. Production Kafka runs with 3+ brokers and a replication factor of 3, and the concepts below only become real once you run that shape locally and kill a broker mid-flight.
Replication
Each partition's records are copied to replication.factor brokers. One is the leader — it alone serves all produce/fetch requests for that partition. The others are followers, which continuously fetch from the leader to stay caught up, purely for durability; they don't serve client traffic directly (in the default configuration).
ISR & min.insync.replicas
A replica is in the ISR (in-sync replica set) only if it has fetched up to (near) the leader's latest offset within a configured lag threshold. A slow or disconnected follower falls out of the ISR. min.insync.replicas (a topic/broker setting, exercised fully in Module 13) sets a floor on how many ISR members must acknowledge a write before it's considered durable when the producer uses acks=all — this is the setting that actually enforces "durable," not acks alone.
Leader election
When a partition's leader broker fails, the controller picks a new leader from the remaining ISR members (never from an out-of-sync replica, by default — that would silently lose acknowledged data). This is why ISR membership matters so much: it defines the pool of "safe" leader candidates at any moment.
Leader election picks the new leader only from brokers that were in the ISR — never from a replica that had fallen behind, since that could silently drop already-acknowledged records.
▲ Pitfall
Setting min.insync.replicas equal to your full replication factor (e.g. 3-of-3) means the topic becomes unwritable the moment a single broker is unavailable, since acks=all can never be satisfied. Most production setups use replication factor 3 with min.insync.replicas=2: tolerate one broker failure while still writing.
✓ Quick recap
What replaced ZooKeeper's role in modern Kafka? KRaft — a subset of brokers running Raft consensus directly, storing metadata as an internal replicated log. Do follower replicas serve client reads by default? No — only the leader serves produce/fetch traffic; followers exist purely for durability. Can a leader be elected from a replica that isn't in the ISR? No, not by default — that would risk losing already-acknowledged data. With replication factor 3, what's a typical safe min.insync.replicas? 2 — tolerates one broker down while still requiring durable acknowledgment from a majority.
Want a visual for this concept?
Generate a diagram tailored to “Running Kafka Locally” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.
Sign in to generate a visual →