Apache Kafka

Apache Kafka 3.9 in KRaft mode — no ZooKeeper. The image generates server.properties from environment variables, formats the KRaft storage idempotently on first boot, and supervises the broker with the same health endpoint as every other container in this collection.

Quickstart

bash
docker run -d \
  --name kafka \
  -v kafka-data:/opt/containers/data \
  -p 9092:9092 \
  ghcr.io/supanadit/containers/apache-kafka

The defaults give you a single combined broker + controller listening on PLAINTEXT://:9092 and advertising PLAINTEXT://localhost:9092 — good for local development. If clients connect from other machines, set the advertised listener to a routable hostname:

bash
docker run -d \
  --name kafka \
  -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka.example.com:9092 \
  -v kafka-data:/opt/containers/data \
  -p 9092:9092 \
  ghcr.io/supanadit/containers/apache-kafka

Configuration

VariableDefaultDescription
KAFKA_NODE_ID1KRaft node id; must be unique per node.
KAFKA_PROCESS_ROLESbroker,controllerCombined role by default; use broker or controller alone for split roles.
KAFKA_CONTROLLER_QUORUM_VOTERS1@localhost:9093KRaft quorum definition, id@host:port comma-separated.
KAFKA_LISTENERSPLAINTEXT://:9092,CONTROLLER://:9093Listener definitions.
KAFKA_ADVERTISED_LISTENERSPLAINTEXT://localhost:9092Address handed to clients; set a routable host in multi-host setups.
KAFKA_LISTENER_SECURITY_PROTOCOL_MAPCONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,...Maps each listener name to its protocol (SSL, SASL_SSL, ...).
KAFKA_INTER_BROKER_LISTENER_NAMEPLAINTEXTListener brokers use to talk to each other.
KAFKA_CLUSTER_ID(generated)Pin the KRaft cluster id; otherwise generated and persisted.
KAFKA_NUM_PARTITIONS1Default partitions for auto-created topics.
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR1Raise to 3 in a real cluster.
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR1Raise to 3 in a real cluster.
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR1Minimum in-sync replicas for the transaction log.
KAFKA_LOG_RETENTION_HOURS168How long log segments are kept.
KAFKA_LOG_SEGMENT_BYTES1073741824Segment rollover size.
KAFKA_AUTO_CREATE_TOPICS_ENABLEtrueAllow clients to implicitly create topics.
KAFKA_DELETE_TOPIC_ENABLEtrueAllow topic deletion.
KAFKA_HEAP_OPTS(empty)JVM heap, e.g. -Xmx1g -Xms1g.
KAFKA_LOG4J_ROOT_LOGLEVELINFOBroker log level (LOG_LEVEL also works).

TLS

Set KAFKA_SSL_ENABLE=true and either provide PKCS12/JKS stores or inline PEM. TLS applies to every SSL/SASL_SSL listener.

VariableDefaultDescription
KAFKA_SSL_ENABLEfalseEnables the TLS settings below.
KAFKA_SSL_KEYSTORE_TYPEPKCS12PKCS12, JKS or PEM.
KAFKA_SSL_KEYSTORE_LOCATION(empty)Path to the keystore (mount it, e.g. under /opt/containers/config).
KAFKA_SSL_KEYSTORE_PASSWORD(empty)Keystore password.
KAFKA_SSL_KEY_PASSWORD(empty)Private key password.
KAFKA_SSL_TRUSTSTORE_TYPEPKCS12Truststore type.
KAFKA_SSL_TRUSTSTORE_LOCATION(empty)Path to the truststore.
KAFKA_SSL_TRUSTSTORE_PASSWORD(empty)Truststore password.
KAFKA_SSL_CLIENT_AUTHnonenone, required or requested (mTLS).
KAFKA_SSL_PROTOCOLTLSv1.3TLS protocol.
KAFKA_SSL_ENABLED_PROTOCOLSTLSv1.3,TLSv1.2Allowed protocol list.

For PEM mode (KAFKA_SSL_KEYSTORE_TYPE=PEM) use KAFKA_SSL_KEYSTORE_KEY, KAFKA_SSL_KEYSTORE_CERTIFICATE_CHAIN and KAFKA_SSL_TRUSTSTORE_CERTIFICATES instead of the store files.

SASL

VariableDefaultDescription
KAFKA_SASL_ENABLED_MECHANISMSPLAINComma-separated mechanisms (PLAIN, SCRAM-SHA-256, SCRAM-SHA-512).
KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOLPLAINMechanism for broker-to-broker traffic.
KAFKA_SASL_JAAS_CONFIG(empty)Global JAAS config applied to every SASL listener; per-listener overrides use KAFKA_SASL_JAAS_CONFIG_<LISTENER_NAME>.
KAFKA_SASL_SCRAM_USERS(empty)user:pass[,user:pass...]; SCRAM credentials are created via kafka-configs.sh once the broker is ready.

Health

The broker is combined-mode KRaft by default, so the health endpoint composites an Apache ZooKeeper-free broker check: the ezx /health API listens on port 8080 and reports the broker listener TCP probe plus KRaft readiness.

bash
curl -s http://localhost:8080/health

KRaft bootstrap

On first boot the entrypoint resolves a cluster id in this order: KAFKA_CLUSTER_ID, then meta.properties in the data directory, then the persisted cluster.id file, and finally a freshly generated kafka-storage.sh random-uuid. Storage is then formatted with --ignore-formatted, so restarts are a no-op.

Two safety rails matter when mounting volumes:

  • An explicit KAFKA_CLUSTER_ID that disagrees with meta.properties in the
  • A non-empty data directory that is not KRaft-formatted refuses to format, so

Clustering

A three-node combined cluster only needs per-node identity and the shared quorum definition:

yaml
services:
  kafka-1: &kafka
    image: ghcr.io/supanadit/containers/apache-kafka
    environment:
      KAFKA_NODE_ID: "1"
      KAFKA_CONTROLLER_QUORUM_VOTERS: "1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093"
      KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://kafka-1:9092"
      KAFKA_LISTENERS: "PLAINTEXT://:9092,CONTROLLER://:9093"
    volumes: [kafka-1-data:/opt/containers/data]
  kafka-2:
    <<: *kafka
    environment:
      KAFKA_NODE_ID: "2"
      KAFKA_CONTROLLER_QUORUM_VOTERS: "1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093"
      KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://kafka-2:9092"
    volumes: [kafka-2-data:/opt/containers/data]
  kafka-3:
    <<: *kafka
    environment:
      KAFKA_NODE_ID: "3"
      KAFKA_CONTROLLER_QUORUM_VOTERS: "1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093"
      KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://kafka-3:9092"
    volumes: [kafka-3-data:/opt/containers/data]

In a multi-node cluster also raise KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR, the transaction-state replication factor and KAFKA_TRANSACTION_STATE_LOG_MIN_ISR (typically to 3 and 2).

Metadata backup

KRaft removes ZooKeeper but the metadata log still deserves backups. Enable the scheduled snapshot with KAFKA_METADATA_BACKUP_ENABLE=true; it runs via the ezx scheduler into /opt/containers/backup with KAFKA_METADATA_BACKUP_SCHEDULE (cron, default 0 3 *), KAFKA_METADATA_BACKUP_TIMEZONE (default UTC) and KAFKA_METADATA_BACKUP_RETENTION_DAYS (default 7).

Next steps