Released v0.1.2 · MIT License · Written in Go

Migrate MySQL PostgreSQL
without the pain.

pgpipe moves your data with cursor-based pagination, smart column mapping, resumable state, and a beautiful TUI — or run headless from your scripts.

Download v0.1.2 View on GitHub See features ↓
zsh — pgpipe
~ $
pgpipe Migration in Progress q quit
individuals (MySQL) individuals (PostgreSQL)
Progress
0%
Batches 0 / 100
Speed 0 rows/s
Rows 0 / 500,000
Skipped 0

Features

Everything you need.
Nothing you don't.

Built for real migrations: large tables, mismatched schemas, interrupted runs, and automated pipelines.

Interactive TUI Wizard

Built with Bubble Tea. Navigate tables, columns, and settings with keyboard shortcuts. Search with /. No YAML required to get started.

Headless CLI Mode

Run pgpipe run --config=file.yaml from scripts, cron, or CI pipelines. No TUI overhead. Parallel table migrations with separate state files.

🔁

Resumable Migrations

State saved after every batch. Interrupted? Resume from the exact cursor position. SHA-256 config hashing detects changes and resets automatically.

📍

Cursor-Based Pagination

No OFFSET degradation. Each batch queries by primary key for constant performance across billions of rows.

🔀

Smart Column Mapping

Auto-matches by name. Auto-detects transforms for type mismatches: TEXT→JSONB, TINYINT→BOOLEAN, CHAR→UUID. Full editor for custom mappings.

⚙️

Config Generator

pgpipe generate-configs introspects both schemas and writes one YAML per table. Migrate everything in one parallel loop.

📊

Real-Time Progress

Live updates every 500ms: batches, rows, speed (rows/sec), skipped count. Terminal-native. No web dashboards, no agents.

📋

JSONL Error Logs

Invalid JSON, bad UUIDs, insert failures — skipped and logged to a timestamped JSONL file. Parse with jq. Migration never stops.

🌿

.env File Support

Drop a .env in your working directory. pgpipe loads it automatically. No extra tooling. Override any value from the shell.

Get running in 60 seconds.

Pre-built binaries for Linux and macOS. Or install via go install.

# Download v0.1.2 binary
curl -L https://github.com/RobertoGongora/pgpipe/releases/download/v0.1.2/pgpipe-linux-amd64 \
     -o pgpipe
chmod +x pgpipe
./pgpipe
# Download v0.1.2 binary (Apple Silicon)
curl -L https://github.com/RobertoGongora/pgpipe/releases/download/v0.1.2/pgpipe-darwin-arm64 \
     -o pgpipe
chmod +x pgpipe
./pgpipe
# Download v0.1.2 binary (Intel Mac)
curl -L https://github.com/RobertoGongora/pgpipe/releases/download/v0.1.2/pgpipe-darwin-amd64 \
     -o pgpipe
chmod +x pgpipe
./pgpipe
# Requires Go 1.24+
go install github.com/RobertoGongora/pgpipe/cmd/pgpipe@latest
git clone https://github.com/RobertoGongora/pgpipe.git
cd pgpipe
make build

Quick Start

1. Create a .env file

# MySQL source
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=secret
MYSQL_DATABASE=source_db

# PostgreSQL target
PGSQL_HOST=localhost
PGSQL_PORT=5432
PGSQL_USER=postgres
PGSQL_PASSWORD=secret
PGSQL_DATABASE=target_db

2. Launch and follow the wizard

./pgpipe          # interactive TUI

# — or headless —
./pgpipe run \
  --config=.pgpipe/config.yaml

# — or generate configs for all tables —
./pgpipe generate-configs \
  --output-dir=./configs

# — parallel migration —
for f in ./configs/*.yaml; do
  pgpipe run --config="$f" &
done && wait
[pgpipe] Starting migration: users → users (batch_size=5000) [pgpipe] Connected to MySQL and PostgreSQL [pgpipe] Source: 1,234,567 rows (id 1..1234567) [pgpipe] users: batch 1 | 5,000/1,234,567 (0.4%) | imported=5,000 [pgpipe] users: batch 2 | 10,000/1,234,567 (0.8%) | imported=10,000 ... [pgpipe] Migration complete: users Processed : 1,234,567 rows Imported : 1,234,390 rows Skipped : 177 rows Duration : 8m32.451s

Auto-Transforms

Type mismatches handled automatically.

pgpipe detects incompatible column types and applies the right transform — in the TUI wizard and in generated configs.

Transform MySQL Source Types PostgreSQL Target Behavior
text_to_jsonb TEXT, VARCHAR, LONGTEXT, JSON JSON, JSONB Validates JSON. Invalid rows skipped + logged.
int_to_bool TINYINT, SMALLINT, INT, BIGINT BOOLEAN 0 → false, non-zero → true, NULL → NULL
string_to_uuid CHAR(36), VARCHAR UUID Passes through. PostgreSQL validates format.

Transforms are set automatically when you run pgpipe generate-configs or step through the TUI wizard. You can override them manually in YAML config files for custom mappings.

Additional transforms (dates, enums, custom functions) are planned for v2. Open an issue to propose one.