/spring-boot-docker

🐳 Spring Boot with Docker

Primary LanguageKotlin

CI Java Kotlin SpringBoot

Spring Boot with Docker

In this sample we will create a 🍀 Spring Boot Application with a simple /hello endpoint and then distribute it as a 🐳 Docker image

And of course we want to ensure the Docker image works, so we will test it using Testcontainers 🤩

Diagram

Ready? Let's go!

You can browse older versions:

Develop

1. We start at Spring Initialzr and create an empty Spring Boot project with Webflux and Kotlin

2. Then we add this simple test

@SpringBootTest(webEnvironment = RANDOM_PORT)
class ApplicationTests {

  @LocalServerPort
  private var port: Int = 0

  @Test
  fun `should say hello`() {
    val responseBody = WebClient.builder()
      .baseUrl("http://localhost:$port").build()
      .get().uri("/hello")
      .exchangeToMono { response ->
        assertThat(response.statusCode()).isEqualTo(HttpStatus.OK)
        response.bodyToMono(String::class.java)
      }.block()
    assertThat(responseBody).isEqualTo("hello!")
  }
}

3. And we add this simple implementation ...

@RestController
class HelloController {
  @GetMapping("/hello")
  fun hello() = "hello!"
}

... and now our test is 🟩👏

4. Next we need to generate a docker image ... 🤔

Some alternatives are documented in Spring Boot with Docker and Topical Guide on Docker

And luckily for us, it is as easy as use the task bootBuildImage of the Spring Boot's Gradle plugin:

./gradlew bootBuildImage

So far so good! 😁

Now we have to test the generated docker image ...

5. First we use org.unbroken-dome.test-sets to create a new test source root named container-test:

plugins {
  id("org.unbroken-dome.test-sets") version "x.x.x"
}

testSets {
  "container-test"()
}

tasks["container-test"].dependsOn("bootBuildImage")

Note that bootBuildImage task is executed before container-test task, so we ensure we are always testing the docker image we've just built from the current source code

6. Then we create test using Testcontainers and JUnit5

@Testcontainers
class ApplicationContainerTests {

  companion object {

    private const val APP_PORT = 8080

    @Container
    private val app = GenericContainer(System.getProperty("docker.image"))
      .withExposedPorts(APP_PORT)
  }

  @Test
  fun `should say hello`() {
    // ...
  }
}

7. Last thing we need to do is set the value of the system property docker.image before running any test:

tasks.withType<Test> {
  useJUnitPlatform()
  systemProperty("docker.image", "${project.name}:${project.version}")
}

As ${project.name}:${project.version} is the default value used by bootBuildImage task

And that is all! Happy coding! 💙

Test this demo

Test the application without assembling it:

./gradlew test

Build the application as a container and test it:

./gradlew container-test

Run this demo

Run the application without assembling it:

./gradlew bootRun

Build the application as a fatjar and run it with java:

./gradlew bootJar
java -jar ./build/libs/spring-boot-docker-0.0.1-SNAPSHOT.jar

Build the application as a container and run it with docker:

./gradlew bootBuildImage
docker run -p 8080:8080 --rm spring-boot-docker:0.0.1-SNAPSHOT

In either case, call the /hello endpoint:

curl -i http://localhost:8080/hello