Bootiful Containers

with Spring-Boot and Docker


  • Run Spring-Boot Apps inside a Docker container
    • Make Spring-Boot Apps configurable using Environment Variables
    • Build your Spring-Boot Docker image using Gradle
    • Deploy the image via docker-compose
  • Unit- and Integration-Test your Spring-Boot App with Spock and Docker
  • Deploy and configure Spring-Boot microservices using docker-compose

Run Spring-Boot Apps inside a Docker container



  • Opinionated view on Spring
  • Lots of autoconfigure magic
  • Useful features and defaults for microservices and 12-factor apps
    • Supports externalized configuration using environment variables
    • Easy to use health and metrics endpoints
    • Use of embedded Tomcat allows for-self contained application bundle
  • Works well inside a container
public class MyBean {

    private String name;

    // ...

$ export FOO_BAR=mobydock

Externalized Configuration

public class MyBeanProperties {

    private String name;

    public String getName() { ... }

    public void setName(String name) { ... }


Externalized Configuration

Type-safe Configuration Properties


Refactoring "Hello World"

Use Type-safe Configuration Properties

  • Create a GreeterConfiguration¬†component using the @ConfigurationProperties annotation

  • Inject your bean into the greeter
  • Run the hello-world application to verify
$ GREETER_NAME=gradle-runner ./gradlew bootrun
$ git clone
  • Groovy DSL for build management
  • Rich plugin ecosystem
  • Allows to use Maven dependencies
  • A bit like Maven if Maven was cool ;)

buildscript {
  ext {
    springBootVersion = '1.5.1.RELEASE'
  repositories {
  dependencies {
    classpath 'com.bmuschko:gradle-docker-plugin:3.0.5'

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

sourceCompatibility = 1.8

repositories {

dependencies {
apply plugin: 'com.bmuschko.docker-remote-api'

import com.bmuschko.gradle.docker.tasks.image.Dockerfile
import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage

def dockerBuildDir = 'build/docker/'
def applicationJar = "${archivesBaseName}.jar"

task copyJar(type: Copy) {
    dependsOn bootRepackage
    from "$libsDir/$applicationJar"
    into dockerBuildDir

task createDockerfile(type: Dockerfile) {
    dependsOn copyJar
    destFile = project.file(dockerBuildDir + "Dockerfile")

    from 'openjdk:8-jre'
    copyFile(applicationJar, "/")
    entryPoint "sh", "-c", 
      "java \$JAVA_OPTS -jar /$applicationJar"

task buildImage(type: DockerBuildImage) {
    dependsOn createDockerfile
    inputDir = createDockerfile.destFile.parentFile
    tag = 'devops/hello-world'


"Hello World" from a container with externalized configuration

$ docker run \
--rm \
--name "hello-world-test" \
-e "GREETER_NAME=docker-command-line" \
$ ./gradlew buildImage

Build your image

(or via IDE...)

Run a container from your image

  • Try using different names!
  • What happens if you omit the environment variable?
version: "3"

    image: dog.bootcamp/greeter
      GREETER_NAME: docker-compose

Deployment with Docker-Compose

Like before:

  • image is the tag from the build.gradle

  • Configure application properties via environment variable


Deploy "Hello World" via the docker-compose file

$ docker-compose up
  • Add a second "greeter"-service greeting another name
  • Run a few times
    • Make an observation about the greeting order!


  • We can externalize the configuration of spring-boot applications
  • We can configure those properties via environment variables
  • We can build a container from our app via gradle using a docker-plugin
  • We can pass the property values both from docker-cli and docker-compose
  • We can not predict any timing behaviour of docker-compose services on startup

Unit- and Integration-Test your Spring-Boot App

with Spock and Docker


class HelloSpockSpec extends spock.lang.Specification {
  def "length of Spock's and his friends' names"() {
    name.size() == length

    name     | length
    "Spock"  | 5
    "Kirk"   | 4
    "Scotty" | 6
class ApplicationTests extends Specification {

    def "should boot up"() {



Writing Spock tests

  • Extend the BookRepositoryTests by a test that ensures that a book without IBAN can't be stored
  • Extend the BookRepositoryTests by a test that ensures that a book with IBAN can be found after it was stored
$ git clone
$ gradlew check

Run the test suite with:

For better visualized results, run the gradle task from your IDE:


  • Use Docker containers in your JUnit tests¬†
  • Spock extension also available ;)
class JpaTests extends Specification {

    PostgreSQLContainer postgresContainer = new PostgreSQLContainer()

  • PostgreSQLContainer will wait until the database is fully operational


Using Testcontainers

  • Change the BookRepositoryTests to use a MySQL 5.5 container instead of a postgres
    • Keep track: How many project files do you have to change?
    • Also change to the latest Jitpack version of the library

Your client tells you they're - by some obscure policy - not allowed to run a postgres database. They are instead confined to using MySQL 5.5. No way you're going to install that on your local machine for testing.


  • We can use the Testcontainers library to use containers for integration tests
    • This brings our test and production environment closer together
  • We can easily simulate other deployment scenarios
  • We can isolate the test environment from the host system
    • This allows for more stable integration tests between developer machines and your continuous integration server
    • Any developer can run the same integration tests - no weird dependency management, no "works on my machine", no excuses

Deploy and configure Spring-Boot microservices

using docker-compose



  • Our bookstore application depends on creating the schema at startup...

  • In our integration test, using the PostgreSQLContainer ensures the database is ready.

  • But how do we ensure the database is ready in our deployment scenario?

'Correct' answer:

"We don't"

  • Rather build applications that can easily recover from network failure
  • Rather fail to boot dependent services in a swarm environment
    • service will be restarted until it works
  • BUT there are legacy applications...
  • ... and more often than not, framework mechanisms don't play nice with that idea

Introducing Dockerize

  • Delay your application start until a port is reachable
version: "2"
    image: postgres
    image: devops/myservice
      - "db"
      - dockerize
      - -wait
      - tcp://db:5432
      - -timeout 
      - 120s
      - java
      - -jar
      - myservice.jar

Installing Dockerize

  • In your createDockerfile-task:

task createDockerfile(type: Dockerfile) {
	// Install dockerize
	environmentVariable('DOCKERIZE_VERSION', 'v0.3.0')
	runCommand 'apt-get update && apt-get install -y wget'
	runCommand 'wget \$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    		&& tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    		&& rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz'

Bootiful Containers

By Kevin Wittek

Loading comments...

More from Kevin Wittek