A small end-to-end walkthrough: a "production" ClickHouse running in Docker, seeded with a sample schema, that you can use to try out chsync snapshot, chsync diff, and chsync snapshot --log (the changelog of schema changes over time).
chsyncinstalled (go install github.com/anytoe/chsync@latestorgo buildfrom the repo root)- Docker
- Ports
8123and9000free on localhost
cd examples
docker compose up -dThis starts a container named chsync-example-production-server and auto-seeds it with initial-schema.sql (database shop with tables users and orders).
Wait a few seconds for the server to be ready, then export the DSN used in the rest of the walkthrough:
export PROD="http://default:default_password@localhost:8123"Sanity check:
curl -s "$PROD" --data-binary "SHOW TABLES FROM shop"
# orders
# usersCapture the current schema and start a changelog directory:
chsync snapshot --from "$PROD" --out clickhouse.schema.sql --log ./changelogThis produces:
clickhouse.schema.sql— the current schema, committed alongside your codechangelog/<timestamp>.sql— on the first run there is no prior snapshot to diff against, so the full schema is written as the initial entry
ls changelog/
# 2026-05-10T120000.sqlPretend an analyst added a column and dropped another directly against production:
docker compose exec production-clickhouse clickhouse-client \
--user default --password default_password \
--query "ALTER TABLE shop.orders ADD COLUMN status String DEFAULT 'pending'"
docker compose exec production-clickhouse clickhouse-client \
--user default --password default_password \
--query "ALTER TABLE shop.users DROP COLUMN email"chsync diff --from clickhouse.schema.sql --to "$PROD" --out drift.sql
cat drift.sqldrift.sql is the migration that would bring production back in line with the committed clickhouse.schema.sql.
Run the same snapshot command as in step 2:
chsync snapshot --from "$PROD" --out clickhouse.schema.sql --log ./changelogThis time the existing clickhouse.schema.sql is loaded into a temporary Docker container and diffed against the new schema. The drift from step 3 is captured as a new timestamped entry in changelog/:
ls changelog/
# 2026-05-10T120000.sql ← initial entry from step 2
# 2026-05-10T121530.sql ← migration capturing the ALTER from step 3
cat changelog/2026-05-10T121530.sql
# -- Add column status to shop.orders
# ALTER TABLE `shop`.`orders` ADD COLUMN `status` String DEFAULT 'pending';
# -- Drop column email from shop.users
# ALTER TABLE `shop`.`users` DROP COLUMN `email`;The committed file clickhouse.schema.sql now contains the latest schema, and changelog/ records every change between snapshots — commit both alongside your code to get a free, version-controlled history of how production evolved.
If a re-snapshot finds no drift, no changelog entry is written.
docker compose down -v
rm -rf clickhouse.schema.sql drift.sql changelog/| File | Purpose |
|---|---|
docker-compose.yml |
Spins up the production ClickHouse container |
initial-schema.sql |
Seeded into the container at startup |
clickhouse.schema.sql |
Generated by chsync snapshot (gitignored) |
drift.sql |
Generated by chsync diff (gitignored) |
changelog/ |
Generated by chsync snapshot --log — timestamped migration entries (gitignored) |