Migrations
Turn schema changes into versioned migrations and apply them.
Updated
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 records in Vault — a single-file embedded
document store, not SQL. Uploaded file blobs live in storage, but their metadata
is still ordinary records. 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. A migration is a versioned record of the change; it
issues no SQL/DDL and isn't executed as a script. Collections appear on first
write and indexes are reconciled from your DSL at boot — migrate up records
that the migration was applied and refreshes the schema snapshot.
Generate a migration
After you change schema/, generate a migration from the diff — run it with
your project's binary (./myapp), since the diff compares against the schema
your project's init() registers; the stock railbase binary has no collections
to diff:
./myapp 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.