Open-source project
fabianlindfors/reshape avatar
fabianlindfors/reshape

Reshape makes schema changes a three-phase deployment, not a lock

An easy-to-use, zero-downtime schema migration tool for Postgres

1,857 stars41 forksRustMIT

At a glance

What is it?
Reshape is a Rust CLI that applies Postgres schema migrations without downtime by putting views between your application and the tables, then using triggers to translate writes across old and new shapes while you roll out code. It is designed for Postgres 12 and later, and its real constraint is not the database work but the requirement that your application adopt a schema-switching connection step.
Who is it for?
Adopt Reshape if you deploy Postgres schema changes alongside application code and cannot schedule a lock, because the view-and-trigger approach is the only way to have both shapes live at once and it handles the translation work you would otherwise write by hand.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 27 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Views in front of tables, triggers doing the translation

The mechanism is stated in one paragraph and it explains everything else. Reshape works by creating views that encapsulate the underlying tables, which your application interacts with. During a migration, Reshape automatically creates a new set of views and sets up triggers to translate inserts and updates between the old and new schema.

That reframes the problem. A schema change that would normally require downtime is usually hard because the old code and the new code disagree about the shape of the data, and you cannot swap one for the other atomically. Views give each generation of your application its own shape of the same underlying data, and triggers keep both shapes consistent as writes arrive.

Because of that, every deployment becomes a three-phase process. The first is start migration, which sets up the views and triggers so both schemas are usable at the same time. The second is rolling out your application, which can be gradual and without downtime, with the existing deployment continuing on the old schema while the new deployment uses the new one. The third is complete migration, which removes the old schema along with any intermediate data and triggers.

There is a fourth path, and it is the one you hope you never need. If the application deployment fails, you run reshape migration abort, which rolls back any changes made by reshape migration start without losing data.

The stated reason for all this is that Reshape performs changes without excessive locking, avoiding downtime caused by blocking other queries. It is designed for Postgres 12 and later.

Three install routes and a version claim that contradicts the manifest

Binaries are published on the Releases page for macOS and Linux. There is also a Docker image on Docker Hub, and a Cargo installation path.

shell
cargo install reshape

The README says the Cargo route requires Rust 1.58 or later. The repository's own manifest disagrees: Cargo.toml declares rust-version = 1.88, and edition 2021, with the package version 0.11.2 matching the latest release tag. The Dockerfile builds from rust:1.93.0. So the 1.58 figure in the documentation is stale, and anyone on an older toolchain will find the build fail before Cargo produces a helpful message.

The Docker invocation mounts the current directory so the tool can read your migration files:

shell
docker run -v $(pwd):/usr/share/app fabianlindfors/reshape reshape migration start

The mount path is not arbitrary. The Dockerfile copies the release binary into /usr/local/bin and sets the work directory to /usr/share/app, so mounting there is what makes your migrations/ directory visible inside the image. The image is a two-stage build: a builder stage on rust:1.93.0 that installs clang and libclang-dev before running cargo build --release, and a slim Debian trixie runtime that carries only the binary and ca-certificates.

That clang dependency in the builder is not incidental. pg_query is in the dependency list, and that crate parses real Postgres SQL, which needs libclang to work at build time. If you are compiling Reshape yourself rather than pulling a release binary, expect to install clang first.

Migrations are JSON or TOML files whose names are the migration names

Each migration is a separate file in a migrations/ directory, in either JSON or TOML format, and the file name becomes the migration name. The recommendation is to prefix every migration with an incrementing number, because migrations are sorted by file name. That sorting rule is worth internalising: it means ordering is lexical on filenames, not a version column you control.

The example creates a users table with an id and a name, which the README states is equivalent to running CREATE TABLE users (id INTEGER GENERATED ALWAYS AS IDENTITY, name TEXT).

toml
[[actions]]
type = "create_table"
name = "users"
primary_key = ["id"]

	[[actions.columns]]
	name = "id"
	type = "INTEGER"
	generated = "ALWAYS AS IDENTITY"

	[[actions.columns]]
	name = "name"
	type = "TEXT"

Note the nesting. There is an array of actions, and each action carries its own properties, and columns are an array of tables inside the action. A single migration file can therefore carry several actions rather than one, which is how a migration that touches a table and an index stays in one unit.

The table of contents lists the action vocabulary, and it is enumerated rather than open ended. Tables support create_table, rename_table, remove_table, add foreign key, remove foreign key, add check and remove check. Columns support add column, alter column and remove column. Indices support add index and remove index. Enums support create enum and remove enum. Then there are Custom, for arbitrary SQL, and a section on complex changes across tables.

That last category is where judgement is needed. A single-table change is a mechanical mapping to an action, but a change that has to keep two tables consistent while old and new code both write is exactly the case the tool was built for, and also the case where you should read the section rather than guess.

Your application has to ask which schema it wants

This is the requirement that decides whether Reshape fits your codebase, and it is not optional.

Reshape relies on your application using a specific schema. When establishing the connection to Postgres, your application needs to run a query to select the most recent schema. Helper libraries exist for Rust, Ruby and Rails, Python and Django, and Go, each in its own repository under the author's name except the Go one.

For anything else, the tool generates the query for you. The command is reshape schema-query, and the suggested wiring is an environment variable set at run script time:

