DevReplicate
CORE + ENTERPRISE

Set up each platform

Choose the artifact and topology that fit your infrastructure. Core and Enterprise use the same configuration and replication workflow; Enterprise adds the API, metrics, backup, and download surfaces.

Native Linux packages Portable tarballs Local Docker images Source builds

Install the edition you need

Delivery PostgreSQL and MySQL products Redis product
Native package Debian/Ubuntu, RHEL/Rocky, openSUSE Leap, Amazon Linux 2023, and Alpine. Core uses the normal package name; Enterprise uses the -ee package from the licensed channel. Not currently documented as a published package. Build from source.
Portable tarball Debian/Ubuntu, EL9/EL8, SUSE 15, Amazon Linux 2023/2, Azure Linux 3, and Alpine. No root or package manager required. Build from source.
Docker Images are not published by CI. Build them locally from the corresponding project checkout.
Source make builds Core; make ENTERPRISE=1 builds Enterprise from an entitled checkout.
Release coordinates are supplied with the release or license. Replace <host>, <version>, and <platform-suffix> with the values from your release portal. Verify the detached signature and checksum before installation.

Native packages

DEBIAN / UBUNTU · CORE EXAMPLE
echo "deb [trusted=yes] https://<host>/api/packages/wgodden/debian jammy main" | \
  sudo tee /etc/apt/sources.list.d/patroni-anonymizer.list
sudo apt-get update
sudo apt-get install patroni-anonymizer

# MySQL/MariaDB/Percona:
sudo apt-get install mysql-anonymizer

# Enterprise uses the licensed channel and -ee suffix:
sudo apt-get install patroni-anonymizer-ee
Linux family Package command Important limit
Debian / Ubuntu apt-get install <product> Core and Enterprise release legs.
RHEL / Rocky EL9 dnf install <product> MySQL streaming is absent where Connector/C is older than 3.3.
openSUSE Leap 15 zypper install <product> MySQL streaming has the same Connector/C limit.
Amazon Linux 2023 dnf install <product> Use the release registry definition supplied with the artifact.
Alpine apk add <product> Uses the Alpine release repository.
EL8 Portable tarball Core only: available OpenSSL is too old for Enterprise.
Amazon Linux 2 / Azure Linux 3 Portable tarball No useful native registry delivery.

Portable tarball

NO ROOT REQUIRED
curl -fsSL -o product.tar.gz \
  https://<host>/wgodden/<product>/releases/download/v<version>/\
<product>-<version>-<platform-suffix>.tar.gz
tar -xzf product.tar.gz
./<product>-<version>-<platform-suffix>/<product> --help

Docker or source

LOCAL IMAGE BUILD
third_party/drcommon/tooling/packaging/docker/build-images.sh

docker run --rm \
  -v "$PWD/config.db:/etc/devreplicate/config.db:ro" \
  <product>:<version> replicate --config /etc/devreplicate/config.db

For source builds, install the listed development packages, then run make. The MySQL product needs MariaDB Connector/C, SQLite, OpenSSL, and Jansson. Redis needs SQLite, OpenSSL, Jansson, pkg-config, and a C11 compiler. PostgreSQL additionally needs libpq and the PostgreSQL client tools used by initial sync.

The five newer products

Build MongoDB, Kafka, SQL Server, Oracle and CouchDB products inside their development container with scripts/dev.sh make. A default make builds the core; make ENTERPRISE=1 builds Enterprise.

Product Build dependencies beyond SQLite, OpenSSL, Jansson and pkg-config
mongodb-anonymizer libmongoc and libbson 1.23 or later.
kafka-anonymizer librdkafka (2.0 in the container and in CI).
mssql-anonymizer FreeTDS (freetds-dev / libsybdb5) at build and run time.
oracle-anonymizer Nothing Oracle-specific to build — ODPI-C is vendored — but Oracle Instant Client (Basic or Basic Lite, 19c or later) must be present at run time, since libclntsh.so is loaded then.
couchdb-anonymizer libcurl (libcurl4-openssl-dev).

PostgreSQL / Patroni

Requirement What to configure
Version PostgreSQL 13 or later on source and replica.
Source wal_level=logical; the source role needs REPLICATION plus SELECT on synchronized tables.
Replica Create the database first. The role needs object-management rights and permission to set session_replication_role.
Tools pg_dump and pg_restore must be in PATH.
Discovery Use multiple Patroni REST URLs. For standalone PostgreSQL, set primary_dsn_override.
CREATE A PATRONI CONFIG
patroni-anonymizer init-config --config /etc/patroni-anonymizer/config.db
sqlite3 /etc/patroni-anonymizer/config.db <<'SQL'
INSERT INTO settings (key,value) VALUES
  ('patroni_urls','http://10.0.0.1:8008,http://10.0.0.2:8008'),
  ('replica_dsn','host=10.0.0.9 port=5432 dbname=appdb user=pa_repl'),
  ('slot_name','pa_slot'),
  ('hash_key_file','/run/secrets/devreplicate-hmac');
