How to Deploy Apicurio v3 Alongside v2

This guide shows you how to prepare the Apicurio Registry v3 values, deploy v3 next to your running v2 on a temporary hostname, and check that v3 is up and empty.

Type

How-to guide

Goal

Run an empty Apicurio Registry v3 next to v2 on a temporary hostname, with no change for clients.

Audience

Platform Operator with Helm access to the Axual Streaming release in the kafka namespace.

When to use

Use this guide as the first stage of Upgrade Apicurio Registry from v2 to v3, once Axual Streaming runs 2.0.0.

Prerequisites

Confirm the following before you begin.

Access and permissions required

You need the following access and permissions:

  • Helm access to the Axual Streaming release in the kafka namespace.

  • The client secret of the platform’s apicurio-api Keycloak client, for the verification step.

Tools and versions required

You need the following tools and versions:

  • helm and kubectl

  • curl and jq

  • Axual Streaming Charts 2.0.0

Resources that must exist before starting

The following resources must already exist before you start:

  • Axual Streaming upgraded to 2.0.0, with your existing Apicurio v2 still running unchanged under its apicurio-registry key. See Upgrade Axual Streaming to 2.0.0.

Replace every <VALUE> placeholder and every example.com hostname with your actual value before running a command. [same as configured for your apicurio-registry] means copy the value from the matching key of your existing apicurio-registry block.

Step 1: Prepare the Apicurio v3 values

Your Apicurio v2 keeps running unchanged under its apicurio-registry: block. Add a new apicurio-registry-v3: block alongside it by working through the sections below.

Don’t pin image.tag in any of the blocks below. The chart already pins the correct Apicurio version.

1.1 Enable Apicurio v3 and disable the bundled Keycloak

Enable Apicurio v3 in the global block. Disable the bundled Keycloak instances, because v3 reuses the existing v2 Keycloak throughout the upgrade.

global:
  apicurio:
    enabled: true              # your existing Apicurio v2, still running
  apicurio-registry-v3:
    enabled: true              # enable Apicurio v3 alongside v2

axual-streaming:
  apicurio-registry-v3:
    imagePullSecrets:
      - name: [same as configured for your apicurio-registry]

    apicurioKeycloak:
      enabled: false   # reuse the v2 Keycloak, don't deploy a new one
    apicurioKeycloakMysql:
      enabled: false

1.2 Set the UI URL

Set the public HTTPS URL the browser uses to reach the v3 API. Use a temporary hostname for now; you switch to the production hostname in Step 6: Switch ingress to v3.

axual-streaming:
  apicurio-registry-v3:
    ui:
      config:
        registryApiUrl: "https://apicurio-v3.example.com/apis/registry/v3"
registryApiUrl must be publicly reachable from the browser. Don’t use localhost.

1.3 Configure the Kafka connection and topics

Set the bootstrap servers and the three v3 topic names. Replace <tenant> and <instance> with the values for your environment.

axual-streaming:
  apicurio-registry-v3:
    kafka:
      bootstrapServers: [same as configured for your apicurio-registry]
      journalTopic: "_<tenant>-<instance>-kafkasql-journal"
      snapshotsTopic: "_<tenant>-<instance>-kafkasql-snapshots"
      eventsTopic: "_<tenant>-<instance>-registry-events"

1.4 Configure TLS

Reuse the same TLS secret names as your existing Apicurio v2 deployment.

axual-streaming:
  apicurio-registry-v3:
    tls:
      clientKeypairSecretName: [same as configured for your apicurio-registry]
      serverKeypairSecretName: [same as configured for your apicurio-registry]
      truststoreCaSecretName: [same as configured for your apicurio-registry]

1.5 Configure the Kafka init container

The init container creates the three Kafka topics and the ACLs (Access Control Lists) for the Apicurio principal. Reuse the same broker TLS secret names as your Apicurio v2 deployment.

axual-streaming:
  apicurio-registry-v3:
    kafkaInitContainer:
      apicurioPrincipal: [same as configured for your apicurio-registry]
      replicationFactor: [same as configured for your apicurio-registry]
      minIsr: [same as configured for your apicurio-registry]
      tls:
        keypairSecretName: [same as configured for your apicurio-registry]
        keypairSecretKeyName: [same as configured for your apicurio-registry]
        keypairSecretCertName: [same as configured for your apicurio-registry]
        truststoreCaSecretName: [same as configured for your apicurio-registry]
        truststoreCaSecretCertName: [same as configured for your apicurio-registry]

1.6 Configure security

Copy the security block from your v2 values as-is. The only value that must differ is webRedirectUrl: point it to the temporary v3 hostname for now, and update it to the production hostname in Step 6: Switch ingress to v3.

axual-streaming:
  apicurio-registry-v3:
    security:
      authentication:
        enabled: true
        basicAuthEnabled: true
        keycloak:
          authUrl: [same as configured for your apicurio-registry]
          realm: [same as configured for your apicurio-registry]
          webClientId: [same as configured for your apicurio-registry]
          webRedirectUrl: "https://apicurio-v3.example.com"   # point to v3, not v2

1.7 Configure the backend and UI ingresses

