Railbase
GPTClaude

Migrations

Turn schema changes into versioned migrations and apply them.

Updated

Video guide —watch on YouTube ↗

Your Go schema is the source of truth. Migrations are how that truth becomes a durable, versioned, replayable record of your data model. Railbase generates them by diffing your DSL against the last applied schema.

Railbase stores everything in Vault — a single-file embedded document store, not SQL. A migration is the captured diff between two schema snapshots: which collections appeared, which fields and indexes changed. Because Vault documents are schema-free, adding a field needs no table rewrite — the change is just a new field your code starts writing, plus any indexes you asked for. Migrations create collections and indexes; they never issue column DDL.

Generate a migration

After you change schema/, generate a migration from the diff:

railbase migrate diff add_posts_status

This writes migrations/NNNN_add_posts_status.up.sql. User migrations are numbered from 1000 up (system migrations occupy the lower range). If the diff is empty you'll see "schema unchanged — no migration emitted."

The .up.sql file is a human-readable record of the change — collection and index operations, one per line. It is the versioned, reviewable history of your model, not a script that gets executed against a SQL engine (there is none). Railbase applies the equivalent Vault operations directly.

Note

If a change can't be made safely automatically (for example a type change that could lose data), migrate diff refuses and lists the incompatible changes so you can hand-write the migration. Field additions are always safe — a new field is simply absent on older documents until you write it.

Apply migrations

railbase migrate up            # apply all pending, then snapshot the schema
railbase migrate status        # VERSION · NAME · STATUS(applied|pending) · SOURCE

migrate up records each pending migration in the _migrations collection and writes a fresh schema snapshot — the baseline the next migrate diff compares against. Vault collections themselves are created implicitly on first write, so a new collection works the moment your code writes to it; the migration record and snapshot are what give you a versioned, drift-checked history.

On serve, Railbase applies its own system migrations automatically and registers every code-defined collection from your DSL (the init() in schema/), so a freshly-deployed binary boots ready to serve. Run migrate up to record your user migrations and refresh the schema snapshot — the scaffolded Makefile provides a standalone migrate-up target you run before serving; it is not auto-chained ahead of dev/serve.

Rolling back

railbase migrate down   →  not implemented (refuses)

There is no automatic down-migration. To reverse a change, write a new forward migration that undoes it — explicit and reviewable beats a magic rollback. And before anything risky, take a snapshot:

railbase backup

See Backups & restore.

Where migrations live

migrations/
├── 1000_initial_schema.up.sql
├── 1001_add_posts_status.up.sql
└── …

Commit the migrations/ directory to source control alongside schema/ — that pair is the full, replayable history of your data model.

Was this page helpful?Thanks for your feedback!