SQL
patroni-anonymizer init-sync --config /etc/patroni-anonymizer/config.db
patroni-anonymizer replicate --config /etc/patroni-anonymizer/config.db
Database-name rule: Patroni discovery expects source and replica to use the same database name. A full primary_dsn_override is the exception and carries its own dbname.

MySQL / MariaDB / Percona Server

Variant Minimum and required settings
MariaDB 10.11+. Binary log enabled, binlog_format=ROW, binlog_row_metadata=FULL.
MySQL 8.0+. The same binlog settings plus gtid_mode=ON. The source role also needs RELOAD for the brief snapshot lock.
Percona Server 8.0+. Uses the MySQL binlog, GTID, and client behavior.
HA source No built-in discovery. Point primary_dsn at ProxySQL or HAProxy that always fronts the current primary.
CREATE A MYSQL/MARIADB CONFIG
mysql-anonymizer init-config --config /etc/mysql-anonymizer/config.db
sqlite3 /etc/mysql-anonymizer/config.db <<'SQL'
INSERT INTO clusters (name,primary_dsn,replica_dsn,server_id,hash_key_file)
VALUES ('default',
  'host=10.0.0.1 port=3306 user=anon dbname=appdb ssl_ca=/etc/ssl/ca.pem',
  'host=10.0.0.9 port=3306 user=anon dbname=appdb_anon ssl_ca=/etc/ssl/ca.pem',
  4200,'/run/secrets/devreplicate-hmac');
SQL
mysql-anonymizer init-sync --config /etc/mysql-anonymizer/config.db
mysql-anonymizer replicate --config /etc/mysql-anonymizer/config.db

The replica database must already exist. Each cluster maps one source database to one replica database; the names may differ. Use a separate cluster row for each additional database. Production DSNs should set ssl_ca, or at minimum require_tls=1.

Redis / Valkey

Redis source topologies can be standalone, Sentinel, or Cluster. Targets can be standalone or Cluster. Redis Cluster uses database 0 only. Valkey 8.1 and later is the same product and the same binary: the handshake announces a 9.0.0 client so a Valkey 9 primary sends its own RDB format, and the parser picks its dialect from the format’s magic. Redis answers that announcement with an error and nothing changes.

Source Target Configuration
Standalone Standalone source_type=standalone, target_type=standalone.
Sentinel Standalone or Cluster List Sentinel endpoints and set sentinel_master_name. The master is resolved again after reconnects.
Cluster Standalone or Cluster List reachable nodes; CLUSTER SLOTS discovers shard masters. One stream is maintained per source shard.
Standalone / Sentinel Cluster Related multi-key writes must share a Redis hash tag or can fail with CROSSSLOT.
BUILD AND CONFIGURE REDIS
sudo apt-get install libsqlite3-dev libssl-dev libjansson-dev pkg-config
make
./bin/redis-anonymizer init-config /etc/redis-anonymizer/config.db
sqlite3 /etc/redis-anonymizer/config.db <<'SQL'
INSERT INTO settings(cluster_name,key,value) VALUES
 ('default','source_type','standalone'),
 ('default','source_urls','10.0.0.1:6379'),
 ('default','target_type','standalone'),
 ('default','target_urls','10.0.0.9:6379'),
 ('default','hash_key_file','/run/secrets/devreplicate-hmac');
SQL
./bin/redis-anonymizer init-sync /etc/redis-anonymizer/config.db --cluster default
./bin/redis-anonymizer replicate /etc/redis-anonymizer/config.db --cluster default

Sentinel source

TOPOLOGY OVERRIDE
INSERT INTO settings(cluster_name,key,value) VALUES
 ('default','source_type','sentinel'),
 ('default','source_urls','10.0.0.1:26379,10.0.0.2:26379'),
 ('default','sentinel_master_name','mymaster');

Cluster source and target

TOPOLOGY OVERRIDE
INSERT INTO settings(cluster_name,key,value) VALUES
 ('default','source_type','cluster'),
 ('default','source_urls','10.0.1.1:6379,10.0.1.2:6379'),
 ('default','target_type','cluster'),
 ('default','target_urls','10.0.2.1:6379,10.0.2.2:6379');
