A developer-first CLI tool for moving data across databases, CSVs, and Google Sheets
Project description
Portl
Portl is a developer-first CLI tool for moving data across databases, CSVs, and Google Sheets. Instead of writing one-off SQL or Python scripts for every migration, Portl gives you an interactive wizard and YAML job configs you can re-run, share, and version-control.
Portl turns migrations into a repeatable, reliable system — not a grind.
Features
- Sources: Postgres, MySQL, CSV, Google Sheets.
- Interactive wizard — no need to remember 12 flags.
- YAML job specs — portable, reusable, version-controlled.
- Hooks — run scripts or APIs before/after rows or batches.
- Dry run + schema validation.
- Batch execution with retries.
Installation
Option 1: Docker (Recommended)
Quick Start:
# Run directly with Docker
docker run --rm ghcr.io/hebaghazali/portl:latest --help
# Install wrapper for native-like experience
# Linux/macOS/WSL:
curl -fsSL https://raw.githubusercontent.com/hebaghazali/portl/main/scripts/install-portl.sh | bash
# Windows PowerShell:
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/hebaghazali/portl/main/scripts/install-portl.ps1" | Invoke-Expression
# Then run:
portl --help
Direct Docker Usage:
# Basic usage
docker run --rm ghcr.io/hebaghazali/portl:latest init
# With volume mounting for file access
docker run --rm -v "$PWD:/work" -w /work ghcr.io/hebaghazali/portl:latest run jobs/migration.yaml
# With environment variables
docker run --rm -e PORTL_API_KEY=your_key ghcr.io/hebaghazali/portl:latest run jobs/sheets_to_db.yaml
# With custom config directory
docker run --rm -v "$PWD:/work" -v "$HOME/.portl:/home/app/.portl" ghcr.io/hebaghazali/portl:latest init
Wrapper Installation: The wrapper script provides a native CLI experience while using Docker under the hood:
- Mounts current directory as
/work - Passes through
PORTL_*environment variables - Handles TTY detection for interactive commands
- Automatically pulls the latest image
Option 2: Python Package
pip install portl
Quickstart
1. Start a New Migration
portl init
This launches the wizard and asks questions like:
- What's your source? (Postgres/MySQL/CSV/Google Sheet)
- What's your destination?
- How do you want to map fields?
- Conflict strategy? (skip/overwrite/merge/fail)
- Any hooks before/after rows or batches?
At the end, Portl generates a YAML job file for you.
2. Example YAML Job
source:
type: csv
path: ./data/users.csv
destination:
type: postgres
host: localhost
database: mydb
table: users
conflict: overwrite
batch_size: 100
hooks:
before_batch: ./scripts/notify_start.sh
after_batch: ./scripts/notify_done.sh
3. Run the Migration
portl run jobs/users_to_pg.yaml
4. Dry Run Preview
portl run jobs/users_to_pg.yaml --dry-run
→ Shows sample rows, schema mapping, and a row count check without writing data.
Docker Distribution
Portl is distributed as a multi-architecture Docker image supporting both linux/amd64 and linux/arm64 platforms.
Image Tags
ghcr.io/hebaghazali/portl:latest- Latest stable releaseghcr.io/hebaghazali/portl:v0.2.0- Specific version (replace with actual version)ghcr.io/hebaghazali/portl:main- Latest from main branch
Platform Support
- ✅ Linux AMD64 - Native support
- ✅ Linux ARM64 - Native support (Apple Silicon, ARM servers)
- ✅ macOS - Via Docker Desktop (both Intel and Apple Silicon)
- ✅ Windows - Via Docker Desktop (PowerShell, CMD, Git Bash, WSL2)
Advanced Usage
Environment File:
# Create .env file with your configuration
echo "PORTL_API_KEY=your_key" > .env
echo "PORTL_DB_HOST=localhost" >> .env
# Use with Docker
docker run --rm --env-file .env -v "$PWD:/work" -w /work ghcr.io/hebaghazali/portl:latest run jobs/migration.yaml
Network Access:
# Access host services (useful for local databases)
docker run --rm --add-host host.docker.internal:host-gateway -v "$PWD:/work" -w /work ghcr.io/hebaghazali/portl:latest run jobs/local_db.yaml
Custom Image:
# Linux/macOS/WSL:
export PORTL_IMAGE=ghcr.io/hebaghazali/portl:v0.2.0
curl -fsSL https://raw.githubusercontent.com/hebaghazali/portl/main/scripts/install-portl.sh | bash
# Windows PowerShell:
$env:PORTL_IMAGE="ghcr.io/hebaghazali/portl:v0.2.0"
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/hebaghazali/portl/main/scripts/install-portl.ps1" | Invoke-Expression
Windows-Specific Usage:
# PowerShell
docker run --rm ghcr.io/hebaghazali/portl:latest --help
# With volume mounting (PowerShell)
docker run --rm -v "${PWD}:/work" -w /work ghcr.io/hebaghazali/portl:latest init
# With volume mounting (CMD)
docker run --rm -v "%CD%:/work" -w /work ghcr.io/hebaghazali/portl:latest init
# With environment variables (PowerShell)
docker run --rm -e PORTL_API_KEY=your_key ghcr.io/hebaghazali/portl:latest run jobs/migration.yaml
Building from Source
# Build locally
make build
# Build multi-architecture
make build-multi
# Run tests
make test
# Create release
make release TAG=v1.0.0
Documentation
See full docs & examples at: [coming soon]
Contributing
We welcome issues, forks, and pull requests. MIT licensed.
With Portl, you'll never write the same migration script twice.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file portl-0.0.2.tar.gz.
File metadata
- Download URL: portl-0.0.2.tar.gz
- Upload date:
- Size: 12.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7bc916ae2e3c9a914cf338e7c82e0f37116d3b3c0cdc4ed893d6259dd3bf6d75
|
|
| MD5 |
029df441831bb7cf22a4e6c1bdf89063
|
|
| BLAKE2b-256 |
57a4c93d81701d0958cf8b3e413e3cc9b20b72356fcfba7fcf6f5c7307321687
|
Provenance
The following attestation bundles were made for portl-0.0.2.tar.gz:
Publisher:
publish.yml on hebaghazali/portl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
portl-0.0.2.tar.gz -
Subject digest:
7bc916ae2e3c9a914cf338e7c82e0f37116d3b3c0cdc4ed893d6259dd3bf6d75 - Sigstore transparency entry: 570267253
- Sigstore integration time:
-
Permalink:
hebaghazali/portl@a67769844802f1eab5aa9aa4e8effe6450e23a71 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/hebaghazali
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a67769844802f1eab5aa9aa4e8effe6450e23a71 -
Trigger Event:
push
-
Statement type:
File details
Details for the file portl-0.0.2-py3-none-any.whl.
File metadata
- Download URL: portl-0.0.2-py3-none-any.whl
- Upload date:
- Size: 12.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b90528a0fa3f6b2db5ba409afa66d20c63bdc5b74e2d27dfab219977d3f39ae2
|
|
| MD5 |
5817e945b5438d8b77880efa7ab60b1a
|
|
| BLAKE2b-256 |
a027c7edd206ff45d58f3a0a0e1959c438ced1410a93d399bc6758df1a80d7e4
|
Provenance
The following attestation bundles were made for portl-0.0.2-py3-none-any.whl:
Publisher:
publish.yml on hebaghazali/portl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
portl-0.0.2-py3-none-any.whl -
Subject digest:
b90528a0fa3f6b2db5ba409afa66d20c63bdc5b74e2d27dfab219977d3f39ae2 - Sigstore transparency entry: 570267259
- Sigstore integration time:
-
Permalink:
hebaghazali/portl@a67769844802f1eab5aa9aa4e8effe6450e23a71 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/hebaghazali
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a67769844802f1eab5aa9aa4e8effe6450e23a71 -
Trigger Event:
push
-
Statement type: