Skip to main content

Migrate from Karapace to Kora

This guide walks you through migrating all schemas, subjects, versions, and compatibility configurations from a Karapace schema registry into Kora. The migration preserves original Karapace schema IDs exactly — your producers and consumers do not need to be reconfigured. The process runs in four sequential phases: audit, dry-run, migrate, verify.

Prerequisites

Tools

uv manages the Python virtual environment and dependencies (fastavro, psycopg2-binary) automatically on first run. You do not need to run pip install manually.

Access requirements

Before starting, make sure you have:
  • HTTP/HTTPS access to your Karapace instance (for the audit and to resolve any collisions)
  • Direct PostgreSQL access to your Kora database (TCP, port 5432 by default) — required for the migration step
  • HTTP/HTTPS access to your Kora instance (for the verification step)

Kora database must be empty

Warning: migrate-run requires a completely empty Kora database (zero rows in schema_contents). It will refuse to run and exit with an error if any schemas already exist. Run the migration on a fresh Kora instance only.

Configuration

All scripts read connection details from environment variables. The justfile loads a .env file from the project root automatically (set dotenv-load), so the recommended approach is to create a .env file once and reuse it across all phases.

Setting up .env

Create a .env file in the root of this repository:
Note: If your Karapace or Kora instance does not require authentication, omit the *_USER and *_PASSWORD variables entirely.

Step 1 — Audit Karapace

This connects to your Karapace instance and fetches every subject, version, schema, and per-subject compatibility configuration — including soft-deleted subjects and versions. It writes the result to a timestamped JSON file under migration/audits/. Fetching is parallelised across subjects (20 concurrent workers by default), so auditing a large registry typically completes in seconds. Example output:
The audit also prints a JSON summary to stdout:
Keep this file — it is the source of truth for all subsequent phases.

Step 2 — Check for dedup collisions

Before moving to the dry run, look at dedup_collision_count in the audit summary.

What is a dedup collision?

Karapace assigns a new schema ID every time a schema is registered, even if the content is byte-for-byte identical to an existing schema. Kora uses content-based deduplication (via schema fingerprinting), so two subjects sharing identical schema content would be collapsed into a single ID — breaking the ID-preservation guarantee. The audit script detects this automatically and reports it as a dedup collision.

If dedup_collision_count is 0

No action needed. Proceed to Step 3.

If dedup_collision_count is greater than 0

The migration will refuse to run until collisions are resolved. The audit JSON includes a dedup_collisions array identifying the conflicting schemas:
This means schema ID 12 and schema ID 47 in Karapace contain the same schema content. To resolve:
  1. Identify which subjects reference each conflicting ID (check the schemas_by_id[id].subject_versions array in the audit file).
  2. In Karapace, consolidate the affected subjects so they all reference the same schema ID — typically by re-registering one subject under the other’s schema version, then deleting the duplicate.
  3. Re-run just migrate-audit to produce a fresh snapshot.
  4. Confirm dedup_collision_count is now 0.
Note: If consolidating the subjects is not straightforward, contact Popsink support — they can advise on the safest resolution strategy for your topology.

Step 3 — Dry run

This reads the latest audit snapshot and prints every database operation that would be executed, without writing a single row. Use it to confirm counts and spot any obvious issues before touching the database. Example output:
Verify that the schema, subject, and version counts match your audit summary before proceeding.

Step 4 — Run the migration

This writes all migrated data directly into Kora’s PostgreSQL in a single transaction. If any step fails, the entire transaction is rolled back — your database is left unchanged. The migration runs five steps in order: Example output:

Step 5 — Verify

This connects to your live Kora instance (via HTTP, not directly to PostgreSQL) and runs three checks against the audit snapshot: Success output:
If any check fails, the script exits with a non-zero status and prints each failure — for example:
Do not route production traffic to Kora until migrate-verify passes with zero failures.

Environment variables reference


Troubleshooting

ERROR: N dedup collision(s) in audit

The migration script detected identical schema content assigned to different IDs in Karapace. See Step 2 — Check for dedup collisions for the resolution process.

ERROR: schema_contents is not empty (N row(s) exist)

The target Kora database already has schema data. This migration tool is designed for initial population only. If you need to re-run the migration, restore the database to a clean state first (e.g. drop and recreate the schema, then re-run Kora’s database migrations).

No audit file found in audits/

Either just migrate-audit has not been run yet, or AUDIT_FILE points to a path that does not exist. Run just migrate-audit first, or set AUDIT_FILE explicitly in your .env.

Connection refused / timeout on Karapace or Kora

Verify that:
  • The URL in KARAPACE_URL / KORA_URL is reachable from the machine running the migration
  • Any firewall or VPN rules allow outbound HTTP/HTTPS to those hosts
  • Credentials in *_USER / *_PASSWORD are correct (test with curl -u user:pass <url>/subjects)

Connection refused on Kora PostgreSQL

Verify that:
  • KORA_DB_URL uses the correct host, port, database name, and credentials
  • The PostgreSQL instance allows connections from your IP (check pg_hba.conf)
  • The database user has INSERT and UPDATE privileges on the Kora schema tables