agentsclimarketplace

Kora database migration

Skill kora-projects/kora-skills/plugins/kora-v1/skills/kora-database-migration

Kora database migration modules for Flyway and Liquibase that run schema migrations on application startup. Covers the FlywayJdbcDatabaseModule and LiquibaseJdbcDatabaseModule, the database-flyway and database-liquibase artifacts, FlywayConfig/LiquibaseConfig keys (locations, changelog, executeInTransaction, validateOnMigrate, mixed), versioned SQL scripts, and the recommended out-of-process strategy (Flyway Gradle plugin, K8s Job, CI) for horizontally scaled services. Use when wiring migrations into a @KoraApp, picking Flyway vs Liquibase, configuring flyway/liquibase config sections, or fixing checksum/race-condition failures on startup.From its SKILL.md

Install
npx -y skills add kora-projects/kora-skills --skill kora-database-migration

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

3 things to look at

  • reads credentialsReads from 3 credential sources: `POSTGRES_JDBC_URL` and 2 more.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
  • runs commandsInstructs the agent to run 1 command, including `./gradlew flywayMigrate`.

SKILL.md

11.6 KB, ~2.8k tokens by cl100k_base, as published. Nobody here has run it

Kora Database Migration — Flyway and Liquibase

Kora ships two optional modules that run database migrations during the application's Lifecycle startup, on top of the JDBC datasource:

  • Flyway — FlywayJdbcDatabaseModule, artifact database-flyway, config class FlywayConfig.
  • Liquibase — LiquibaseJdbcDatabaseModule, artifact database-liquibase, config class LiquibaseConfig.

Both require the JDBC module (database-jdbc). Migrations execute through a Lifecycle interceptor (FlywayJdbcDatabaseInterceptor / LiquibaseJdbcDatabaseInterceptor) before dependent components are initialized.

Production note. Kora's maintainers do not recommend running migrations from inside the application when the service scales horizontally — every replica and every restart would trigger a migration. Prefer the out-of-process strategy (Flyway Gradle plugin locally, K8s Job / CI in production). See Out-of-process migrations. The canonical kora-java-crud example wires only JdbcDatabaseModule and runs Flyway via the Gradle plugin, not the migration module.


Quick Start (Flyway, in-app)

1. Dependencies (build.gradle). Kora artifacts inherit the version from the kora-parent BOM — never version them individually. The annotation processor is mandatory: without it @KoraApp generates nothing.

dependencies {
    koraBom platform("ru.tinkoff.kora:kora-parent:1.2.17")
    annotationProcessor "ru.tinkoff.kora:annotation-processors"

    implementation "ru.tinkoff.kora:database-jdbc"     // required datasource
    implementation "ru.tinkoff.kora:database-flyway"   // Flyway migration module
}

Kotlin: replace the processor with ksp "ru.tinkoff.kora:symbol-processors".

2. Plug the module into @KoraApp:

import ru.tinkoff.kora.common.KoraApp;
import ru.tinkoff.kora.config.hocon.HoconConfigModule;
import ru.tinkoff.kora.database.flyway.FlywayJdbcDatabaseModule;

@KoraApp
public interface Application extends
        HoconConfigModule,
        FlywayJdbcDatabaseModule { }

3. Migration script at src/main/resources/db/migration/V1__setup_tables.sql:

