Skip to content
Lenso
English
Esc
navigateopen⌘Jpreview
On this page

Database and Migrations

Add and run Lenso platform, runtime, built-in, and host-linked module migrations.

Lenso migrations are append-only Rust declarations that point at SQL strings. The migration runner records applied names in platform.schema_migrations.

What runs

The host migration app collects:

  1. platform migrations;
  2. runtime migrations;
  3. enabled first-party linked module migrations;
  4. host-linked module migrations from HostComposition.

Services are separate processes with their own Stores. Installing a Service can record provider and lifecycle state, but the Host migration runner never applies that Service’s private migrations.

Local commands

Generated hosts expose a migration binary:

cargo run --bin migrate

lenso serve runs migrations before starting API and worker unless you pass:

lenso serve --skip-migrate

Production deploys should run the migration job before API and worker:

DATABASE_URL=postgres://... cargo run --bin migrate
DATABASE_URL=postgres://... cargo run --bin api
DATABASE_URL=postgres://... cargo run --bin worker

Add a linked module migration

Keep module SQL beside the module:

src/modules/billing/
  mod.rs
  migrations/
    0001_create_billing_schema.sql

Declare it in the module:

use lenso::host::prelude::*;

pub const MODULE_NAME: &str = "billing";

const MIGRATIONS: &[Migration] = &[Migration {
    name: "billing/0001_create_billing_schema",
    sql: include_str!("migrations/0001_create_billing_schema.sql"),
}];

pub fn linked_module() -> HostLinkedModule {
    HostLinkedModule::manifest_only(MODULE_NAME, manifest, MIGRATIONS)
}

fn manifest() -> ModuleManifest {
    ModuleManifest::builder(MODULE_NAME).build()
}

Wire the module into host composition:

use lenso::host::prelude::*;

mod modules;

pub fn host_composition() -> HostComposition {
    HostBuilder::new()
        .linked_module(modules::billing::linked_module())
        .build()
}

Run:

cargo run --bin migrate
cargo run --bin api

Rules

  • Never rename an applied migration.
  • Never edit an applied migration in a way that changes its meaning.
  • Add a new migration for schema changes.
  • Keep migration names stable and prefixed by owner, such as billing/0002_add_invoice_status.
  • Make SQL idempotent only when it is naturally safe; the runner already tracks applied names.
  • Keep Service Store migrations in the owning Service’s migration Workload and deployment process.

Verify

Check applied migrations:

select name, applied_at
from platform.schema_migrations
order by applied_at;

Run the smallest useful local gate:

just check

If a migration affects OpenAPI-visible routes or DTOs, also run:

just generate
just generated-check

Last updated on August 1, 2026

Was this page helpful?