TLS is verified, never opportunistic. Set source_tls=1 or target_tls=1 and provide a CA file when the system trust store is not appropriate. Optional client certificate and key settings enable mTLS. A verification failure never falls back to plaintext.

MongoDB

Requirement What to configure
Source MongoDB 6.0 or later as a replica set or a sharded cluster — change streams need one, and a single-node mongod --replSet is enough. A standalone mongod is refused at init-sync with a message saying so. Tested against MongoDB 7.0 Community.
Target Any MongoDB 4.4 or later, standalone or replica set. It receives ordinary CRUD plus drop, renameCollection, dropDatabase and createIndexes.
Driver libmongoc 1.23 or later.
Settings source_uri and target_uri (they must differ), hash_key or hash_key_file, and optionally status_interval_ms, batch_size, copy_indexes and full_document.
Out of scope FerretDB, Amazon DocumentDB and Azure Cosmos DB’s MongoDB API. Their change-stream behaviour differs in resume tokens, fullDocument modes and DDL events, and this product does not paper over that.
CREATE A MONGODB CONFIG
mongodb-anonymizer init-config /etc/mongodb-anonymizer/config.db
sqlite3 /etc/mongodb-anonymizer/config.db <<'SQL'
INSERT INTO settings VALUES
  ('default','source_uri','mongodb://src1,src2,src3/?replicaSet=rs0&tls=true'),
  ('default','target_uri','mongodb://anon-target:27017'),
  ('default','hash_key_file','/run/secrets/devreplicate-hmac');
SQL
mongodb-anonymizer init-sync /etc/mongodb-anonymizer/config.db
mongodb-anonymizer replicate /etc/mongodb-anonymizer/config.db

init-sync takes the stream position first, then copies every user collection — every database but admin, local, config and __devreplicate, every collection but views and system.* — dropping the target’s copy first and recreating indexes. replicate then opens the deployment-wide change stream at that position. The state document lives on the target, in __devreplicate.state, so a target restored from a backup carries the checkpoint that matches it.

full_document decides where an update’s document comes from. The default updateLookup has the server fetch the document’s current state per update event. required uses the post-image the source recorded at write time, which needs changeStreamPreAndPostImages enabled on every collection — the stream fails on one where it is not.

Apache Kafka / Redpanda

Requirement What to configure
Source and target Apache Kafka 3.x in KRaft mode, or Redpanda 24 and later. ZooKeeper-mode Kafka is not supported.
Bootstrap source_bootstrap and target_bootstrap. They must differ: mirroring onto same-named topics of its own source would feed plaintext back into itself, and an identical string is refused.
Security Per side: <side>_security_protocol (plaintext, ssl, sasl_plaintext, sasl_ssl), SASL PLAIN / SCRAM-SHA-256 / SCRAM-SHA-512 / GSSAPI / OAUTHBEARER, a CA bundle, and a client certificate and key for mTLS.
Coverage policy unmatched_topic_policy is skip by default: a topic no rule matches is not mirrored at all. passthrough copies it byte-for-byte. Topics whose name starts with _ are never mirrored under either policy.
State state_topic, a compacted topic on the target, __devreplicate.state by default; it must start with _ and init-sync creates it.
CREATE A KAFKA CONFIG
kafka-anonymizer init-config /etc/kafka-anonymizer/config.db
sqlite3 /etc/kafka-anonymizer/config.db <<'SQL'
INSERT INTO settings(cluster_name,key,value) VALUES
 ('default','source_bootstrap','10.0.0.1:9092'),
 ('default','target_bootstrap','10.0.0.9:9092'),
 ('default','source_security_protocol','sasl_ssl'),
 ('default','source_sasl_mechanism','SCRAM-SHA-512'),
 ('default','source_sasl_username','devreplicate'),
 ('default','source_sasl_password_file','/run/secrets/kafka-source.pw'),
 ('default','hash_key_file','/run/secrets/devreplicate-hmac');
SQL
kafka-anonymizer init-sync /etc/kafka-anonymizer/config.db
kafka-anonymizer replicate /etc/kafka-anonymizer/config.db

init-sync creates each target topic with the source’s partition count, or empties an existing one with DeleteRecords, copies every record up to the high watermark it saw at the start, and records that watermark per partition. replicate starts at exactly that watermark, so nothing is copied twice and nothing between the two commands is skipped.