CREATE TABLE IF NOT EXISTS categories (
    id   BIGINT  NOT NULL GENERATED ALWAYS AS IDENTITY,
    name VARCHAR NOT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS pets (
    id          BIGINT   NOT NULL GENERATED ALWAYS AS IDENTITY,
    name        VARCHAR  NOT NULL,
    status      SMALLINT NOT NULL,
    category_id BIGINT   NOT NULL REFERENCES categories(id),
    PRIMARY KEY (id)
);

4. Configure (application.conf). The JDBC datasource and the flyway section both read environment variables for credentials:

db {
  jdbcUrl  = ${POSTGRES_JDBC_URL}
  username = ${POSTGRES_USER}
  password = ${POSTGRES_PASS}
}

flyway {
  locations = ["db/migration"]   // default; classpath dir holding V*.sql
}

On startup Flyway scans db/migration, applies pending scripts in version order, and records them in its flyway_schema_history table (created automatically).


Flyway vs Liquibase

FlywayLiquibase
Artifactdatabase-flywaydatabase-liquibase
ModuleFlywayJdbcDatabaseModuleLiquibaseJdbcDatabaseModule
Config classFlywayConfigLiquibaseConfig
Script formatversioned SQL (V<n>__<name>.sql)changelog (XML default; formatted SQL, YAML, JSON)
Default locationdb/migrationdb/changelog/db.changelog-master.xml
Rollbacknot in the open-source Flywaydeclarable in the changelog
Picked whenthe default; simplest, SQL-onlyyou need declarative rollback or already have a Liquibase changelog

Pick one module. Plug exactly one of the two into @KoraApp — they both manage the same schema and must not run together.


Flyway configuration

The keys below are the full FlywayConfig surface (defaults shown):

flyway {
  enabled              = true            // run migrations on startup; false disables
  locations            = ["db/migration"] // classpath dirs with V*.sql scripts
  executeInTransaction = true            // wrap each migration in a transaction
  validateOnMigrate    = true            // verify checksums of applied scripts first
  mixed                = false           // allow transactional + non-transactional in one run
  configurationProperties {}             // raw key/value passed to Flyway#configurationProperties
}
  • mixed = true is needed only for databases where some statements cannot run inside a transaction (PostgreSQL, Aurora PostgreSQL, SQL Server, SQLite) — e.g. CREATE INDEX CONCURRENTLY. When enabled, the whole run executes without a transaction.
  • configurationProperties is the escape hatch for any native Flyway option not surfaced as a typed key (e.g. flyway.defaultSchema).

See references/flyway-migration-reference.md for script naming rules, multiple locations, and checksum recovery.


Liquibase configuration

LiquibaseConfig exposes a single key: the path to the master changelog.

liquibase {
  changelog = "db/changelog/db.changelog-master.xml"  // default master file path
}

Point it at a formatted-SQL master if you prefer SQL over XML:

liquibase {
  changelog = "db/changelog/db.changelog-master.sql"
}

Liquibase formatted SQL requires a header line and a --changeset directive per change; rollback is declared with --rollback:

--liquibase formatted sql

--changeset developer:1
CREATE TABLE users (
    id    BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE
);
--rollback DROP TABLE users;

--changeset developer:2
CREATE TABLE orders (
    id      BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    user_id BIGINT NOT NULL REFERENCES users(id),
    total   DECIMAL(10,2) NOT NULL
);
--rollback DROP TABLE orders;

See references/liquibase-migration-reference.md for changeset directives (contexts, labels), include files, and rollback patterns.


Out-of-process migrations (recommended)

For horizontally scaled services, disable the in-app module and run migrations once, before the app starts.

Local development — Flyway Gradle plugin (as in kora-java-crud):

plugins {
    id "org.flywaydb.flyway" version "8.4.2"
}

flyway {
    url       = "jdbc:postgresql://localhost:5432/mydb"
    user      = "postgres"
    password  = "postgres"
    locations = ["classpath:db/migration"]
}
./gradlew flywayMigrate

Production — Kubernetes Job runs the migration container once before rolling out the app, and the app keeps flyway.enabled = false:

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
spec:
  backoffLimit: 1
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: flyway
          image: flyway/flyway:8.4.2
          args: ["migrate"]
          env:
            - name: FLYWAY_URL
              valueFrom: { configMapKeyRef: { name: db-config, key: url } }

CI — invoke ./gradlew flywayMigrate (or the Liquibase equivalent) as a pipeline step against the target database before deployment.

If you keep the in-app module in a scaled deployment, two replicas may race on the first migration. Flyway serializes via its history table lock, but a failed/locked run can still block startup — out-of-process avoids this entirely.


Testing migrations

The kora-java-crud example does not call Flyway by hand in tests. It uses the Testcontainers extension io.goodforgod:testcontainers-extensions-postgres, which runs the same db/migration scripts against a throwaway Postgres container:

@TestcontainersPostgreSQL(
        network = @Network(shared = true),
        mode = ContainerMode.PER_RUN,
        migration = @Migration(
                engine = Migration.Engines.FLYWAY,
                apply  = Migration.Mode.PER_METHOD,
                drop   = Migration.Mode.PER_METHOD))
@KoraAppTest(Application.class)
class IntegrationTests implements KoraAppTestConfigModifier {

    @ConnectionPostgreSQL
    private JdbcConnection connection;

    // @TestComponent-injected repositories use the migrated schema
}

Test dependency:

testImplementation "io.goodforgod:testcontainers-extensions-postgres:0.13.1"
testImplementation "org.testcontainers:junit-jupiter:1.21.4"
testImplementation "ru.tinkoff.kora:test-junit5"

Migration.Engines.LIQUIBASE switches the same extension to Liquibase. See kora-testing-junit-java for @KoraAppTest.


What's in references/ and assets/

FilePurpose
references/flyway-migration-reference.mdScript naming, multiple locations, transactional vs mixed, checksum recovery
references/liquibase-migration-reference.mdFormatted-SQL changesets, rollback, contexts/labels, includes
assets/V1__initial_schema.sql.templateFlyway initial migration starter
assets/002-orders-table.sql.templateFollow-up Flyway/Liquibase change starter
assets/db.changelog-master.sql.templateLiquibase formatted-SQL master changelog
assets/README.mdHow to copy and rename the templates

Common pitfalls

SymptomFix
No migrations run on startupflyway.enabled/module present; locations points at a real classpath dir holding V*.sql
"Validation failed. Checksum changed"An applied script was edited. Never edit applied migrations — add a new versioned script, or repair the history out-of-process
CREATE INDEX CONCURRENTLY fails inside a transactionSet flyway.mixed = true (whole run becomes non-transactional)
Two replicas race on first deployDisable the in-app module; run migrations via Gradle plugin / K8s Job / CI
Liquibase formatted SQL ignoredFirst line must be --liquibase formatted sql; each change needs --changeset <author>:<id>
Both modules plugged into @KoraAppKeep exactly one of FlywayJdbcDatabaseModule / LiquibaseJdbcDatabaseModule

Related skills

Source of truth

What ships with it: 6 files

20.2 KB alongside SKILL.md

evals/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.