bash
RESHAPE_SCHEMA_QUERY=$(reshape schema-query)

and then in your application, a Python example, read the variable and execute it:

python
reshape_schema_query = os.getenv("RESHAPE_SCHEMA_QUERY")
db.execute(reshape_schema_query)

The interesting detail is that the query is resolved once at startup rather than being hardcoded. That is what lets the same build of your application run against the old schema or the new one, and it is what makes a gradual rollout possible: the deployment environment decides which schema each instance gets.

The cost of this design is that adopting Reshape is not only a migration-tooling change. It is a connection-layer change in every service that touches the migrated tables. If a service is not under your control, or is a third-party tool that connects straight to Postgres, it will not participate in the switch.

With nothing else specified, Reshape connects to a Postgres database on localhost using postgres as both username and password, with connection options documented separately for changing that.

Start, complete, abort, status

The command surface is small and maps onto the three-phase model plus an escape hatch.

To create the new table in the tutorial you run the migration with a flag that skips the middle phase:

bash
reshape migration start --complete

The flag exists because during initial development you do not have an old deployment to keep compatible. The README is explicit that in a production deployment you should instead run reshape migration start first, then reshape migration complete once your application has been fully rolled out. Running with --complete in production removes the old schema immediately, which is the downtime you installed the tool to avoid.

There is also reshape status, which fits naturally into a deployment pipeline as a check before or after the switch, reshape migration abort for the failed deployment case, and reshape docs for reference.

For day-to-day development the README recommends running reshape migration start and skipping the completion step, so that migrations accumulate in the same half-migrated state production will briefly occupy. That is good discipline: the state your local database sits in is the state your production database will sit in for the length of your rollout.

The repository also carries a CLAUDE.md, which is a reasonable inclusion for a tool whose output is a set of declarative files describing schema state to another program.

One area the README does not cover in the visible portion is what happens when a migration is wrong in production and abort is not enough, such as a data transformation that has already been applied. The migration model is expressed as views and triggers rather than as data movement, which limits the exposure, but the rollback story beyond abort is not described.

Licence, release cadence and the hosted alternative

The project is MIT licensed, with LICENSE.md at the root. That permits forking and modification without the copyleft obligations of the GPL, and for a tool you intend to embed in your own deployment pipeline it is the pragmatic choice.

Three of the recent releases, 0.11.0, 0.11.1 and 0.11.2, all landed on the same day in September 2026. The repository is not archived and the last push was on 2026-09-26.

The dependency list is short enough to read for what it implies. The postgres crate with serde_json support is the driver, clap with derive handles the command line, serde and serde_json plus toml cover the two migration formats, and typetag is the tag-based deserialisation that lets each action type carry its own schema. pg_query is the real Postgres SQL parser, which is how Custom actions and complex changes can be validated rather than passed through blind. anyhow with backtrace for errors, colored for terminal output, rand and lexical-sort for deterministic ordering, dotenvy for local configuration, and native-tls with vendored features for a TLS connection to the database.

There is also a commercial angle mentioned in the README. It points readers to Specific, described as a complete cloud platform designed for coding agents, which includes special support for Reshape and will automatically handle the zero-downtime migration procedure for deployments. That is a hosted product, not a repository feature, and it is worth separating from what the open source tool does: everything described in the phases above is what you run yourself.

Editorial conclusion

Adopt Reshape if you deploy Postgres schema changes alongside application code and cannot schedule a lock, because the view-and-trigger approach is the only way to have both shapes live at once and it handles the translation work you would otherwise write by hand. Do not adopt it if your application cannot change its connection logic, since the whole design depends on running a schema-selecting query when opening a connection, and that is not something you can retrofit from the migration tool alone. Verify first which action types your migration needs against the README's list, since the supported set is enumerated and anything outside it, including custom actions and complex cross-table changes, has its own section you should read before assuming it works on your schema.

Frequently asked questions

How do I install Reshape for Postgres migrations?

Three ways: binaries for macOS and Linux from the Releases page, cargo install reshape, or the Docker image run as docker run -v $(pwd):/usr/share/app fabianlindfors/reshape reshape migration start. The README says Rust 1.58 or later, but Cargo.toml declares rust-version 1.88.

How does Reshape achieve zero-downtime schema changes?

It creates views that encapsulate the underlying tables and sets up triggers that translate inserts and updates between the old and new schema, so both are usable at once while your application rolls out gradually. Complete migration then removes the old schema and the intermediate triggers.

What has to change in my application to use Reshape?

Your connection layer has to run a schema-selecting query when connecting to Postgres, so the same build can run against either schema. Helper libraries exist for Rust, Ruby and Rails, Python and Django, and Go, or you can generate the query with reshape schema-query and pass it through the RESHAPE_SCHEMA_QUERY environment variable.

What happens if an application deployment fails during a Reshape migration?

Run reshape migration abort, which rolls back the changes made by reshape migration start without losing data. In production you should run reshape migration start and reshape migration complete as separate steps rather than using the --complete flag, which removes the old schema immediately.

Official sources

  1. fabianlindfors/reshape on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/fabianlindfors-reshape.svg)](https://hysenlabs.com/projects/fabianlindfors-reshape)

Was this useful?