Skip to main content
Winter Boot includes a standalone SQL migration tool that executes plain .sql files against your configured datasources and tracks each applied migration in a winter_migrations table. Version state is determined by the relative file path — if a path has already been recorded in the tracking table, the file is skipped on subsequent runs. This gives you repeatable, idempotent deploys without requiring any migration-specific DSL.
The same runner also applies JSON-based OpenSearch index templates, ISM policies, and index schemas. See OpenSearch Migrations.

Quick Start

Follow these steps to apply your first migration:
1

Enable migrations in application.yml

Add migrations.enabled: true to the datasource you want to migrate.
application.yml
2

Create the migrations directory

Create a sub-folder named after your datasource under your migrations root.
3

Add a SQL migration file

Create /migrations/defaultdb/001-init-schema.sql with your schema statements:
001-init-schema.sql
4

Run the migrations

Execute the migration tool from the CLI or using the pre-built PHAR.
5

Verify the migration was applied

Query the winter_migrations tracking table to confirm the file was recorded:

Configuration

Standalone Datasource

Enable migrations on any datasource by setting migrations.enabled: true. You can enable it on multiple datasources simultaneously — each datasource’s folder is migrated independently.
application.yml

Multi-Tenant Datasource

Migrations work with multi-tenant datasources too. The tool runs each SQL file against every tenant returned by TenantDataSourceProvider::getAllTenantIds().
application.yml

Native CLI Mode (useCli)

By default, the migration tool parses SQL files in PHP and executes each statement individually. For complex SQL files that contain transactions, PL/SQL or T-SQL blocks, stored procedures, triggers, or mixed DDL/DML, set useCli: true to hand the entire file to the database’s native command-line client instead.
application.yml
When useCli: true is set, Winter Boot selects the appropriate CLI tool based on the DSN scheme:
When using useCli: true, make sure the corresponding CLI binary (e.g. psql, mysql) is installed on the machine running migrations and is available on the system PATH.

Directory Structure

Organise migration files under a root directory with one sub-folder per datasource name. Files are executed in alphabetical order — use numeric or date-based prefixes to enforce a deterministic sequence.
Each top-level sub-folder must exactly match the datasource name in application.yml. The match is case-sensitive on Linux. SQL files must use the .sql extension. Sub-folders for release-based organisation are supported and scanned recursively.

The winter_migrations Tracking Table

The framework automatically creates a winter_migrations table the first time it runs against a datasource. Each successfully executed migration is recorded by its relative path from the migrations root (e.g. defaultdb/001-init-schema.sql).
On subsequent runs, the tool queries COUNT(*) WHERE migration_path = ?. Any file that already has a row is skipped entirely, making every run idempotent.

SQL File Format

Each file may contain one or more SQL statements. The parser supports both # and -- style line comments and ignores blank lines. Every statement must be terminated with a semicolon (;).
orders.sql

CLI Reference

Building the PHAR

Build a self-contained PHAR for use in Docker images or CI pipelines:

Kubernetes Init Container

Run migrations as an init container so your schema is always up-to-date before the main application pod starts:
pod.yaml
Baking winter-migrations-app.phar directly into your application image means migrations and the application always share the same versioned artifact, eliminating drift between schema and code.

Migration Execution Flow

Understanding the exact sequence helps you predict behaviour and debug failures:
1

Load configuration

Read application.yml and collect all datasources with migrations.enabled: true.
2

Locate SQL folder

For each datasource, resolve the SQL folder at {sqlBasePath}/{datasource-name}/.
3

Scan and sort

Recursively scan for .sql files; sort alphabetically within each directory level.
4

Enumerate tenants (multi-tenant only)

For multi-tenant datasources, retrieve all tenant IDs from TenantDataSourceProvider::getAllTenantIds().
5

Check tracking table

For each file (and each tenant, if multi-tenant): query winter_migrations to check whether the file has already been applied.
6

Execute new migrations

If not yet recorded, execute the file using PHP parser mode or native CLI mode (useCli: true).
7

Record success

Insert a row into winter_migrations on successful execution.
8

Halt on failure

Stop immediately on the first failure. No automatic rollback is performed — you must fix the failing statement and re-run.

Troubleshooting

The directory under --sqlPath does not contain a sub-folder that exactly matches the datasource name. Verify your folder structure is {sqlPath}/{datasource-name}/ and confirm the datasource name in application.yml exactly matches the folder name — the comparison is case-sensitive on Linux.
Check that migrations.enabled: true is present in application.yml for the target datasource, and that the file has a .sql extension. Then query the tracking table:
If the file has already been recorded and you need to re-run it, delete its row from winter_migrations. Use this with care in production environments.
Install the appropriate client package and confirm the binary is on the system PATH:
Fix the failing SQL statement and manually roll back or compensate any partial changes. Re-run the migration tool — files already recorded in winter_migrations are skipped, so only the failed (and unrecorded) file will be re-attempted.