Kafka in Docker is the standard way to run a local development cluster, and the fastest way to ship a misconfiguration if you treat the container as a deployment. This page covers the local stack done right, and where the container stops being enough.
It assumes you know what Kafka is, and the production tutorial covers the operating layer.
Core needs
Running Kafka in Docker means three decisions: which image, which Compose topology, and how the listeners are configured so anything outside the Docker network can actually connect.
The image comes from an official registry: the Apache Kafka image or Confluent’s images are the maintained options, and modern tags run KRaft mode so no ZooKeeper container is needed. The habit of grabbing whichever image a tutorial happens to use has a real cost, because containerised Kafka tooling goes stale: running an abandoned image means running software with known, unpatched security vulnerabilities.
Maintained means republished, not merely published once. My colleague Nicolas Venegas republished our 95.3 images specifically to pick up base layer CVE fixes, and that refresh is what keeps the health scores on Docker Hub honest. A containerised Kafka stack inherits every unpatched layer underneath it, so the question to ask of any image is not when it was built but when it was last rebuilt.
Docker Compose is the standard way to run a complete local environment. A realistic template runs either a single broker for quick tests or three brokers to mirror production replication, plus the supporting services: a schema registry, Kafka Connect and a UI. A well-built stack is one docker compose pull and docker compose up away from a working environment. The idea is old and proven: Spotify open-sourced a combined Kafka and ZooKeeper Docker image years ago precisely to give developers a local Kafka for testing.
Networking is where local Kafka fails first. A client connects to the bootstrap server, then follows the addresses the broker advertises for each partition leader. Inside Docker those advertised addresses default to container hostnames that resolve nowhere on the host, so KAFKA_ADVERTISED_LISTENERS must expose one listener for traffic inside the Docker network and a second on localhost for clients outside it. Nearly every “connection refused from my app but the container is running” report is this.
The reason your Docker Kafka deserves real care: it is one of three clusters, not a toy beside the real one. Almost every team running Kafka in production runs at least three clusters, development, staging and production, and the Docker Compose stack IS the development cluster for most of them. A Compose file that diverges from production, different image lineage, different security posture, no schema registry, quietly moves your integration testing onto a system that does not resemble the one you ship to.
Which leads to the practice I hold teams to even in Compose: treat the configuration as code. Version the Compose file, the broker configs and the listener setup like any other source, and change them through review. Broker configuration changes should be version-controlled and applied through CI in production, and the habit starts, or dies, in the development stack. Two checks finish the discipline: after any rollback, diff the running config against your baseline and confirm it is okay, and periodically audit the running config against your documented best practices. Even shops running automated GitOps keep a break-glass option for emergencies and then forget to apply those changes back, which is exactly how drift starts.
Production warnings
A single-node Docker Kafka is a development tool, not a deployment target. Kafka is stateful software: without volume mounts its entire log sits in the container’s writable layer and dies with the container, and without memory limits and resource tuning it competes with everything else on the host. Container defaults are sized for laptops, and real deployments size deliberately: published container guidance for Kafka tooling starts at figures like 1 CPU and 2Gi of memory per service, and some UIs default to a 4 GB RAM minimum in their Docker configuration.
Disk is the risk that containers make easier to hit. Running out of disk is about the worst state a Kafka broker can be in and is very hard to recover from, and a containerised broker on an undersized volume gets there quickly. Retention settings and volume sizing have to be decided together.
Log retention is 7 days by default on most installs, and that default is what fills the volume. Producing gigabytes per hour for seven days adds up to a lot of disk, the volume fills slowly over weeks, and nobody notices until it is urgent. Set retention.bytes as well as the time-based retention on high-throughput topics, even if that means deleting data earlier than you intended.
Orchestrating containerised Kafka at production scale means Kubernetes, and Kafka’s stateful nature is exactly what makes that hard: persistent volume sizing, storage class selection and pod anti-affinity all need specific knowledge that a stateless service never demands. The KRaft migration removed ZooKeeper from that picture, which is one less stateful system to run, but the brokers themselves still carry all of the above.
The honest framing for all of it: the failure pattern the production tutorial describes, misconfiguration rather than software defect, applies double in containers. Docker does not change the failure modes. It just makes the misconfiguration faster to ship.
The cost of getting containerised state wrong is documented in dollars: one post-incident analysis of a production Kubernetes-hosted Kafka failure put it at $240,000 in SLA penalties over 62 minutes. And storage choice is exactly the kind of decision that causes it. Grab replaced an earlier Kafka storage design built on NVMe instance store volumes because those volumes could not survive worker node replacement without manual intervention: fast disks, wrong lifecycle. In containers, the storage question is never only “is it fast”, it is “what happens to it when the node goes away”.
When something does go wrong, my operational rule is one change at a time. Do not do what we did and roll up your settings change and your disk increase to go at the same time: increase your disk first, make sure that works, then increase your network throughput, single changes stacked on top of each other with each one verified. A cluster that just hurt you is exactly the wrong place to apply three fixes at once, because when the graphs move you will not know which change moved them. Containers make bundling changes temptingly easy, one new image, five config diffs, and the discipline matters more there, not less.
FAQ
Why can’t my application connect to Kafka running in Docker?
Because a client connects to the bootstrap server and then follows the addresses the broker advertises, and inside Docker those default to container hostnames that resolve nowhere on the host. KAFKA_ADVERTISED_LISTENERS must expose one listener for traffic inside the Docker network and a second on localhost for clients outside it. Nearly every “connection refused but the container is running” report is this.
Is Kafka in Docker suitable for production?
A single-node Docker Kafka is a development tool, not a deployment target. Kafka is stateful: without volume mounts the log dies with the container, and container defaults are sized for laptops. Production containerised Kafka means Kubernetes, with persistent volume sizing, storage class selection and pod anti-affinity handled deliberately, and the storage question is never only “is it fast” but “what happens to it when the node goes away”.