Standalone database migration tool which works for Postgres, MariaDB, MySQL, Sqlite and LibSQL(Turso).
Rust
272
447 commits
updated Jul 15, 2026
Geni is a standalone migration tool designed to work in conjunction with your preferred ORM/toolkit/code. It allows multiple developers to collaborate without overriding database migrations. It can be used in a CD pipeline alongside your code to ensure your database stays up to date.
This project was heavily inspired by dbmate and was created because dbmate lacked support for LibSQL.
The application is developed using the Rust programming language and relies on the libsql-client-rs library for SQLite and LibSQL. Moreover, it makes use of SQLX to support Postgres, MariaDB, and MySQL databases. As this is written in rust is lighting fast, blazingly fast, tiny, ultra fast and memory safe
geni new **name**geni upgeni downgeni creategeni dropmysqldump need to be installed(already installed in docker)mariadb-dump need to be installed(already installed in docker)$ sudo curl -fsSL -o /usr/local/bin/geni https://github.com/emilpriver/geni/releases/latest/download/geni-linux-amd64
$ sudo chmod +x /usr/local/bin/geni
brew install geni
TBA
Run using PKGX
pkgx geni up
Run using nix
nix run github:emilpriver/geni -- up
cargo install geni
Docker images are published to GitHub Container Registry (ghcr.io/emilpriver/geni).
$ docker run --rm -it --network=host ghcr.io/emilpriver/geni:latest --help
there is also a slim docker image that don't have each database respective libraries(such as pg_dump).
Note: This image won't try to dump the database
$ docker run --rm -it --network=host ghcr.io/emilpriver/geni:latest-slim --help
If you wish to create or apply migrations, you will need to use Docker's bind mount feature to make your local working directory (pwd) available inside the geni container:
$ docker run --rm -it --network=host -v "$(pwd)/migrations:/migrations" ghcr.io/emilpriver/geni:latest new create_users_table`
geni new # Generate a new migrations file
geni up # Run any pending migration
geni down # Rollback migrations, use --amount to speify how many migrations(default 1)
geni create # Create the database, only works for Postgres, MariaDB and MySQL. If you use SQLite will geni create the file before running migrations if the sqlite file don't exist. LibSQL should be create using respective interface.
geni drop # Remove database
geni status # Print pending migrations
geni help # Print help message
DATABASE_MIGRATIONS_FOLDER
./migrationsDATABASE_URL
DATABASE_URL="postgres://[email protected]:5432/app?sslmode=disable"mysql://root:password@localhost:3307/appmariadb://root:password@localhost:3307/appsqlite://./database.sqlitehttps://localhost:6000
DATABASE_URL pointed at the remote/private database host. Geni will rewrite it to a local forwarded address at runtime.DATABASE_SSH_HOST
~/.ssh/config host alias.ssh client, so OpenSSH settings such as HostName, User, Port, IdentityAgent, ProxyJump, and related auth settings are honored automatically.DATABASE_SSH_USER
DATABASE_SSH_PORT
DATABASE_SSH_IDENTITY_FILE
ssh client, including ssh-agent and IdentityAgent from ~/.ssh/config.DATABASE_SSH_LOCAL_PORT
60000..=65000DATABASE_SSH_REMOTE_HOST
DATABASE_URLDATABASE_SSH_REMOTE_PORT
DATABASE_URL, or 5432 for Postgres and 3306 for MySQL/MariaDBDATABASE_TOKEN
Turso and LibSQL and require token to authenticate. If not specified will Geni try to migrate without any authDATABASE_WAIT_TIMEOUT
30 secondsDATABASE_SCHEMA_FILE
DATABASE_MIGRATIONS_TABLE
Running
DATABASE_URL="x" geni new hello_world
Will create 2 files(with path written in console). 1 file ending with .up.sql and 1 ending with .down.sql. .up.sql is for creating migrations and .down.sql is for rollbacking migrations. This means that .down.sql should include information that rollback the changed you added to .up.sql.
Example:
If I want to create table named Persons should I add this to .up.sql
CREATE TABLE Persons (
PersonID int
)
And the rollback migration should then be
DROP TABLE Persons;
in the generated .down.sql file as this code would revert the creation of the table Persons
Geni defaults to always run in transactions but if you want to prevent usage of transactions, add transaction: no as the first line of the migration file.
Then Geni won't use transactions for the specific migration.
This works for both up and down
Example:
-- transaction:no
CREATE TABLE table_2 (
id INT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Running migration can be done using
geni up
You can also pass the database URL as a flag instead of an environment variable:
geni --database-url "postgres://[email protected]:5432/app?sslmode=disable" up
Rollbacking last added migrations can be done using
geni down
and If you want to rollback more then 1 can you add -a to the cli to specify how many
geni down -a 3
DATABASE_URL="postgres://[email protected]:5432/app?sslmode=disable" geni up
SSH tunneling is supported for local CLI usage with Postgres, MySQL, and MariaDB URLs.
If you do not specify --ssh-local-port, geni will try high local ports in 60000..=65000 until OpenSSH successfully binds one. Use --ssh-local-port or DATABASE_SSH_LOCAL_PORT if you want a deterministic local port. If that explicit port is already in use, tunnel startup fails immediately.
geni \
--database-url "postgres://app:[email protected]:5432/app?sslmode=disable" \
--ssh-host "prod-bastion" \
up
Because geni shells out to the system ssh client, you can also rely on ~/.ssh/config for connection details and agent selection. Example with an OpenSSH alias and the 1Password SSH agent:
Host vps
HostName your-server.example.com
User root
Port 22
IdentityAgent "~/.1password/agent.sock"
Then run:
geni \
--database-url "postgres://app:[email protected]:5432/app?sslmode=disable" \
--ssh-host "vps" \
up
If the database is only reachable as localhost from the SSH server, keep the SSH host separate and override the remote target:
geni \
--database-url "postgres://app:secret@localhost:5432/app?sslmode=disable" \
--ssh-host "prod-bastion" \
--ssh-remote-host "127.0.0.1" \
up
V1 tunnel support is local CLI only. The published Docker image and GitHub Action still expect direct database connectivity.
- uses: emilpriver/geni@main
with:
migrations_folder: "./migrations"
wait_timeout: "30"
migrations_table: "schema_migrations"
database_url: "https://localhost:3000"
database_token: "X"
migrations_folder(optional): The path to where your migrations exist.
wait_timeout(optional): The time to wait before dropping the attempt to connect to the database
migrations_table(optional): The name of the migrations table
schema_migrationsIn a CI/CD should the database_url come from a security store and be appended to the environment as the DATABASE_URL. If the DATABASE_URL is provided as a environment variable is the only command you need to run
geni up
to make migrations.
Geni can be used as a library as well.
All exposed functions can be found in the library example folder
use geni;
#[tokio::main]
async fn main() {
// Migrate the database
geni::migrate_database(
"sqlite://./test.db".to_string(), // Database URL
None, // Database Token
"migrations".to_string(), // Migration Table
"./migrations".to_string(), // Migration Folder
"schema.sql".to_string(), // Schema File
Some(30), // Wait timeout for the database to be ready
false, // Dump Schema
)
.await
.unwrap();
()
}
603 followers · starred Nov 2025
20,600 followers · starred Sep 2024
220 followers · starred Mar 2026
720 followers · starred Apr 2024
Standalone database migration tool which works for Postgres, MariaDB, MySQL, Sqlite and LibSQL(Turso).
Rust
272
447 commits
updated Jul 15, 2026
Geni is a standalone migration tool designed to work in conjunction with your preferred ORM/toolkit/code. It allows multiple developers to collaborate without overriding database migrations. It can be used in a CD pipeline alongside your code to ensure your database stays up to date.
This project was heavily inspired by dbmate and was created because dbmate lacked support for LibSQL.
The application is developed using the Rust programming language and relies on the libsql-client-rs library for SQLite and LibSQL. Moreover, it makes use of SQLX to support Postgres, MariaDB, and MySQL databases. As this is written in rust is lighting fast, blazingly fast, tiny, ultra fast and memory safe
geni new **name**geni upgeni downgeni creategeni dropmysqldump need to be installed(already installed in docker)mariadb-dump need to be installed(already installed in docker)$ sudo curl -fsSL -o /usr/local/bin/geni https://github.com/emilpriver/geni/releases/latest/download/geni-linux-amd64
$ sudo chmod +x /usr/local/bin/geni
brew install geni
TBA
Run using PKGX
pkgx geni up
Run using nix
nix run github:emilpriver/geni -- up
cargo install geni
Docker images are published to GitHub Container Registry (ghcr.io/emilpriver/geni).
$ docker run --rm -it --network=host ghcr.io/emilpriver/geni:latest --help
there is also a slim docker image that don't have each database respective libraries(such as pg_dump).
Note: This image won't try to dump the database
$ docker run --rm -it --network=host ghcr.io/emilpriver/geni:latest-slim --help
If you wish to create or apply migrations, you will need to use Docker's bind mount feature to make your local working directory (pwd) available inside the geni container:
$ docker run --rm -it --network=host -v "$(pwd)/migrations:/migrations" ghcr.io/emilpriver/geni:latest new create_users_table`
geni new # Generate a new migrations file
geni up # Run any pending migration
geni down # Rollback migrations, use --amount to speify how many migrations(default 1)
geni create # Create the database, only works for Postgres, MariaDB and MySQL. If you use SQLite will geni create the file before running migrations if the sqlite file don't exist. LibSQL should be create using respective interface.
geni drop # Remove database
geni status # Print pending migrations
geni help # Print help message
DATABASE_MIGRATIONS_FOLDER
./migrationsDATABASE_URL
DATABASE_URL="postgres://[email protected]:5432/app?sslmode=disable"mysql://root:password@localhost:3307/appmariadb://root:password@localhost:3307/appsqlite://./database.sqlitehttps://localhost:6000
DATABASE_URL pointed at the remote/private database host. Geni will rewrite it to a local forwarded address at runtime.DATABASE_SSH_HOST
~/.ssh/config host alias.ssh client, so OpenSSH settings such as HostName, User, Port, IdentityAgent, ProxyJump, and related auth settings are honored automatically.DATABASE_SSH_USER
DATABASE_SSH_PORT
DATABASE_SSH_IDENTITY_FILE
ssh client, including ssh-agent and IdentityAgent from ~/.ssh/config.DATABASE_SSH_LOCAL_PORT
60000..=65000DATABASE_SSH_REMOTE_HOST
DATABASE_URLDATABASE_SSH_REMOTE_PORT
DATABASE_URL, or 5432 for Postgres and 3306 for MySQL/MariaDBDATABASE_TOKEN
Turso and LibSQL and require token to authenticate. If not specified will Geni try to migrate without any authDATABASE_WAIT_TIMEOUT
30 secondsDATABASE_SCHEMA_FILE
DATABASE_MIGRATIONS_TABLE
Running
DATABASE_URL="x" geni new hello_world
Will create 2 files(with path written in console). 1 file ending with .up.sql and 1 ending with .down.sql. .up.sql is for creating migrations and .down.sql is for rollbacking migrations. This means that .down.sql should include information that rollback the changed you added to .up.sql.
Example:
If I want to create table named Persons should I add this to .up.sql
CREATE TABLE Persons (
PersonID int
)
And the rollback migration should then be
DROP TABLE Persons;
in the generated .down.sql file as this code would revert the creation of the table Persons
Geni defaults to always run in transactions but if you want to prevent usage of transactions, add transaction: no as the first line of the migration file.
Then Geni won't use transactions for the specific migration.
This works for both up and down
Example:
-- transaction:no
CREATE TABLE table_2 (
id INT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Running migration can be done using
geni up
You can also pass the database URL as a flag instead of an environment variable:
geni --database-url "postgres://[email protected]:5432/app?sslmode=disable" up
Rollbacking last added migrations can be done using
geni down
and If you want to rollback more then 1 can you add -a to the cli to specify how many
geni down -a 3
DATABASE_URL="postgres://[email protected]:5432/app?sslmode=disable" geni up
SSH tunneling is supported for local CLI usage with Postgres, MySQL, and MariaDB URLs.
If you do not specify --ssh-local-port, geni will try high local ports in 60000..=65000 until OpenSSH successfully binds one. Use --ssh-local-port or DATABASE_SSH_LOCAL_PORT if you want a deterministic local port. If that explicit port is already in use, tunnel startup fails immediately.
geni \
--database-url "postgres://app:[email protected]:5432/app?sslmode=disable" \
--ssh-host "prod-bastion" \
up
Because geni shells out to the system ssh client, you can also rely on ~/.ssh/config for connection details and agent selection. Example with an OpenSSH alias and the 1Password SSH agent:
Host vps
HostName your-server.example.com
User root
Port 22
IdentityAgent "~/.1password/agent.sock"
Then run:
geni \
--database-url "postgres://app:[email protected]:5432/app?sslmode=disable" \
--ssh-host "vps" \
up
If the database is only reachable as localhost from the SSH server, keep the SSH host separate and override the remote target:
geni \
--database-url "postgres://app:secret@localhost:5432/app?sslmode=disable" \
--ssh-host "prod-bastion" \
--ssh-remote-host "127.0.0.1" \
up
V1 tunnel support is local CLI only. The published Docker image and GitHub Action still expect direct database connectivity.
- uses: emilpriver/geni@main
with:
migrations_folder: "./migrations"
wait_timeout: "30"
migrations_table: "schema_migrations"
database_url: "https://localhost:3000"
database_token: "X"
migrations_folder(optional): The path to where your migrations exist.
wait_timeout(optional): The time to wait before dropping the attempt to connect to the database
migrations_table(optional): The name of the migrations table
schema_migrationsIn a CI/CD should the database_url come from a security store and be appended to the environment as the DATABASE_URL. If the DATABASE_URL is provided as a environment variable is the only command you need to run
geni up
to make migrations.
Geni can be used as a library as well.
All exposed functions can be found in the library example folder
use geni;
#[tokio::main]
async fn main() {
// Migrate the database
geni::migrate_database(
"sqlite://./test.db".to_string(), // Database URL
None, // Database Token
"migrations".to_string(), // Migration Table
"./migrations".to_string(), // Migration Folder
"schema.sql".to_string(), // Schema File
Some(30), // Wait timeout for the database to be ready
false, // Dump Schema
)
.await
.unwrap();
()
}
603 followers · starred Nov 2025
20,600 followers · starred Sep 2024
220 followers · starred Mar 2026
720 followers · starred Apr 2024