This directory contains the Docker pieces used by the demos, the integration-test fixtures, and the packaged example deployment.
These are example assets — they are not a production deployment blueprint.
compose.yml is the base local database stack used by the shipped demos and
by the Docker-backed integration / end-to-end tests.
Current images:
- MySQL 8.4
- PostgreSQL 18
Start the databases only:
docker compose -f ops/docker/compose.yml up -dThe shipped fixture compose publishes MySQL and PostgreSQL on 127.0.0.1
only, so the disposable demo credentials are not exposed on non-loopback host
interfaces by default.
Stop and remove them:
docker compose -f ops/docker/compose.yml down -vNote on MySQL auth. The fixture starts MySQL 8.4 with
--mysql-native-password=ONand creates thedbloguser viaIDENTIFIED WITH mysql_native_password. This keeps the deprecated auth plugin available for compatibility with the binlog tooling used by the integration tests. MySQL 9.x removes the plugin entirely — if you bump the image, switchops/docker/mysql/init/01-create-users.sqltocaching_sha2_passwordand drop the server flag.
The recommended evaluation flow is to keep the databases in Docker and run
DBLog itself on the host via ./gradlew bootRun.
Example application properties:
examples/local/mysql-to-ndjson/application.propertiesexamples/local/mysql-to-postgres/application.propertiesexamples/local/postgres-to-mysql/application.properties
Launch one of them:
./gradlew bootRun --args="--spring.config.additional-location=file:./ops/docker/examples/local/mysql-to-postgres/application.properties"The control plane binds 127.0.0.1 in all host-run examples. Remote access
is out of scope.
The host-run examples persist H2 state under build/example-state/.... If you
recreate the MySQL or PostgreSQL fixture containers from scratch, first clear
the matching host-run state so DBLog does not resume from a stale checkpoint
against a fresh source history:
rm -f build/example-state/mysql-to-postgres/runtime-state.mv.db \
build/example-state/mysql-to-postgres/runtime-state.trace.db \
build/example-state/mysql-to-ndjson/runtime-state.mv.db \
build/example-state/mysql-to-ndjson/runtime-state.trace.db \
build/example-state/mysql-to-ndjson/events.ndjson \
build/example-state/postgres-to-mysql/runtime-state.mv.db \
build/example-state/postgres-to-mysql/runtime-state.trace.dbcompose.runtime.yml overlays a containerized DBLog built from the root
Dockerfile on top of the database fixtures. It demonstrates the
MySQL -> DBLog -> PostgreSQL path in a single docker compose command.
Start the packaged example:
docker compose -f ops/docker/compose.yml -f ops/docker/compose.runtime.yml up -d --buildStart the packaged example with the additional NDJSON inspection sink:
docker compose \
-f ops/docker/compose.yml \
-f ops/docker/compose.mysql-to-postgres-with-ndjson.yml \
up -d --buildThat variant uses examples/mysql-to-postgres-with-ndjson/application.properties
and writes recent emitted events to
ops/docker/example-state/mysql-to-postgres-with-ndjson/mysql-to-postgres-events.ndjson
on the host.
End-to-end packaged proof:
docker compose -f ops/docker/compose.yml -f ops/docker/compose.runtime.yml down -v
rm -f ops/docker/example-state/mysql-to-postgres/runtime-state.mv.db \
ops/docker/example-state/mysql-to-postgres/runtime-state.trace.db \
ops/docker/example-state/mysql-to-postgres/mysql-to-postgres-events.ndjson
docker compose -f ops/docker/compose.yml up -d
until docker compose -f ops/docker/compose.yml exec -T mysql mysqladmin ping -uroot -proot --silent; do sleep 1; done
until docker compose -f ops/docker/compose.yml exec -T postgres pg_isready -U postgres -d app; do sleep 1; done
docker compose -f ops/docker/compose.yml exec -T mysql \
mysql -udblog -pdblog -e "USE app; TRUNCATE TABLE sample_orders; INSERT INTO sample_orders (id, customer_name, status) VALUES (1, 'packaged-one', 'PENDING'), (2, 'packaged-two', 'READY');"
docker compose -f ops/docker/compose.yml exec -T postgres \
psql -U postgres -d app -c "TRUNCATE TABLE app.sample_orders;"
docker compose -f ops/docker/compose.yml -f ops/docker/compose.runtime.yml up -d --build
until curl -fsS http://127.0.0.1:8085/api/v1/runtime/status; do sleep 1; done
curl -sS -X POST http://127.0.0.1:8085/api/v1/requests \
-H 'Content-Type: application/json' \
-d '{"scope":"ALL_TABLES"}'
until [ "$(docker compose -f ops/docker/compose.yml exec -T postgres psql -U postgres -d app -tAc "SELECT COUNT(*) FROM app.sample_orders")" = "2" ]; do sleep 1; done
docker compose -f ops/docker/compose.yml exec -T mysql \
mysql -udblog -pdblog -e "USE app; UPDATE sample_orders SET status='SYNCED' WHERE id=1; INSERT INTO sample_orders (id, customer_name, status) VALUES (3, 'packaged-three', 'NEW'); DELETE FROM sample_orders WHERE id=2;"
until [ "$(docker compose -f ops/docker/compose.yml exec -T postgres psql -U postgres -d app -tAc "SELECT status FROM app.sample_orders WHERE id=1")" = "SYNCED" ]; do sleep 1; done
until [ "$(docker compose -f ops/docker/compose.yml exec -T postgres psql -U postgres -d app -tAc "SELECT COUNT(*) FROM app.sample_orders WHERE id=3")" = "1" ]; do sleep 1; done
until [ "$(docker compose -f ops/docker/compose.yml exec -T postgres psql -U postgres -d app -tAc "SELECT COUNT(*) FROM app.sample_orders WHERE id=2")" = "0" ]; do sleep 1; done
docker compose -f ops/docker/compose.yml exec -T postgres \
psql -U postgres -d app -c "TABLE app.sample_orders;"That proves the packaged container boots, accepts an ALL_TABLES request, copies
the initial MySQL rows into PostgreSQL, and then applies later live MySQL
changes into PostgreSQL.
Stop and remove it:
docker compose -f ops/docker/compose.yml -f ops/docker/compose.runtime.yml down -vIf you recreate the MySQL or PostgreSQL fixture containers from scratch, first clear the bind-mounted runtime state so DBLog does not resume from a stale checkpoint against a fresh source history:
rm -f ops/docker/example-state/mysql-to-postgres/runtime-state.mv.db \
ops/docker/example-state/mysql-to-postgres/runtime-state.trace.db \
ops/docker/example-state/mysql-to-postgres/mysql-to-postgres-events.ndjson \
ops/docker/example-state/mysql-to-postgres-with-ndjson/runtime-state.mv.db \
ops/docker/example-state/mysql-to-postgres-with-ndjson/runtime-state.trace.db \
ops/docker/example-state/mysql-to-postgres-with-ndjson/mysql-to-postgres-events.ndjsonInside the container DBLog binds 0.0.0.0:8085 so Docker port publishing
works; the overlay publishes it on 127.0.0.1:8085 on the host.
Inspect the control plane:
curl -sS http://127.0.0.1:8085/api/v1/runtime/statusRun the packaged MySQL startup-check example:
docker compose \
-f ops/docker/compose.yml \
-f ops/docker/compose.mysql-startup-check.yml \
up --build --abort-on-container-exit --exit-code-from dblog dblogThis uses examples/mysql-startup-check/application.properties and validates
the fixture-backed MySQL source plus PostgreSQL target preflight without
launching the runtime request loop.
Run the packaged PostgreSQL startup-check example:
docker compose \
-f ops/docker/compose.yml \
-f ops/docker/compose.postgres-startup-check.yml \
up --build --abort-on-container-exit --exit-code-from dblog dblogThis uses examples/postgres-startup-check/application.properties and verifies
the fixture-backed PostgreSQL source contract with the dedicated dblog role,
including replica identity, publication, and logical-slot readiness. It uses an
explicit no-op sink because startup-check does not launch the runtime request
loop or emit change events.
The packaged Dockerfile sets production-oriented JVM defaults via
JAVA_TOOL_OPTIONS (G1GC, container-aware memory sizing, heap-dump on OOM,
rolling JFR to the mounted state directory). Override by providing your own
JAVA_TOOL_OPTIONS in a compose override file. The host-run ./gradlew bootRun path does not apply these flags automatically.
| Path | Purpose |
|---|---|
compose.yml |
MySQL and PostgreSQL fixture services |
compose.runtime.yml |
Packaged DBLog container overlay |
compose.mysql-to-postgres-with-ndjson.yml |
Packaged runtime overlay with NDJSON inspection sink |
compose.mysql-startup-check.yml |
Packaged MySQL startup-check override |
compose.postgres-startup-check.yml |
Packaged PostgreSQL startup-check override |
examples/local/ |
Host-run application.properties templates |
examples/mysql-to-postgres/ |
Packaged-deploy application.properties |
examples/mysql-to-postgres-with-ndjson/ |
Packaged-deploy with NDJSON sink |
examples/mysql-startup-check/ |
Packaged-deploy in startup-check boot mode |
examples/postgres-startup-check/ |
Packaged PostgreSQL startup-check application.properties |
example-state/mysql-to-postgres/ |
Bind-mount target for the packaged example's H2 state |
example-state/mysql-to-postgres-with-ndjson/ |
Bind-mount target for packaged NDJSON state and events |
example-state/mysql-startup-check/ |
Bind-mount target for MySQL startup-check state |
example-state/postgres-startup-check/ |
Bind-mount target for PostgreSQL startup-check state |
mysql/init/ |
MySQL initialization SQL |
postgres/init/ |
PostgreSQL initialization SQL |