Simple Kotlin Docker builder for tests.
Dokker is a lightweight Kotlin wrapper around the Docker command line that lets you build, start, stop, execute commands on, and remove Docker containers from your tests.
If you want a container for tests but prefer a Kotlin-idiomatic API, Dokker provides an alternative to libraries like TestContainers.
You must have the Docker command line installed on your system:
https://docs.docker.com/get-docker/
Dokker is distributed through Maven Central.
<dependency>
<groupId>io.github.corbym</groupId>
<artifactId>dokker</artifactId>
<version>0.5.2</version>
<scope>test</scope>
</dependency>dependencies {
testImplementation("io.github.corbym:dokker:0.5.2")
}The dokker { } builder constructs a Docker command line and executes it via java.lang.ProcessBuilder. It returns a DokkerContainer:
val myContainer = dokker {
name("my-container")
detach()
expose("9092", "29092", "9101")
image { "my/container" }
version { "1.1" }
publish("12300" to "12300")
network("local-network")
env(
"MY_CONFIG_PROP" to "hello"
)
}| Method | Docker command |
|---|---|
myContainer.start() |
docker run |
myContainer.stop() |
docker stop |
myContainer.remove() |
docker rm |
You can read back the configuration used to run the container:
val publishedPorts = myContainer.publishedPorts
val exposedPorts = myContainer.expose
val image = myContainer.image
// etc.Dokker can poll a command until an expected response is returned, with a configurable initial delay, polling interval, and timeout. Note that this polls via exec inside the container and does not use Docker's built-in health check functionality.
val myContainer = dokker {
healthCheck {
timeout(Duration.ofSeconds(30))
pollingInterval(Duration.ofSeconds(10))
initialDelay(Duration.ofSeconds(10))
checking { "curl -i --fail http://localhost:8080/health?ready=1" to "HTTP/1.1 200 OK" }
}
onStartup { container, _ ->
container.waitForHealthCheck()
}
}You can run commands against a container with exec:
myContainer.exec("some-command")Use execWithSpacedParameter when a command argument contains spaces (a limitation of ProcessBuilder):
myContainer.execWithSpacedParameter { "some-command" to "argument with spaces" }You can also run any Docker command directly using the String.runCommand extension:
"docker ps".runCommand()DokkerNetwork manages a Docker network. It requires the container runtime process name and the network name. Use DokkerProcessName.processName to pick up whichever process is configured (see Podman support):
val myNetwork = DokkerNetwork(DokkerProcessName.processName, "some-network").also { it.start() }DokkerNetwork implements DokkerLifecycle and can be managed alongside containers.
Both DokkerContainer and DokkerNetwork implement the DokkerLifecycle interface, which allows them to be managed together in sets:
interface DokkerLifecycle {
val name: String
fun start()
fun stop()
fun remove()
fun hasStarted(): Boolean
}You can implement DokkerProvider and use it with JUnit 5's @ExtendWith annotation. The container lifecycle is managed automatically:
| Phase | Action |
|---|---|
| BeforeAll | Validate, run the container, execute startup commands |
| JVM Shutdown | Tear down the container |
Startup validation: if a container with the same name exists but is stopped, Dokker will error rather than restart it — remove the container manually first. If the container is already running, Dokker leaves it alone and will not stop it on shutdown ("if it's not mine, don't touch it").
See ExampleDokkerProvider.kt for a full example.
DokkerExtension supports the JUnit 5 @RegisterExtension annotation. It starts the container in BeforeAll and stops and removes it in AfterAll:
import io.github.corbym.dokker.junit5.DokkerExtension
import io.github.corbym.dokker.junit5.dokkerExtension
import io.github.corbym.dokker.junit5.findFreePort
import org.junit.jupiter.api.extension.RegisterExtension
class ExampleJUnit5RegisteredDokkerTest {
companion object {
val couchbasePort = findFreePort()
@JvmStatic
@RegisterExtension
val server: DokkerExtension = dokkerExtension {
container {
dokker {
name("couchbase")
detach()
debug()
expose(couchbasePort)
publish(couchbasePort to couchbasePort)
image { "arungupta/couchbase" }
version { "latest" }
}
}
doNotStop()
}
}
// ... rest of test ...
}findFreePort() is a utility function that returns a random available port as a String. You can prevent shutdown and removal by calling doNotStop() and doNotRemove() in the dokkerExtension { } builder.
See ExampleJUnit5RegisteredDokkerTest for a full example.
Dokker supports alternative container runtimes such as Podman. The process used can be configured in three ways (in order of precedence):
- In code:
dokker { process("podman") } - Via the environment variable
DOKKER_PROCESS - Default:
docker
Please open an issue or fork the repository and open a PR for any changes you wish to be considered.