Skip to main content
Winter Boot’s OpenSearch migration runner applies JSON-based index templates, ISM policies, and index schemas against configured OpenSearch connections. It tracks execution the same way SQL migrations do, so you can version-control your search cluster schema alongside your database schema.
The migration runner is part of the framework and does not require the OpenSearch library to be enabled at runtime. It reads OpenSearch connection details directly from application.yml or from a module config file such as opensearch-config.yml.

Quick start

1

Enable migrations on the connection

Add migrations.enabled: true to your OpenSearch connection. This works both for connections declared directly in application.yml and for connections defined in a module config file such as opensearch-config.yml.
opensearch-config.yml
2

Create the migration directory

Migrations live in one folder per connection name, so a connection named opensearch reads files from /migrations/opensearch/.
3

Add JSON files

Create /migrations/opensearch/sf-entities-template.json:
4

Run migrations

Omit -m (or pass -m sql) to run SQL migrations instead. The --sqlPath argument is the shared migrations root for both types.

Migration file types

An explicit envelope ({method, path, body?, name?}) is always sent as a raw request. Otherwise the filename decides first: For *-template.json, a full composable template body (with index_patterns) is sent as-is. A raw settings / mappings / aliases definition is wrapped into a valid template, defaulting index_patterns to ["{name}*"] (which covers the bare index plus dated variants like sf-events-2026.09.04). Put an explicit index_patterns list in the file to override that default. For other filenames the content decides: The resource {name} defaults to the file name without .json and without a trailing -template, -policy, or -index suffix, so sf-entities-template.json manages the sf-entities index (or template, or policy, depending on content). Index names are lowercased because OpenSearch requires lowercase index names. Files are executed in alphabetical order (including sub-folders such as release-1.0/), and the relative path is the migration identity.

Idempotency

Executed migrations are recorded as documents in the winter_migrations index (one document per connection plus relative path, the OpenSearch equivalent of the winter_migrations SQL table), together with a SHA-256 hash of the file content. Re-runs skip files whose hash is unchanged and re-apply files whose content changed, so editing a template or policy file and re-running updates it in place. Every operation is also safe to replay:
  • Index template and ISM policy PUTs are natural upserts.
  • Index creation first checks whether the index already exists, so re-running against a cluster whose winter_migrations index was lost succeeds instead of failing with resource_already_exists_exception.
Re-applying an index-schema file does not alter a live index: OpenSearch applies settings and mappings at index-creation time. To change a live index, add a new migration file with the appropriate API call (for example an envelope {method: PUT, path: /<index>/_mapping, ...} to add a field).

Connection configuration keys

  • hosts (required)
  • username / password (basic auth)
  • ssl_verification (default true)
  • timeout and connect_timeout in seconds
  • proxy (explicit proxy URL, or false to disable proxying)

Notes

  • The HTTP client is self-contained (no opensearch-php dependency). It uses the Swoole coroutine HTTP client inside a Swoole coroutine (the same approach as the winter-opensearch SwooleHttpHandler), cURL when available, and the PHP stream wrapper as a fallback. Hosts are tried in order on transport failure.
  • First failure stops all migrations. There is no automatic rollback, matching the SQL migration behaviour.