developer tool
Automation
pgsync
A deliberately small wrapper around pg_dump piped to psql — rsync-shaped flags, safer defaults, a GHCR Docker image, and CI from ShellCheck through Bats integration tests.
Bash · PostgreSQL · Docker · GitHub Actions
A deliberately boring tool.
Copying a database between two Postgres instances is easy to get wrong in a hurry: dump flags, ON_ERROR_STOP, and whether logs contaminate the dump stream.
The problem did not need a service, a framework, or a container.
Why a Bash script?
PostgreSQL already provides the primitive:
pg_dump | psql
pgsync wraps that primitive with safer defaults, URI source/target, bash completion, and a few flags people already know from rsync.
The interesting decision was knowing what not to build. There is no incremental mode. This is always a full logical dump/restore.
The pipe is the interface
All status goes to stderr. stdout is reserved for the dump. A dry-run prints the quoted pipeline and exits.
psql runs with ON_ERROR_STOP=1. Unless --no-clean, dump uses --clean --if-exists. Every dump uses --no-owner --no-privileges so restores do not drag source roles onto the target.
--delete is a separate, explicit pre-step: DROP SCHEMA public CASCADE then CREATE SCHEMA public.
rsync-shaped CLI
| Flag | Behavior |
|---|---|
-n |
Print the pipeline; do not run it |
--delete |
Recreate public on the target first |
-q / -v |
Quiet / show the exact command |
--schema-only / --data-only |
Narrow the dump |
Completion lives in completions/pgsync.bash and does not require bash-completion.deb. install.sh places the binary and the completion file under a configurable prefix.
Docker and GHCR
The same script runs in a container built on postgres:16-bookworm, so callers do not need local pg_dump / psql:
docker run --rm ghcr.io/mamahoos/pgsync:latest \
-s 'postgresql://user:pass@source:5432/dbname' \
-t 'postgresql://user:pass@target:5432/dbname'
Every version tag publishes to GHCR with semver and latest. docker-compose.yml runs a two-Postgres smoke test — the same stack the publish workflow exercises before push.
CI
GitHub Actions splits concerns instead of one catch-all job:
| Workflow | Trigger | What it proves |
|---|---|---|
test.yml |
push / PR | ShellCheck on the scripts, Bats unit tests, then integration sync against two Postgres 16 services |
docker-publish.yml |
version tag | image build, compose smoke test, push to GHCR |
Integration tests use real URIs and assert quiet mode, data-only restores, and the full dump/restore path. The Docker publish job only runs after the image passes the same compose sync the README documents.
What this demonstrates
Operational restraint: a one-shot, auditable script with Unix I/O discipline, instead of a platform around a two-command pipeline — plus just enough packaging and CI to trust it in other workflows.