Skip to content

Gate the Flyway run on an environment flag and add ephemeral MySQL storage. - #31

Merged
gustavofreze merged 2 commits into
mainfrom
feat/gated-flyway-and-ephemeral-mysql
Oct 15, 2026
Merged

gustavofreze merged 2 commits into
mainfrom
feat/gated-flyway-and-ephemeral-mysql

Conversation

@gustavofreze

@gustavofreze gustavofreze commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Summary

The integration test bootstrap of the services that consume the library wrapped MySQL and Flyway in if (getenv('RUN_MIGRATIONS') === '1'), kept the started MySQL in a variable only to hand it to Flyway, and repeated the root credentials the MySQL definition already holds. Each bootstrap also chose its own readiness timeout and connection retries.

The suites were slow too. MySQL wrote its data and binary log to the container disk, and Flyway waited for MySQL to accept connections a second time after runIfNotExists() had already waited.

What changes

  1. FlywayDockerContainer::withSource() takes the MySQLDockerContainer definition. Flyway starts it with runIfNotExists() only when a command runs, and reads the root user and password from it.
  2. onlyWhenEnabled(variable: 'RUN_MIGRATIONS') returns a FlywayContainerGated. It reads the variable when a command runs. When the variable is not 1, repair, migrate, validate and cleanAndMigrate return null and no Docker command runs, neither for Flyway nor for MySQL. The ungated Flyway keeps returning a non-null ExecutionCompleted.
  3. withCredentials(password:, username:) overrides the credentials read from the MySQL definition.
  4. MySQLDockerContainer::withEphemeralStorage() mounts /var/lib/mysql on tmpfs and passes --skip-log-bin. Measured with mysql:8.4, the boot went from 14 to 20 seconds down to 10 to 12 seconds, and a batch of 300 TRUNCATE and INSERT statements from 14 to 17 seconds down to 2 seconds.
  5. GenericDockerContainer::withTmpfsMount(pathOnContainer:) mounts a tmpfs in any container.
  6. Flyway no longer waits for MySQL readiness again before it runs.
  7. DockerCommandExecutionFailed has a private constructor and a from(reason:, command:) factory, like the other exceptions.

The bootstrap of a service becomes one chain:

FlywayDockerContainer::from(image: 'flyway/flyway:13.9.0-alpine')
    ->withSource(
        container: MySQLDockerContainer::from(image: 'mysql:8.4')
            ->withName(name: $databaseHost)
            ->withNetwork(name: $network)
            ->withDatabase(database: 'ledger_test')
            ->withRootPassword(rootPassword: 'root')
            ->withEphemeralStorage()
    )
    ->withNetwork(name: $network)
    ->withMigrations(pathOnHost: sprintf('%s/../database/migrations', __DIR__))
    ->withMigrationNamingValidation()
    ->onlyWhenEnabled(variable: 'RUN_MIGRATIONS')
    ->cleanAndMigrate();

Validated against two consuming services with the local source in their vendor, together with Infection reusing the PHPUnit coverage. make tests went from 1443 to 947 seconds in one and from 424 to 312 seconds in the other, with every mutant still killed.

Public API

The change is breaking, so the release is 5.0.0.

Changed: FlywayDockerContainer::withSource(password:, username:, container:) becomes withSource(container:) and takes a MySQLDockerContainer definition instead of a started container. DockerCommandExecutionFailed is built through from(reason:, command:).

Added: FlywayDockerContainer::onlyWhenEnabled(), FlywayContainerGated, FlywayDockerContainer::withCredentials(), MySQLDockerContainer::withEphemeralStorage() and GenericDockerContainer::withTmpfsMount().

Related issue

None.

Checklist

  • Tests added or updated. The gated run with the variable enabled, disabled, unset and set to 01, the absence of any Docker command when it is off, the credentials read from the definition and their override, the tmpfs mount and the ephemeral storage arguments, and a single readiness ping before Flyway.
  • Documentation updated when applicable. README: the source as a MySQL definition, the gated run, the ephemeral storage, the tmpfs mounts and the bootstrap as one chain. The section to migrate from 3.x and the FAQ entry on the Flyway container left the README.
  • make review passes.
  • make tests passes. 182 tests, lines 100% (777/777), MSI 100% (331 of 331 killed).

…SQL storage.

The test bootstrap of the consuming services wrapped MySQL and Flyway
in an if on RUN_MIGRATIONS and repeated the root credentials the
MySQL definition already holds. Flyway now takes the MySQL definition,
starts it only when a command runs, and onlyWhenEnabled() moves the
run behind the flag in the same chain.
@gustavofreze gustavofreze self-assigned this Oct 15, 2026
@gustavofreze
gustavofreze merged commit e6e7ad6 into main Oct 15, 2026
5 checks passed
@gustavofreze
gustavofreze deleted the feat/gated-flyway-and-ephemeral-mysql branch October 15, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant