We will start a Docker Compose cluster with Kafka, KNEP, confluent-schema-registry and a Kafka UI.
First, we need to create a docker-compose.yaml file. This file will define the services we want to run in our local environment:
    
    
        cat <<EOF > docker-compose.yaml
services:
  kafka:
    image: apache/kafka:3.9.0
    container_name: kafka
    ports:
      - "9092:19092"
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_LISTENERS: INTERNAL://kafka:9092,CONTROLLER://kafka:9093,EXTERNAL://0.0.0.0:19092
      KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:9092,EXTERNAL://localhost:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
      KAFKA_CLUSTER_ID: 'abcdefghijklmnopqrstuv'
      KAFKA_LOG_DIRS: /tmp/kraft-combined-logs
  schema-registry:
      image: confluentinc/cp-schema-registry:latest
      container_name: schema-registry
      depends_on:
        - kafka
      ports:
        - "8081:8081"
      environment:
        SCHEMA_REGISTRY_HOST_NAME: schema-registry
        SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: kafka:9092
        SCHEMA_REGISTRY_LISTENERS: http://0.0.0.0:8081
      healthcheck:
        test: curl -f http://localhost:8081/subjects
        interval: 10s
        timeout: 5s
        retries: 5
  
  knep:
    image: kong/kong-native-event-proxy:latest
    container_name: knep
    ports:
      - "8080:8080"
      - "19092:19092"
    env_file: "knep.env"
    environment:
      KONNECT_API_TOKEN: ${KONNECT_TOKEN}
      KONNECT_API_HOSTNAME: us.api.konghq.com
      KONNECT_CONTROL_PLANE_ID: ${KONNECT_CONTROL_PLANE_ID}
      KNEP__RUNTIME__DRAIN_DURATION: 1s # makes shutdown quicker, not recommended to be set like this in production 
      # KNEP__OBSERVABILITY__LOG_FLAGS: "info,knep=debug" # Uncomment for debug logging
    healthcheck:
      test: curl -f http://localhost:8080/health/probes/liveness
      interval: 10s
      timeout: 5s
      retries: 5
  
  kafka-ui:
    image: provectuslabs/kafka-ui:latest
    container_name: kafka-ui
    environment:
      # First cluster configuration (direct Kafka connection)
      KAFKA_CLUSTERS_0_NAME: "direct-kafka-cluster"
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: "kafka:9092"
      KAFKA_CLUSTERS_0_SCHEMAREGISTRY: "http://schema-registry:8081"
      # Second cluster configuration (KNEP proxy connection)
      KAFKA_CLUSTERS_1_NAME: "knep-proxy-cluster"
      KAFKA_CLUSTERS_1_BOOTSTRAPSERVERS: "knep:9092"
      KAFKA_CLUSTERS_1_SCHEMAREGISTRY: "http://schema-registry:8081"
      
      SERVER_PORT: 8082
    ports:
      - "8082:8082"
EOF
 
        
        
        
     
 
Note that the above config publishes the following ports to the host:
  - 
kafka:9092 for plaintext auth 
  - 
kafka:9094 for SASL username/password auth 
  - 
kafka-ui:8082 for access to the Kafka UI 
  - 
schema-registry:8081 for access to the schema registry 
  - 
knep:9192 to knep:9292 for access to the KNEP proxy (the port range is wide to allow many virtual clusters to be created) 
  - 
knep:8080 for probes and metrics access to KNEP 
The KNEP container will use environment variables from knep.env file. Let’s create it:
    
    
        cat <<EOF > knep.env
KONNECT_API_TOKEN=\${KONNECT_TOKEN}
KONNECT_API_HOSTNAME=us.api.konghq.com
KONNECT_CONTROL_PLANE_ID=\${KONNECT_CONTROL_PLANE_ID}
EOF
 
        
        
        
     
 
Now let’s start the local setup:
Let’s look at the logs of the KNEP container to see if it started correctly:
You should see something like this:
    
    
        knep  | 2025-04-30T08:59:58.004076Z  WARN tokio-runtime-worker ThreadId(09) add_task{task_id="konnect_watch_config"}:task_run:check_dataplane_config{cp_config_url="/v2/control-planes/c6d325ec-0bd6-4fbc-b2c1-6a56c0a3edb0/declarative-config/native-event-proxy"}: knep::konnect: src/konnect/mod.rs:218: Konnect API returned 404, is the control plane ID correct?
 
        
     
 
This is expected, as we haven’t configured the Control Plane yet. We’ll do this in the next step.