Migrate
voltro migrate — apply the declared schema through the declarative differ (an alias of voltro db apply).
voltro migrate applies your declared schema (*.entity.ts / *.schema.ts / schema.ts) to the configured database. It is an alias of voltro db apply: it diffs the declared schema against the live database and emits the ALTERs, so a changed column or a new index actually lands.
Before 0.11.4 this command was a create-only apply (
CREATE TABLE IF NOT EXISTS, no diffing), which meant a column or type change reported success having applied nothing. If you need that bootstrap-only behaviour for a brand-new database, it is nowvoltro migrate --create-only.
voltro migrate # apply the discovered schema to the configured store
voltro migrate apps/api # explicit app directory (defaults to cwd)voltro migrate forwards its arguments to voltro db apply, so the same flags apply; --create-only selects the bootstrap-only emitter instead. The diff / plan / apply / drift / squash workflow lives under voltro db (see below).
For the deep dive on the schema DSL + day-to-day patterns, see Database / Migrations.
How discovery works
The migrator walks the project root (skipping node_modules, dist, .framework, etc.) for:
*.entity.ts/*.schema.ts/schema.ts— your table descriptors.- Feature files (
*.workflow.tsx,*.cron.tsx,*.webhook.tsx) — their presence decides which framework-internal_voltro_*tables get created (workflow-run tables, schedule ledgers, etc.)._voltro_tracesis the exception — env-gated, not feature-gated: it's created undervoltro dev(durable trace history on by default) but NOT undervoltro serve/voltro start(prod default off → setVOLTRO_TRACING_PERSIST=interestingto opt in). See Distributed tracing.
The core actors table is injected automatically when you didn't declare your own, and tenants is registered when present, so the audit / soft-delete / tenant mixins resolve their FK targets.
voltro dev already auto-migrates
On a SQL-backed store, voltro dev AUTO-APPLIES the discovered schema before any handler boots — so a standalone voltro migrate is rarely needed in the inner loop. It's the CI / ops-pipeline entry point.
Opt out of the dev auto-migrate when you ship schema through a separate reviewed pipeline:
VOLTRO_AUTO_MIGRATE=0 voltro dev .The boot log then says auto-migrate: skipped (VOLTRO_AUTO_MIGRATE=0), and you run voltro migrate (or voltro db apply) explicitly.
Store + dialect selection
The dialect is resolved from DB_DIALECT (default postgres); the connection comes from DB_URL / DB_PRIMARY_URL, falling back to the discrete DB_* / PG_* fields (DB_HOST / PG_HOST, DB_PORT / PG_PORT, etc.).
DB_DIALECT=postgres DB_URL=postgres://app:app@localhost:5432/app voltro migrateMigrating against DB_DIALECT=memory is rejected — there's nothing to migrate. In dev with the memory store, the in-memory store learns its shape from the schema declaration directly; switch to a SQL dialect (postgres, sqlite, mysql, mariadb, mssql, turso) to test the real migration path.
The declarative diff workflow — voltro db
The plan / apply / drift / squash machinery — diffing the declared schema against a live database, generating reviewable DDL, applying a pre-reviewed plan in production — lives under voltro db:
voltro db plan # diff declared schema vs live, color-coded
voltro db plan --json # machine-readable for CI / PR comments
voltro db plan --sql # raw DDL preview
voltro db plan --against <url> # diff vs a REMOTE env via /_voltro/inspect/migrations
voltro db apply # execute the plan (dev only — refuses NODE_ENV=production)
voltro db apply --plan plan.json # prod: apply a pre-reviewed plan from CI/CD
voltro db plans [--limit 20] # plan history from _voltro_migration_plans
voltro db drift # alert if live diverged from the latest applied fingerprint
voltro db squash --before <iso-date> # consolidate history into one snapshot
voltro db restore-snapshot <plan-id> # restore soft-dropped columns from a planA separate file-based migration surface (the offline escape hatch) lives alongside it:
voltro db migrate # apply pending migration files
voltro db rollback [--to <id>] # undo migrations
voltro db status # list applied / pending migrations
voltro db seed # run boot-lifecycle seeds against the configured store
voltro db seed --id <name> # run one seed by id
voltro db seed --store memory # explicit override; default = the app's configured storevoltro db seed defaults to the same store the app runs on (DB_DIALECT → STORE → app.config.ts store: → postgres) — seeding to memory was a silent data-loss footgun, so you opt into it explicitly.
Production safety
For production deploys:
- Always apply schema BEFORE app version bumps. App N+1 expects schema N+1; app N should still tolerate schema N+1.
- Review the plan first.
voltro db plan --jsonin CI, apply the reviewed plan withvoltro db apply --plan plan.json. - Check for drift.
voltro db driftflags a live database that diverged from the last applied fingerprint. - Verify backups before destructive changes. Restoring is the actually-tested rollback path.
For zero-downtime deploys with breaking schema changes:
- Add new columns / tables → apply first, then deploy.
- Remove columns → deploy code that doesn't read them, then apply the drop.
- Rename → add new column + dual-write, deploy, drop old column later.
The framework doesn't enforce these — that's an SRE responsibility. See Migrations for the playbook.
See also
- Database / Migrations — the schema DSL deep dive
- Self-hosting — production migration patterns