Where v2 had a single ingress block with a hosts array, v3 splits it into ingress.backend (the API) and ingress.ui (the web console). Both blocks accept the same className, annotations and tls fields you already have on the v2 ingress, and both route to the same Apicurio Service, typically <release-name>-apicurio-registry-v3, on different ports and paths:

Ingress (values key) Service name Service port Path

ingress.backend

<release-name>-apicurio-registry-v3

20500 (http)

/apis

ingress.ui

<release-name>-apicurio-registry-v3

20501 (ui-http)

/

If you front Apicurio with an external ingress or gateway rather than the chart’s ingress resources, see the Ingress routing reference for the full service, port and path mapping, including the Auth Proxy.

Use a temporary hostname here; you cut over to the production hostname in Step 6: Switch ingress to v3.

axual-streaming:
  apicurio-registry-v3:
    ingress:
      backend:
        enabled: true
        className: [same as configured for your apicurio-registry]
        annotations: [same as configured for your apicurio-registry]
        host: "apicurio-v3.example.com"
        tls:
          - secretName: [same as configured for your apicurio-registry]
            hosts:
              - "apicurio-v3.example.com"
      ui:
        enabled: true
        className: [same as configured for your apicurio-registry]
        annotations: [same as configured for your apicurio-registry]
        host: "apicurio-v3.example.com"
        tls:
          - secretName: [same as configured for your apicurio-registry]
            hosts:
              - "apicurio-v3.example.com"
Put the external-dns annotations on backend only. Backend and UI share the same hostname, so a single DNS registration is enough.

1.8 Configure the Auth Proxy (optional)

Skip this section if you don’t use the Auth Proxy sidecar. The Auth Proxy is disabled by default (authProxy.enabled: false); when enabled, it runs as a sidecar and validates JWT tokens before forwarding requests to the Apicurio backend.

If you’re adapting your v2 authProxy values, update these three fields. The v3 backend listens on 8081, the port the v2 proxy used, so the proxy moves to 8082:

Value v2 v3

authProxy.port

8081

8082

authProxy.config.server.port

8081

8082

authProxy.config.auth-proxy.backend-service

http://localhost:8080

http://localhost:8081

The Auth Proxy ingress can’t share a hostname and path with ingress.backend or ingress.ui, because Nginx rejects duplicate rules. Give the Auth Proxy its own hostname and set ui.config.registryApiUrl (section 1.2) to the direct ingress.backend hostname, so the UI, which authenticates through Apicurio Keycloak rather than the Auth Proxy, bypasses it. v3 removes ingressWithoutAuthProxy; the split ingress.backend and ingress.ui blocks cover the same use case.

axual-streaming:
  apicurio-registry-v3:
    authProxy:
      enabled: true
      secrets:
        auth-proxy:
          client-secret-salt: <CLIENT_SECRET_SALT>
          clientSecretAlgorithm: HmacSHA256
      config:
        auth-proxy:
          valid-issuer-uri: [same as configured for your apicurio-registry]
          jwks-endpoint-uri: [same as configured for your apicurio-registry]
          client-id: [same as configured for your apicurio-registry]
          check-audience: [same as configured for your apicurio-registry]
      ingress:
        enabled: true
        className: [same as configured for your apicurio-registry]
        annotations: [same as configured for your apicurio-registry]
        hosts:
          - host: "apicurio-auth-proxy.example.com"
            paths:
              - path: "/apis"
                pathType: "Prefix"
        tls:
          - secretName: "<same TLS secret as your other ingresses>"
            hosts:
              - "apicurio-auth-proxy.example.com"

Step 2: Deploy Apicurio v3

Apply the updated values with a Helm upgrade of your existing Streaming release:

helm upgrade --install streaming oci://registry.axual.io/axual-charts/axual-streaming --version=2.0.0 -n kafka -f values.yaml

Only the v3 resources are added. The v2 registry keeps serving clients on its current hostname.

Step 3: Verify Apicurio v3 is running

Check that the v3 pods are ready, then confirm the v3 API answers on the temporary hostname and holds no artifacts yet.

  1. List the v3 pods:

    kubectl get pods -n kafka | grep apicurio-registry-v3

    Every pod shows Running, and its READY column shows all containers ready.

  2. Get a token for the platform’s apicurio-api client from the existing v2 Keycloak, and count the artifacts in v3:

    TOKEN=$(curl -skS --fail-with-body -X POST \
      "https://<APICURIO_KEYCLOAK_HOST>/auth/realms/<APICURIO_REALM>/protocol/openid-connect/token" \
      -d "grant_type=client_credentials&client_id=apicurio-api&client_secret=<APICURIO_API_CLIENT_SECRET>" \
      | jq -r '.access_token')
    
    curl -skS --fail-with-body \
      "https://apicurio-v3.example.com/apis/registry/v3/search/artifacts?limit=1" \
      -H "Authorization: Bearer $TOKEN" | jq '.count'

    The command prints 0. If it prints a higher number, a previous attempt left data behind; empty v3 with Restore the Apicurio v3 topics before you continue.

  3. Open https://apicurio-v3.example.com in a browser and log in with an sr-admin user. The v3 web console loads with an empty artifact list.

Next steps