88 * [ Creating a container] ( #creating-a-container )
99 * [ Running a container] ( #running-a-container )
1010 * [ Running if not exists] ( #running-if-not-exists )
11- * [ Pulling an image ] ( #pulling-an-image )
11+ * [ Pulling images in parallel ] ( #pulling-images-in-parallel )
1212 * [ Setting network] ( #setting-network )
1313 * [ Setting port mappings] ( #setting-port-mappings )
1414 * [ Setting volume mappings] ( #setting-volume-mappings )
1515 * [ Setting environment variables] ( #setting-environment-variables )
1616 * [ Disabling auto-remove] ( #disabling-auto-remove )
1717 * [ Copying files to a container] ( #copying-files-to-a-container )
1818 * [ Stopping a container] ( #stopping-a-container )
19+ * [ Stopping on shutdown] ( #stopping-on-shutdown )
1920 * [ Executing commands after startup] ( #executing-commands-after-startup )
2021 * [ Wait strategies] ( #wait-strategies )
2122* [ MySQL container] ( #mysql-container )
2223 * [ Configuring MySQL options] ( #configuring-mysql-options )
2324 * [ Setting readiness timeout] ( #setting-readiness-timeout )
2425 * [ Retrieving connection data] ( #retrieving-connection-data )
26+ * [ Flyway container] ( #flyway-container )
27+ * [ Setting the database source] ( #setting-the-database-source )
28+ * [ Configuring migrations] ( #configuring-migrations )
29+ * [ Configuring Flyway options] ( #configuring-flyway-options )
30+ * [ Running Flyway commands] ( #running-flyway-commands )
2531* [ Usage examples] ( #usage-examples )
2632 * [ MySQL with Flyway migrations] ( #mysql-with-flyway-migrations )
2733* [ License] ( #license )
@@ -80,57 +86,69 @@ Starts a container only if a container with the same name is not already running
8086$container->runIfNotExists();
8187```
8288
83- ### Pulling an image
89+ ### Pulling images in parallel
8490
85- Starts pulling the container image in the background. When ` run() ` or ` runIfNotExists() ` is called, it waits for
86- the pull to complete before starting the container. Calling this on multiple containers before running them enables
87- parallel image pulls.
91+ Calling ` pullImage() ` starts downloading the image in the background via a non-blocking process. When ` run() ` or
92+ ` runIfNotExists() ` is called, it waits for the pull to complete before starting the container.
93+
94+ To pull multiple images in parallel, call ` pullImage() ` on all containers ** before** calling ` run() ` on any of
95+ them. This way the downloads happen concurrently:
8896
8997``` php
90- $alpine = GenericDockerContainer::from(image: 'alpine:latest')->pullImage();
91- $nginx = GenericDockerContainer::from(image: 'nginx:latest')->pullImage();
98+ $mysql = MySQLDockerContainer::from(image: 'mysql:8.4', name: 'my-database')
99+ ->pullImage()
100+ ->withRootPassword(rootPassword: 'root');
101+
102+ $flyway = FlywayDockerContainer::from(image: 'flyway/flyway:12-alpine')
103+ ->pullImage()
104+ ->withMigrations(pathOnHost: '/path/to/migrations');
92105
93- $alpineStarted = $alpine->run();
94- $nginxStarted = $nginx->run();
106+ // Both images are downloading in the background.
107+ // MySQL pull completes here, container starts and becomes ready.
108+ $mySQLStarted = $mysql->runIfNotExists();
109+
110+ // Flyway pull already finished while MySQL was starting.
111+ $flyway->withSource(container: $mySQLStarted, username: 'root', password: 'root')
112+ ->cleanAndMigrate();
95113```
96114
97115### Setting network
98116
99117Sets the Docker network the container should join. The network is created automatically when the container is
100- started via ` run() ` or ` runIfNotExists() ` , if it does not already exist.
118+ started via ` run() ` or ` runIfNotExists() ` , if it does not already exist. Networks created by the library are
119+ labeled with ` tiny-blocks.docker-container=true ` for safe cleanup.
101120
102121``` php
103122$container->withNetwork(name: 'my-network');
104123```
105124
106125### Setting port mappings
107126
108- Maps ports between the host and the container. Multiple port mappings are supported .
127+ Maps a port from the host to the container.
109128
110129``` php
111- $container->withPortMapping(portOnHost: 9000, portOnContainer: 9000);
112130$container->withPortMapping(portOnHost: 8080, portOnContainer: 80);
113131```
114132
115133### Setting volume mappings
116134
117- Maps a volume from the host to the container.
135+ Mounts a directory from the host into the container.
118136
119137``` php
120- $container->withVolumeMapping(pathOnHost: '/path/on/ host', pathOnContainer: '/path/in/ container');
138+ $container->withVolumeMapping(pathOnHost: '/host/data ', pathOnContainer: '/container/data ');
121139```
122140
123141### Setting environment variables
124142
125- Sets environment variables inside the container.
143+ Adds an environment variable to the container.
126144
127145``` php
128146$container->withEnvironmentVariable(key: 'APP_ENV', value: 'testing');
129147```
130148
131149### Disabling auto-remove
132150
133- Prevents the container from being automatically removed when stopped.
151+ By default, containers are removed when stopped. This disables that behavior .
134152
135153``` php
136154$container->withoutAutoRemove();
@@ -160,6 +178,23 @@ With a custom timeout:
160178$result = $started->stop(timeoutInWholeSeconds: 60);
161179```
162180
181+ ### Stopping on shutdown
182+
183+ Registers the container to be forcefully removed when the PHP process exits. On shutdown, the following cleanup
184+ is performed automatically:
185+
186+ - The container is killed and removed (` docker rm --force --volumes ` ).
187+ - Anonymous volumes created by the container (e.g., MySQL's ` /var/lib/mysql ` ) are removed.
188+ - Unused networks created by the library are pruned.
189+
190+ Only resources labeled with ` tiny-blocks.docker-container=true ` are affected. Containers, volumes, and networks
191+ from other environments are never touched.
192+
193+ ``` php
194+ $started = $container->run();
195+ $started->stopOnShutdown();
196+ ```
197+
163198### Executing commands after startup
164199
165200Runs commands inside an already-started container.
@@ -192,11 +227,11 @@ Blocks until a readiness condition is satisfied, with a configurable timeout. Th
192227depends on another being fully ready.
193228
194229``` php
195- $mySQLStarted = MySQLDockerContainer::from(image: 'mysql:8.1 ')
230+ $mySQLStarted = MySQLDockerContainer::from(image: 'mysql:8.4 ')
196231 ->withRootPassword(rootPassword: 'root')
197232 ->run();
198233
199- $flywayContainer = GenericDockerContainer::from(image: 'flyway/flyway:11.1.0 ')
234+ $container = GenericDockerContainer::from(image: 'my-app:latest ')
200235 ->withWaitBeforeRun(
201236 wait: ContainerWaitForDependency::untilReady(
202237 condition: MySQLReady::from(container: $mySQLStarted),
@@ -223,7 +258,7 @@ MySQL-specific configuration and automatic readiness detection.
223258| ` withGrantedHosts ` | ` $hosts ` | Sets hosts granted root privileges (default: ` ['%', '172.%'] ` ). |
224259
225260``` php
226- $mySQLContainer = MySQLDockerContainer::from(image: 'mysql:8.1 ', name: 'my-database')
261+ $mySQLContainer = MySQLDockerContainer::from(image: 'mysql:8.4 ', name: 'my-database')
227262 ->withTimezone(timezone: 'America/Sao_Paulo')
228263 ->withUsername(user: 'app_user')
229264 ->withPassword(password: 'secret')
@@ -240,7 +275,7 @@ Configures how long the MySQL container waits for the database to become ready b
240275` ContainerWaitTimeout ` exception. The default timeout is 30 seconds.
241276
242277``` php
243- $mySQLContainer = MySQLDockerContainer::from(image: 'mysql:8.1 ', name: 'my-database')
278+ $mySQLContainer = MySQLDockerContainer::from(image: 'mysql:8.4 ', name: 'my-database')
244279 ->withRootPassword(rootPassword: 'root')
245280 ->withReadinessTimeout(timeoutInSeconds: 60)
246281 ->run();
@@ -264,6 +299,65 @@ $password = $environmentVariables->getValueBy(key: 'MYSQL_PASSWORD');
264299$jdbcUrl = $mySQLContainer->getJdbcUrl();
265300```
266301
302+ ## Flyway container
303+
304+ ` FlywayDockerContainer ` provides a specialized container for running Flyway database migrations. It encapsulates
305+ Flyway configuration, database source detection, and migration file management.
306+
307+ ### Setting the database source
308+
309+ Configures the Flyway container to connect to a running MySQL container. Automatically detects the JDBC URL and
310+ target schema from ` MYSQL_DATABASE ` , and sets the history table to ` schema_history ` .
311+
312+ ``` php
313+ $flywayContainer = FlywayDockerContainer::from(image: 'flyway/flyway:12-alpine')
314+ ->withNetwork(name: 'my-network')
315+ ->withMigrations(pathOnHost: '/path/to/migrations')
316+ ->withSource(container: $mySQLStarted, username: 'root', password: 'root');
317+ ```
318+
319+ The schema and table can be overridden after calling ` withSource() ` :
320+
321+ ``` php
322+ $flywayContainer
323+ ->withSource(container: $mySQLStarted, username: 'root', password: 'root')
324+ ->withSchema(schema: 'custom_schema')
325+ ->withTable(table: 'custom_history');
326+ ```
327+
328+ ### Configuring migrations
329+
330+ Sets the host directory containing Flyway migration SQL files. The files are copied into the container at
331+ ` /flyway/migrations ` .
332+
333+ ``` php
334+ $flywayContainer->withMigrations(pathOnHost: '/path/to/migrations');
335+ ```
336+
337+ ### Configuring Flyway options
338+
339+ | Method | Parameter | Description |
340+ | -------------------------------| -------------| ------------------------------------------------------------------|
341+ | ` withTable ` | ` $table ` | Overrides the history table name (default: ` schema_history ` ). |
342+ | ` withSchema ` | ` $schema ` | Overrides the target schema (default: auto-detected from MySQL). |
343+ | ` withCleanDisabled ` | ` $disabled ` | Enables or disables Flyway's clean command. |
344+ | ` withConnectRetries ` | ` $retries ` | Sets the number of database connection retries. |
345+ | ` withValidateMigrationNaming ` | ` $enabled ` | Enables or disables migration naming validation. |
346+
347+ ### Running Flyway commands
348+
349+ | Method | Flyway command | Description |
350+ | ---------------------| -----------------| ----------------------------------------------|
351+ | ` migrate() ` | ` migrate ` | Applies pending migrations. |
352+ | ` validate() ` | ` validate ` | Validates applied migrations against local. |
353+ | ` repair() ` | ` repair ` | Repairs the schema history table. |
354+ | ` cleanAndMigrate() ` | ` clean migrate ` | Drops all objects and re-applies migrations. |
355+
356+ ``` php
357+ $flywayContainer->migrate();
358+ $flywayContainer->cleanAndMigrate();
359+ ```
360+
267361## Usage examples
268362
269363- When running the containers from the library on a host (your local machine), map the volume
@@ -273,59 +367,32 @@ $jdbcUrl = $mySQLContainer->getJdbcUrl();
273367
274368### MySQL with Flyway migrations
275369
276- The MySQL container is configured and started :
370+ Configure both containers and start image pulls in parallel before running either one :
277371
278372``` php
279- $mySQLContainer = MySQLDockerContainer::from(image: 'mysql:8.1', name: 'test-database')
280- ->withNetwork(name: 'tiny-blocks')
373+ $mySQLContainer = MySQLDockerContainer::from(image: 'mysql:8.4', name: 'test-database')
374+ ->pullImage()
375+ ->withNetwork(name: 'my-network')
281376 ->withTimezone(timezone: 'America/Sao_Paulo')
282- ->withUsername(user: 'xpto')
283- ->withPassword(password: '123')
377+ ->withPassword(password: 'secret')
284378 ->withDatabase(database: 'test_adm')
285- ->withPortMapping(portOnHost: 3306, portOnContainer: 3306)
286379 ->withRootPassword(rootPassword: 'root')
287- ->withGrantedHosts()
288- ->withReadinessTimeout(timeoutInSeconds: 60)
289- ->withoutAutoRemove()
290- ->runIfNotExists();
291- ```
292-
293- With the MySQL container started, retrieve the connection data:
294-
295- ``` php
296- $environmentVariables = $mySQLContainer->getEnvironmentVariables();
297- $jdbcUrl = $mySQLContainer->getJdbcUrl();
298- $database = $environmentVariables->getValueBy(key: 'MYSQL_DATABASE');
299- $username = $environmentVariables->getValueBy(key: 'MYSQL_USER');
300- $password = $environmentVariables->getValueBy(key: 'MYSQL_PASSWORD');
301- ```
302-
303- The Flyway container is configured and only starts after the MySQL container is ** ready** :
304-
305- ``` php
306- $flywayContainer = GenericDockerContainer::from(image: 'flyway/flyway:11.1.0')
307- ->withNetwork(name: 'tiny-blocks')
308- ->copyToContainer(pathOnHost: '/test-adm-migrations', pathOnContainer: '/flyway/sql')
309- ->withVolumeMapping(pathOnHost: '/test-adm-migrations', pathOnContainer: '/flyway/sql')
310- ->withWaitBeforeRun(
311- wait: ContainerWaitForDependency::untilReady(
312- condition: MySQLReady::from(container: $mySQLContainer),
313- timeoutInSeconds: 30
314- )
315- )
316- ->withEnvironmentVariable(key: 'FLYWAY_URL', value: $jdbcUrl)
317- ->withEnvironmentVariable(key: 'FLYWAY_USER', value: $username)
318- ->withEnvironmentVariable(key: 'FLYWAY_TABLE', value: 'schema_history')
319- ->withEnvironmentVariable(key: 'FLYWAY_SCHEMAS', value: $database)
320- ->withEnvironmentVariable(key: 'FLYWAY_EDITION', value: 'community')
321- ->withEnvironmentVariable(key: 'FLYWAY_PASSWORD', value: $password)
322- ->withEnvironmentVariable(key: 'FLYWAY_LOCATIONS', value: 'filesystem:/flyway/sql')
323- ->withEnvironmentVariable(key: 'FLYWAY_CLEAN_DISABLED', value: 'false')
324- ->withEnvironmentVariable(key: 'FLYWAY_VALIDATE_MIGRATION_NAMING', value: 'true')
325- ->run(
326- commands: ['-connectRetries=15', 'clean', 'migrate'],
327- waitAfterStarted: ContainerWaitForTime::forSeconds(seconds: 5)
328- );
380+ ->withGrantedHosts();
381+
382+ $flywayContainer = FlywayDockerContainer::from(image: 'flyway/flyway:12-alpine')
383+ ->pullImage()
384+ ->withNetwork(name: 'my-network')
385+ ->withMigrations(pathOnHost: '/path/to/migrations')
386+ ->withCleanDisabled(disabled: false)
387+ ->withConnectRetries(retries: 5)
388+ ->withValidateMigrationNaming(enabled: true);
389+
390+ $mySQLStarted = $mySQLContainer->runIfNotExists();
391+ $mySQLStarted->stopOnShutdown();
392+
393+ $flywayContainer
394+ ->withSource(container: $mySQLStarted, username: 'root', password: 'root')
395+ ->cleanAndMigrate();
329396```
330397
331398## License
0 commit comments