Deployment examples for Conduktor Schema Registry Proxy: production, standalone topic-acl and read-only modes, Confluent Cloud and Keycloak integration, monitoring.
Ready-to-adapt deployment examples and operational guidance for Schema Registry Proxy (SRP). For the full list of configuration options, see the Schema Registry Proxy environment variables reference.All deployments require a Conduktor Scale or Gateway license in CDK_LICENSE and an explicit AUTH_PROVIDER — SRP refuses to start without either.
Reactive authorization (permissions managed in Console), JWT client authentication, HTTPS to clients, SASL/SSL to Kafka and mTLS to the backend registry. For the Self-service resources that authorize subjects for a service account, see grant schema permissions with an ApplicationInstance.
Standalone deployment with topic ACL authorization
No Console and no Conduktor-specific Kafka topics: SRP authorizes against the topic ACLs already defined in your Kafka cluster, through the same Kafka connection settings. The authenticated identity (here, the JWT claim) has to match the Kafka ACL principal name.
services: schema-registry-proxy: image: conduktor/conduktor-schema-registry-proxy:1.0.0 environment: PORT: "8080" CDK_LICENSE: ${CDK_LICENSE} # Kafka connection, used for ACL lookups via the Admin API KAFKA_BOOTSTRAP_SERVERS: kafka1:9093,kafka2:9093 KAFKA_SECURITY_PROTOCOL: SASL_SSL KAFKA_SASL_MECHANISM: SCRAM-SHA-512 KAFKA_SASL_JAAS_CONFIG: 'org.apache.kafka.common.security.scram.ScramLoginModule required username="${KAFKA_USER}" password="${KAFKA_PASSWORD}";' # Schema Registry backend CONFLUENT_SCHEMA_REGISTRY_URL: <YOUR_SCHEMA_REGISTRY_URL> # Client authentication AUTH_PROVIDER: jwt JWT_JWKS_URL: <YOUR_JWKS_ENDPOINT> JWT_SUBJECT_CLAIM_NAME: preferred_username # Authorization from Kafka topic ACLs AUTHORIZATION_MODE: topic-acl AUTHORIZATION_TOPIC_ACL_CACHE_TTL: 500ms AUTHORIZATION_TOPIC_ACL_LOOKUP_TIMEOUT: 5s # Observability (required for the Prometheus /metrics port) OTEL_EXPORTER_OTLP_ENDPOINT: <http://otel-collector:4317> ports: - "8080:8080" - "9464:9464"
In this mode Read requires a READ ACL on the subject’s topic and Write requires WRITE (ALL matches either). Write does not imply Read: a producer principal that also fetches schemas needs READ or ALL.Grant access with regular Kafka ACLs — nothing Conduktor-specific is involved. For example, to let the orders-app service account register and fetch schemas for every subject whose topic starts with orders:
Both LITERAL and PREFIXED patterns apply, and a matching DENY always wins. The grant takes effect within the ACL cache TTL (500ms by default) — a producer that was getting 403 switches to successful schema registrations without a proxy restart.
Standalone read-only deployment with Basic authentication
The smallest possible deployment: just the proxy, a mounted credentials file and the backend registry. Every authenticated read passes through; every write is rejected with 403. No Kafka connection and no OAuth provider are needed.
services: schema-registry-proxy: image: conduktor/conduktor-schema-registry-proxy:1.0.0 environment: PORT: "8080" CDK_LICENSE: ${CDK_LICENSE} # Schema Registry backend CONFLUENT_SCHEMA_REGISTRY_URL: <YOUR_SCHEMA_REGISTRY_URL> # Client authentication from a mounted credentials file AUTH_PROVIDER: basic BASIC_AUTH_LOCATION: /etc/srp/basic-auth.json BASIC_AUTH_RELOAD_INTERVAL: 30s # Reads pass through, writes return 403 AUTHORIZATION_MODE: read-only # Optional break-glass identity allowed to write through the proxy AUTHORIZATION_SUPER_USERS: schema-admin@example.com # Observability (required for the Prometheus /metrics port) OTEL_EXPORTER_OTLP_ENDPOINT: <http://otel-collector:4317> volumes: - ./basic-auth.json:/etc/srp/basic-auth.json:ro ports: - "8080:8080" - "9464:9464"
The credentials file holds bcrypt-hashed passwords and is hot-reloaded on change, so you can rotate credentials without a restart:
Mount choice decides whether live reload survives your editor. Bind-mounting the single file (as above) binds the container to that file’s inode: only in-place writes (cat > file, tee, Ansible copy and most CD tooling) are seen. Editors like vim, VS Code and IntelliJ, plus sed -i, mv and git checkout, replace the file with a new inode — the container keeps reading the copy it started with, and nothing after that reaches the proxy until a restart rebinds it. Mounting the parent directory instead (- ./conf:/etc/srp:ro) re-resolves the filename on every read and survives any editor, at the cost of exposing everything else in that directory to the proxy.
Generate a hash with htpasswd (from Apache httpd, also available as a container image):
htpasswd -nbB -C 12 consumer-app@example.com '<password>' | cut -d: -f2# or without a local install:docker run --rm httpd:alpine htpasswd -nbB -C 12 consumer-app@example.com '<password>'
Verify the behavior split: a super user may register a schema, while every other authenticated user can read but not write:
# super user registers a schema (200)curl -u schema-admin@example.com:<password> \ -X POST -H "Content-Type: application/vnd.schemaregistry.v1+json" \ --data '{"schema":"{\"type\":\"string\"}"}' \ http://localhost:8080/subjects/orders-value/versions# read-only user: read succeeds (200), the same write is denied (403)curl -u consumer-app@example.com:<password> \ http://localhost:8080/subjects/orders-value/versions/latest