With this setup:

spring:
  r2dbc:
    url: r2dbc:tc:postgresql:///test?TC_IMAGE_TAG=12-alpine
  flyway:
    url: jdbc:tc:postgresql:12-alpine:///test

a first container (created for migrations) gets down right after successful migration and a new one gets up for testing. One can see this from this log (when one constantly runs docker ps):

    ~                                                                                                                                                                                                                        17:50:33 
❯ docker ps
CONTAINER ID   IMAGE                       COMMAND   CREATED        STATUS                  PORTS                     NAMES
953fc83fc678   testcontainers/ryuk:0.3.1   "/app"    1 second ago   Up Less than a second   0.0.0.0:61792->8080/tcp   testcontainers-ryuk-93520c73-9205-4b22-8ff2-83a1edbf980a

    ~                                                                                                                                                                                                                        17:50:37 
❯ docker ps
CONTAINER ID   IMAGE                       COMMAND                  CREATED         STATUS                  PORTS                     NAMES
818232ec554d   postgres:12-alpine          "docker-entrypoint.s…"   1 second ago    Up Less than a second   0.0.0.0:61802->5432/tcp   intelligent_mirzakhani
953fc83fc678   testcontainers/ryuk:0.3.1   "/app"                   2 seconds ago   Up 1 second             0.0.0.0:61792->8080/tcp   testcontainers-ryuk-93520c73-9205-4b22-8ff2-83a1edbf980a

    ~                                                                                                                                                                                                                        17:50:38 
❯ docker ps
CONTAINER ID   IMAGE                       COMMAND                  CREATED                  STATUS                  PORTS                     NAMES
1fff5f400c40   postgres:12-alpine          "docker-entrypoint.s…"   Less than a second ago   Up Less than a second   0.0.0.0:61812->5432/tcp   festive_mcclintock
953fc83fc678   testcontainers/ryuk:0.3.1   "/app"                   3 seconds ago            Up 2 seconds            0.0.0.0:61792->8080/tcp   testcontainers-ryuk-93520c73-9205-4b22-8ff2-83a1edbf980a

    ~                                                                                                                                                                                                                        17:50:39 
❯ docker ps
CONTAINER ID   IMAGE                       COMMAND                  CREATED         STATUS         PORTS                     NAMES
1fff5f400c40   postgres:12-alpine          "docker-entrypoint.s…"   5 seconds ago   Up 5 seconds   0.0.0.0:61812->5432/tcp   festive_mcclintock
953fc83fc678   testcontainers/ryuk:0.3.1   "/app"                   8 seconds ago   Up 7 seconds   0.0.0.0:61792->8080/tcp   testcontainers-ryuk-93520c73-9205-4b22-8ff2-83a1edbf980a

i.e. 818232ec554d -> 1fff5f400c40.

The same thing happens with in-mem H2 db. But after adding DB_CLOSE_DELAY=-1 parameter the issue gets resolved:

spring:
  r2dbc:
    url: r2dbc:h2:mem:///test;MODE=PostgreSQL
  flyway:
    url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL

Sorry if this should had been ticketed in flyway's repo.

build.gradle.kts:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

buildscript {
    dependencies {
        classpath("com.google.cloud.tools:jib-spring-boot-extension-gradle:0.1.0")
    }
}

plugins {
    id("org.springframework.boot") version "2.5.4"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    id("com.google.cloud.tools.jib") version "3.1.2"
    id("org.flywaydb.flyway") version "7.5.4"
    kotlin("jvm") version "1.5.21"
    kotlin("plugin.spring") version "1.5.21"
}

group = "kz.qbi"
version = System.getenv("CELLS_VERSION")
java.sourceCompatibility = JavaVersion.VERSION_11

repositories {
    mavenCentral()
}

extra["testcontainersVersion"] = "1.16.0"

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
    implementation("org.springframework.boot:spring-boot-starter-validation")
    implementation("org.springframework:spring-jdbc")

    implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")

    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")

    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")

    implementation("org.flywaydb:flyway-core")

    runtimeOnly("org.postgresql:postgresql")
    runtimeOnly("io.r2dbc:r2dbc-postgresql")

    developmentOnly("org.springframework.boot:spring-boot-devtools")

    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude(module = "mockito-core")
    }

    testImplementation("io.projectreactor:reactor-test")

    testImplementation("org.testcontainers:junit-jupiter")
    testImplementation("org.testcontainers:postgresql")
    testImplementation("org.testcontainers:r2dbc")

    testImplementation("com.ninja-squad:springmockk:3.0.1")

    testRuntimeOnly("com.h2database:h2")
    testRuntimeOnly("io.r2dbc:r2dbc-h2")
}

dependencyManagement {
    imports {
        mavenBom("org.testcontainers:testcontainers-bom:${property("testcontainersVersion")}")
    }
}

jib {
    from {
        image = "***"
    }
    to {
        image = "***/cells"
        tags = setOf("$version")
    }
    container {
        creationTime = "USE_CURRENT_TIMESTAMP"
        user = "***"
    }
    pluginExtensions {
        pluginExtension {
            implementation = "com.google.cloud.tools.jib.gradle.extension.springboot.JibSpringBootExtension"
        }
    }
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        @Suppress("SpellCheckingInspection")
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "11"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
    failFast = true
}

tasks {
    build {
        dependsOn(jib)
    }
}

Comment From: wilkinsona

Thanks for the report but I don't think there's anything we can do about this in Spring Boot.

Flyway closes the connection when it has finished the migration. This is the right thing for it to do to avoid leaking a connection. When using Testcontainers this causes its JDBC driver to stop the container. You can control this behaviour by adding TC_DAEMON=true to the jdbc URL:

jdbc:tc:postgresql:12-alpine:///test?TC_DAEMON=true

This will leave the container running. Unfortunately, this isn't sufficient to cause the container to be reused when connecting using R2DBC.

Unless I've missed something, Testcontainers doesn't support container reuse across its R2DBC and JDBC integration. In other words, it doesn't see r2dbc:tc:postgresql:///test?TC_IMAGE_TAG=12-alpine and jdbc:tc:postgresql:12-alpine:///test as referring to the same database. This means that setting TC_DAEMON only results in two containers running at the same time rather than the two running one after the other.

If you'd like to pursue this, I'd recommend raising it with the Testcontainers team to see if they could enable container reuse across r2dbc:tc and jdbc:tc URLs.

/cc @bsideup

Comment From: yerzhant

Thank you for clarification.