Partition counts and retention are checked, not assumed. A target topic whose partition count differs from the source’s is refused, and a source topic whose count grew since its init-sync stops the mirror until init-sync --topic re-baselines it. The source consumer runs with auto.offset.reset=error: if retention passed the recorded offset while the mirror was down, replicate stops rather than jumping silently to the earliest or latest offset.

Microsoft SQL Server

Requirement What to configure
Version SQL Server 2019 or 2022 on both sides; a ProductMajorVersion below 15 is refused.
Edition An edition with Change Data Capture on the source: Developer, Standard or Enterprise. Express has no SQL Server Agent and no CDC and is refused at init-sync. The target can be any edition.
Agent SQL Server Agent must be running on the source — CDC’s capture job is an Agent job. Without it sys.fn_cdc_get_max_lsn() stays NULL and init-sync gives up after 60 s saying so. In a container, MSSQL_AGENT_ENABLED=true.
Client FreeTDS at build and run time; the client speaks TDS 7.4 with a UTF-8 client charset.
Target The target database must already exist. init-sync creates and recreates schemas and tables inside it; it never creates the database.
Privileges The source login needs sysadmin for init-sync (sp_cdc_enable_db requires it), or db_owner where CDC is already enabled. The target login needs db_owner: the baseline drops and recreates tables and every write runs under SET IDENTITY_INSERT.
CREATE A SQL SERVER CONFIG
mssql-anonymizer init-config --config /etc/mssql-anonymizer/config.db
sqlite3 /etc/mssql-anonymizer/config.db <<'SQL'
INSERT INTO settings (key, value) VALUES
  ('source_host','prod-sql.internal'), ('source_port','1433'),
  ('source_user','devreplicate'), ('source_password_file','/run/secrets/mssql-source.pw'),
  ('source_database','app'),
  ('target_host','dev-sql.internal'), ('target_port','1433'),
  ('target_user','devreplicate'), ('target_password_file','/run/secrets/mssql-target.pw'),
  ('target_database','app_anon'),
  ('hash_key_file','/run/secrets/devreplicate-hmac');
SQL
mssql-anonymizer init-sync --config /etc/mssql-anonymizer/config.db
mssql-anonymizer replicate --config /etc/mssql-anonymizer/config.db

One source database maps to one target database; a second pair is a second configuration database today. replicate bounds each poll to batch_transactions source transactions, merges every tracked table’s changes into the source’s commit order, and applies them with the moved checkpoint in one target transaction. Latency is the capture job’s interval plus poll_interval_ms.

This tool changes the source. init-sync runs sp_cdc_enable_db and creates one capture instance, with its change table and capture and cleanup jobs, per table with a primary key. Only keyed tables are replicated by CDC — a heap is copied at init-sync and refreshed only by another one. The target carries no foreign keys, check constraints, defaults or triggers, and a unique index whose key includes an anonymized column is created non-unique. DDL on a tracked table stops replication, because CDC does not follow DDL: run init-sync again. Disable CDC with sys.sp_cdc_disable_db if you stop using the tool.

Oracle Database

