Planned Migrations
Planned migrations are the production-grade schema evolution mechanism in MooseStack. Unlike Automatic Migrations, this system separates the generation of schema changes from their execution, introducing a reviewable artifact (the plan) into your deployment lifecycle.
Command
Generate a migration plan by comparing your local code against a production environment:
# For Server Runtime (connect to Moose Admin API)
moose generate migration --url <ADMIN_API_URL> --token <ADMIN_TOKEN> --save
# For Serverless (connect to ClickHouse directly)
moose generate migration --clickhouse-url <CLICKHOUSE_CONNECTION_STRING> --saveKey Benefits:
- Deterministic: The plan is a static file (
plan.yaml) that won't change at runtime. - Drift Detection: Snapshots (
remote_state.json) ensure the DB hasn't changed since the plan was created. - Reviewable: You can audit every operation (e.g.,
DropColumn,AddTable) before it runs, and you can edit the plan to override Moose's assumptions. - Versioned: Commit plans to Git to create a permanent audit trail.
Workflow
The lifecycle consists of four distinct stages:
-
Code Change — Modify your data models (tables, views) in your Moose project.
-
Generation — Run the CLI to compare your code against production.
moose generate migration --save ... -
Review — Inspect the generated
migrations/plan.yamlfile and commit it to Git. -
Application — Execute the plan during deployment (if using Moose Runtime), manually via the CLI or your own CI/CD pipeline (if using Serverless).
moose migrate ... ## or if using the Moose Runtime moose prod
Generated Artifacts
Running the generation command produces three files in the migrations/ directory.
| File | Purpose |
|---|---|
plan.yaml | The imperative list of operations (e.g., AddTableColumn) to execute. See Plan Reference. |
remote_state.json | A snapshot of the production database schema at the time of generation. Used to detect drift. |
local_infra_map.json | A snapshot of your local code's schema definitions. Used to validate the plan against the code. |
Configuration
Planned migrations are active whenever a plan.yaml file exists in the migrations/ directory. No feature flag is required -- if the file is present, moose prod applies the static plan; if it is absent, Moose falls back to dynamic OLAP reconciliation.
[features]olap = trueProduction Safety Gate
By default, moose prod refuses to start if the computed infrastructure diff contains destructive operations (table drops, column drops, table recreates, view removals) and no plan.yaml is present. This prevents accidental data loss when deploying schema changes that haven't been reviewed.
[migration_config]# Default: false — destructive changes blocked unless a plan.yaml is presentprod_auto_allow_destructive = false| Scenario | Behavior |
|---|---|
| No destructive changes | moose prod starts normally regardless of plan.yaml. |
Destructive changes + plan.yaml present | moose prod applies the reviewed plan and starts normally. |
Destructive changes + no plan.yaml | moose prod refuses to start with an error listing the destructive operations. |
When startup is blocked, the error message lists every destructive operation and suggests two remediation paths:
- Run
moose generate migration --saveto create a reviewedplan.yaml. - Set
prod_auto_allow_destructive = trueinmoose.config.toml(orMOOSE_MIGRATION_CONFIG__PROD_AUTO_ALLOW_DESTRUCTIVE=trueas an environment variable) to opt out of the safety gate.
Opting out
Setting prod_auto_allow_destructive = true restores the pre-safety-gate behavior where moose prod applies destructive changes without a plan file. Only do this if you accept the risk of unreviewed destructive schema changes in production.
Command Options
The moose generate migration command accepts different arguments depending on your deployment model for applying changes.
via Moose Runtime (moose prod)
Connect to the Admin API of the running service.
moose generate migration --url <ADMIN_API_URL> --token <ADMIN_TOKEN> --save| Option | Description |
|---|---|
--url | The endpoint of your production Moose Admin API. |
--token | The authentication token for the Admin API. |
--save | Writes the generated plan to the migrations/ directory. Without this, it performs a dry run. |
via Serverless (moose migrate)
Connect directly to the ClickHouse database.
moose generate migration --clickhouse-url <CLICKHOUSE_CONNECTION_STRING> --save| Option | Description |
|---|---|
--clickhouse-url | Direct connection string (e.g., clickhouse://user:pass@host:9440/db). |
--save | Writes the generated plan to the migrations/ directory. Without this, it performs a dry run. |
Confirmation flags
These flags control the interactive confirmation prompts during plan generation:
| Option | Environment Variable | Description |
|---|---|---|
--yes-all | MOOSE_ACCEPT_ALL=1 | Skip all confirmation prompts (renames and destructive operations). |
--yes-destructive | MOOSE_ACCEPT_DESTRUCTIVE=1 | Skip the confirmation prompt for destructive operations. |
--yes-rename | MOOSE_ACCEPT_RENAME=1 | Auto-accept detected column renames as genuine renames. |
--no-auto-backfill-sql | — | Disable automatic backfill SQL generation for versioned tables. |
Drift Detection
Drift occurs when the target database's schema changes between the time you generate a plan and the time you apply it.
How it works:
moose generatewrites the current DB state toremote_state.json.moose migrate(serverless) ormoose prod(server runtime) comparesremote_state.jsonwith the current DB state.- If they differ (hash mismatch), the migration aborts.
Resolution: To fix drift, you must regenerate the plan against the new production state.
# Regenerate to accept the new state
moose generate migration ... --saveSee Also
- Migration Plan Reference - Detailed syntax of
plan.yaml. - Manual Execution - Execute migration plans manually via the
moose migratecommand. - Runtime Execution - Execute migration plans automatically via the Moose Server Runtime.