Requirement What to configure
Version Oracle Database 19c, 21c, 23ai or 26ai on the source; the target can be any release that accepts the source’s DDL. Oracle Database Free works for both and includes LogMiner.
Supplemental logging ALTER DATABASE ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS; as SYSDBA in CDB$ROOT. Without it UPDATE and DELETE redo carries no key columns, and both init-sync and replicate check V$DATABASE and refuse to start.
ARCHIVELOG Required for anything beyond development. replicate mines the online logs and the archived logs, and refuses to continue past a gap, naming the SCN it needed. In NOARCHIVELOG mode a log switch that recycles unmined redo stops replication and needs a new init-sync.
Container database In a CDB, LogMiner runs in CDB$ROOT: connect to the root service as a common user (C##…) and name the pluggable database in source_pdb. On a non-CDB leave it empty.
Client Oracle Instant Client (Basic or Basic Lite, 19c or later) on the machine running the tool. ODPI-C is compiled into the binary and loads libclntsh.so at run time.
Target The target schemas must already exist, as users of the same names as the source owners, with quota. init-sync creates and replaces tables inside them, never the users.
CREATE AN ORACLE CONFIG
oracle-anonymizer init-config --config /etc/oracle-anonymizer/config.db
sqlite3 /etc/oracle-anonymizer/config.db <<'SQL'
INSERT INTO settings (key, value) VALUES
  ('hash_key_file','/run/secrets/devreplicate-hmac');
INSERT INTO clusters (name, source_connect, source_user, source_password_file, source_pdb,
                      target_connect, target_user, target_password_file, schemas) VALUES
  ('default','db1.example.org:1521/FREE','c##anon','/run/secrets/ora-source.pw','FREEPDB1',
   'db2.example.org:1521/ANONPDB','app','/run/secrets/ora-target.pw','APP, HR');
SQL
oracle-anonymizer init-sync --config /etc/oracle-anonymizer/config.db
oracle-anonymizer replicate --config /etc/oracle-anonymizer/config.db

schemas is the comma-separated list of owners to replicate, every table in each. init-sync copies each table AS OF SCN, recreates tables, then plain indexes and finally referential constraints, and records the snapshot SCN and the SCN mining starts from. replicate lists the redo logs covering that range, registers them with DBMS_LOGMNR.ADD_LOGFILE, buffers each transaction until its COMMIT, and applies it with its checkpoint in one target transaction.

Column names are matched exactly as the catalog spells them — upper case unless the object was created with a quoted identifier — and a rule that matches no column of any replicated table fails init-sync rather than protecting nothing quietly. DDL is not replayed: DDL on a replicated table stops replicate with the statement in the message, so apply it to the target by hand and re-run init-sync. Triggers and views are not recreated, and a table containing an object type, XMLTYPE, SDO_GEOMETRY, VECTOR or BFILE is refused.

CouchDB

Requirement What to configure
Source and target CouchDB 3.x on both sides. Everything used is stable API; _bulk_get with revs exists since 2.2, so 2.2+ may work, but only 3.x is tested. 1.x has no _bulk_get and is not supported.
Authentication HTTP Basic, through *_username and *_password or *_password_file, or credentials in the URL. The account needs _admin on the target (it creates and deletes databases) and read access to every source database plus /_all_dbs.
Transport http and https through libcurl with the system CA store; verification is always on. There are no client-certificate settings yet.
Separation target_url must name a different server from source_url unless target_database_prefix is set — otherwise the copy would overwrite the source, and validation refuses it.
Selection database_pattern is a glob over source database names (default *); discovery_interval_ms is how often replicate re-lists /_all_dbs for new databases.
CREATE A COUCHDB CONFIG
couchdb-anonymizer init-config /etc/couchdb-anonymizer/config.db
sqlite3 /etc/couchdb-anonymizer/config.db <<'SQL'
INSERT INTO settings(cluster_name,key,value) VALUES
 ('default','source_url','https://couch-prod.internal:6984'),
 ('default','target_url','https://couch-dev.internal:6984'),
 ('default','source_username','devreplicate'),
 ('default','source_password_file','/run/secrets/couch-source.pw'),
 ('default','target_username','devreplicate'),
 ('default','target_password_file','/run/secrets/couch-target.pw'),
 ('default','database_pattern','app*'),
 ('default','hash_key_file','/run/secrets/devreplicate-hmac');
SQL
couchdb-anonymizer init-sync /etc/couchdb-anonymizer/config.db
couchdb-anonymizer replicate /etc/couchdb-anonymizer/config.db

This never uses CouchDB’s own replicator, which copies documents byte-for-byte — exactly the plaintext transfer the tool exists to prevent. Every document crosses through the process instead. init-sync reads update_seq first, deletes and recreates the target database, pages _all_docs, and writes each page with _bulk_docs and new_edits=false. replicate follows a continuous _changes feed, fetches each batch with _bulk_get?revs=true, and checkpoints the batch’s last sequence in _local/devreplicate-state on the target.

The target is owned by this tool, and its revision hashes no longer describe its content. init-sync deletes and recreates every target database before copying, so never point target_url at a server holding anything you want to keep under those names. _id and _rev and the whole revision tree are the source’s, which is what makes a replay after a crash a no-op — but 2-abc on the target holds the anonymized body. Attachments are copied verbatim, and _users, _replicator, _global_changes and any other _-prefixed database are never replicated.

Run the system continuously

Create the configuration database

Run init-config, store it with restrictive permissions, and place HMAC key material in a protected file or secret mechanism.

Define every sensitive field or key

Add SQL anon_field rows or Redis anon_rule rows before the first copy. Continue with the anonymization guide.

Run the initial sync

This creates the baseline target and applies configured transformations before values are written there.

Supervise replication

Run replicate as a long-lived service under systemd, your orchestrator, or another supervisor.

Backfill or re-baseline policy additions

A rule added after initial sync is pending. In PostgreSQL, MySQL and Redis, run the core backfill workflow; MongoDB Enterprise provides its own document backfill. Kafka resyncs affected topics, while SQL Server, Oracle and CouchDB re-run init-sync for affected data. Either way, do not share the target until pending fields or rules are zero.