<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>docker Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/tag/docker/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>Kotlin &#38; Backend Tutorials - Learn Through Practice.</description>
	<lastBuildDate>Wed, 16 Apr 2025 04:50:05 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://blog.codersee.com/wp-content/uploads/2025/04/cropped-codersee_logo_circle_2-32x32.png</url>
	<title>docker Archives - Codersee blog- Kotlin on the backend</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Testing Micronaut Application in Kotlin</title>
		<link>https://blog.codersee.com/testing-micronaut-appplication-kotlin/</link>
					<comments>https://blog.codersee.com/testing-micronaut-appplication-kotlin/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Wed, 10 Jan 2024 08:24:44 +0000</pubDate>
				<category><![CDATA[Micronaut]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[JUnit 5]]></category>
		<category><![CDATA[MockK]]></category>
		<category><![CDATA[REST Assured]]></category>
		<category><![CDATA[Testing]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9508620</guid>

					<description><![CDATA[<p>In this, practical tutorial, I will show you how to test a REST API created with Micronaut, Kotlin and MongoDB.</p>
<p>The post <a href="https://blog.codersee.com/testing-micronaut-appplication-kotlin/">Testing Micronaut Application in Kotlin</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Hello and welcome to my next article, in which I will show you <strong>how to test a Micronaut application in Kotlin</strong>. </p>



<p>To be even more specific- we will work with an application that exposes a <strong>REST API</strong> and connects to <strong>MongoDB</strong>. If you would like to learn how to do that step-by-step, then you can check out <a href="https://blog.codersee.com/micronaut-with-mongodb-and-kotlin-revisited-2022/" target="_blank" rel="noreferrer noopener">my other article</a>. (But don&#8217;t worry, we will see its code snippets in this tutorial, too). </p>



<p>For testing, we&#8217;re going to use <strong>JUnit 5</strong>, <strong>MockK</strong>, <strong>REST Assured</strong>, as well as the <strong>@MicronautTest</strong> and <strong>Micronaut Test Resources</strong>.</p>



<h2 class="wp-block-heading" id="h-video-tutorial">Video Tutorial</h2>



<p>If you prefer <strong>video content</strong>, then check out my video:</p>



<div style="text-align: center; width: 90%; margin-left: 5%;">
<a href="https://blog.codersee.com/testing-micronaut-appplication-kotlin/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FH-LbDsi4qKg%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /></p></div>



<p>If you find this content useful,<strong> please leave a subscription&nbsp;</strong> 😉</p>



<h2 class="wp-block-heading" id="h-micronaut-project-overview">Micronaut Project Overview</h2>



<p>Let&#8217;s start everything by <strong>checking the project we will test today</strong>. Of course, we will focus on the most important parts, and for the full source code, I invite you to <a href="https://github.com/codersee-blog/kotlin-micronaut-testing" target="_blank" rel="noreferrer noopener">this GitHub repository</a>.</p>



<h3 class="wp-block-heading" id="h-application-properties">Application Properties</h3>



<p>In our project, we introduce 3 application properties files: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// application.properties
micronaut.application.name=micronaut-testing

// application-mongo.properties
mongodb.uri=mongodb://localhost:27017/example

// application-test.properties
mongodb.package-names=com.codersee.model</pre>



<p>The application name is the default property inserted when generating a new project. </p>



<p>Nevertheless, the two remaining are created on purpose. With their names- <code>*mongo</code>, <code>*test</code>&#8211; we instruct Micronaut that it should consider them only when <code>mongo</code> and <code>test</code> profiles are active. </p>



<p>But why this way? Well, to avoid two issues. </p>



<h3 class="wp-block-heading" id="h-mongodb-connection-issues-with-micronaut-test-resources">MongoDB Connection Issues with Micronaut Test Resources</h3>



<p>So, let&#8217;s start everything by explaining <strong>why we don&#8217;t put the Mongo URI inside the main properties</strong>.</p>



<p>Well, when we use the Micronaut Test Resources, MongoDB support will start a MongoDB container and provide the value of the <code>mongodb.uri</code> property. </p>



<p>Nevertheless, <strong>this won&#8217;t happen</strong> when we explicitly set the URI in the application properties. And that&#8217;s why we want to make this setting available <strong>only when we set the environment to <code>mongo</code>.</strong></p>



<h3 class="wp-block-heading" id="h-codeccachekey-issue">CodecCacheKey Issue</h3>



<p>Additionally, if we don&#8217;t specify explicitly the package name, where our model classes live, we&#8217;re going to end up with the following issue when running our Micronaut tests: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[io-executor-thread-1] ERROR i.m.http.server.RouteExecutor - Unexpected error occurred: Can't find a codec for CodecCacheKey{clazz=class com.codersee.model.AppUser, types=null}.
org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for CodecCacheKey{clazz=class com.codersee.model.AppUser, types=null}.
  at org.bson.internal.ProvidersCodecRegistry.lambda$get$0(ProvidersCodecRegistry.java:87)
  at java.base/java.util.Optional.orElseGet(Optional.java:364)
  at org.bson.internal.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:80)
  at org.bson.internal.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:50)
  at com.mongodb.internal.operation.Operations.createFindOperation(Operations.java:188)
...</pre>



<p>To me, looks like a bug. But thankfully, we can easily avoid that by putting that in the <code>application-test.properties</code> (which is used in tests by default).</p>



<h3 class="wp-block-heading" id="h-models">Models</h3>



<p>Following, we introduce a bunch of models:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// Address.kt: 

import io.micronaut.serde.annotation.Serdeable

@Serdeable.Serializable
@Serdeable.Deserializable
data class Address(
  val street: String,
  val city: String,
  val code: Int
)

// AppUser.kt:

import io.micronaut.data.annotation.GeneratedValue
import io.micronaut.data.annotation.Id
import io.micronaut.data.annotation.MappedEntity

@MappedEntity
data class AppUser(
  @field:Id
  @field:GeneratedValue
  val id: String? = null,
  val firstName: String,
  val lastName: String,
  val email: String,
  val address: Address
)

// AppUserRequest.kt: 

import io.micronaut.serde.annotation.Serdeable

@Serdeable.Deserializable
data class AppUserRequest(
  val firstName: String,
  val lastName: String,
  val email: String,
  val street: String,
  val city: String,
  val code: Int
)

// SearchRequest.kt:

import io.micronaut.serde.annotation.Serdeable

@Serdeable.Deserializable
data class SearchRequest(val name: String)</pre>



<p>Those classes are either used to persist/retrieve data from MongoDB or when deserializing JSON payloads into the objects. </p>



<h3 class="wp-block-heading" id="h-repository">Repository</h3>



<p>Following, we have a repository layer responsible for communicating with our storage:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// AppUserRepository.kt: 

import com.codersee.model.AppUser
import io.micronaut.data.mongodb.annotation.MongoFindQuery
import io.micronaut.data.mongodb.annotation.MongoRepository
import io.micronaut.data.repository.CrudRepository

@MongoRepository
interface AppUserRepository : CrudRepository&lt;AppUser, String> {

  fun findByFirstNameEquals(firstName: String): List&lt;AppUser>

  @MongoFindQuery("{ firstName: { \$regex: :name}}")
  fun findByFirstNameLike(name: String): List&lt;AppUser>
}</pre>



<h3 class="wp-block-heading" id="h-service">Service</h3>



<p>Nextly, the service layer, where we inject the repository and encapsulate its methods: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// AppUserService.kt:

import com.codersee.model.Address
import com.codersee.model.AppUser
import com.codersee.model.AppUserRequest
import com.codersee.repository.AppUserRepository
import io.micronaut.http.HttpStatus
import io.micronaut.http.exceptions.HttpStatusException
import jakarta.inject.Singleton

@Singleton
class AppUserService(
  private val appUserRepository: AppUserRepository
) {

  fun create(userRequest: AppUserRequest): AppUser =
    appUserRepository.save(
      userRequest.toAppUserEntity()
    )

  fun findAll(): List&lt;AppUser> =
    appUserRepository
      .findAll()
      .toList()

  fun findById(id: String): AppUser =
    appUserRepository.findById(id)
      .orElseThrow { HttpStatusException(HttpStatus.NOT_FOUND, "User with id: $id was not found.") }

  fun update(id: String, updateRequest: AppUserRequest): AppUser {
    val foundUser = findById(id)

    val updatedEntity =
      updateRequest
        .toAppUserEntity()
        .copy(id = foundUser.id)

    return appUserRepository.update(updatedEntity)
  }

  fun deleteById(id: String) {
    val foundUser = findById(id)

    appUserRepository.delete(foundUser)
  }

  fun findByNameLike(name: String): List&lt;AppUser> =
    appUserRepository
      .findByFirstNameLike(name)

  private fun AppUserRequest.toAppUserEntity(): AppUser {
    val address = Address(
      street = this.street,
      city = this.city,
      code = this.code
    )

    return AppUser(
      id = null,
      firstName = this.firstName,
      lastName = this.lastName,
      email = this.email,
      address = address
    )
  }
}</pre>



<h3 class="wp-block-heading" id="h-controller">Controller </h3>



<p>And lastly, the place where we expose our REST endpoints: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// AppUserController.kt: 

import com.codersee.model.AppUserRequest
import com.codersee.service.AppUserService
import com.codersee.model.SearchRequest
import com.codersee.model.AppUser
import io.micronaut.http.HttpStatus
import io.micronaut.http.annotation.*
import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.annotation.ExecuteOn

@Controller("/users")
@ExecuteOn(TaskExecutors.IO)
class AppUserController(
  private val appUserService: AppUserService
) {

  @Get
  fun findAllUsers(): List&lt;AppUser> =
    appUserService.findAll()

  @Get("/{id}")
  fun findById(@PathVariable id: String): AppUser =
    appUserService.findById(id)

  @Post
  @Status(HttpStatus.CREATED)
  fun createUser(@Body request: AppUserRequest): AppUser =
    appUserService.create(request)

  @Post("/search")
  fun searchUsers(@Body searchRequest: SearchRequest): List&lt;AppUser> =
    appUserService.findByNameLike(
      name = searchRequest.name
    )

  @Put("/{id}")
  fun updateById(
    @PathVariable id: String,
    @Body request: AppUserRequest
  ): AppUser =
    appUserService.update(id, request)

  @Delete("/{id}")
  @Status(HttpStatus.NO_CONTENT)
  fun deleteById(@PathVariable id: String) =
    appUserService.deleteById(id)
}</pre>



<h2 class="wp-block-heading" id="h-simple-kotlin-testing-in-micronaut">Simple Kotlin Testing in Micronaut</h2>



<p>Excellent. At this point, we know what exactly we are going to test. </p>



<p>And as the first approach we&#8217;re going to see will be a plain, old unit test. </p>



<p>Why? </p>



<p>Because at the end of the day, <strong>that&#8217;s what we will be dealing with most of the time</strong>. </p>



<p>So with that said, let&#8217;s take a look at the example test: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">  import com.codersee.repository.AppUserRepository
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class AppUserServiceTest {

  private val appUserRepository = mockk&lt;AppUserRepository>()

  private val appUserService = AppUserService(appUserRepository)

  @Test
  fun `should return empty user list`() {
    every {
      appUserRepository.findAll()
    } returns emptyList()

    val result = appUserService.findAll()

    assertTrue(result.isEmpty())

    verify(exactly = 1) { appUserRepository.findAll() }
  }
}</pre>



<p>As we can see above, we simply mock (with MockK) the <em>AppUserRepository </em>and inject it into the <em>AppUserService </em>instance.</p>



<p>Then, we specify that all invocations of the <code>findAll</code> method must return the empty list (with <code>every</code>). </p>



<p>Lastly, we assert that the <code>findAll</code> method from our service returns an empty list (<code>assertTrue</code>) and that the <code>findAll</code> method from the repository was invoked only once (<code>verify</code>). </p>



<p>Nevertheless, we&#8217;re not going to spend too much here right now as this tutorial focuses on Micronaut testing in Kotlin. If you are interested in learning more about these tools, then let me know in the comments section 🙂 </p>



<h2 class="wp-block-heading" id="h-integration-testing-with-micronauttest">Integration Testing with @MicronautTest</h2>



<p>With all of that done, let&#8217;s see how the Micronaut framework helps us with testing and a few interesting cases that will help us in real life. </p>



<h3 class="wp-block-heading" id="h-what-is-a-micronauttest">What is a @MicronautTest? </h3>



<p>Let&#8217;s start everything by explaining what exactly is the <strong>@MicronautTest</strong>.</p>



<p>In simple words, it&#8217;s an annotation that we can put on the test class to mark it a Micronaut test (no sh&#8230; Sherlock 🙂 :</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.micronaut.test.extensions.junit5.annotation.MicronautTest

@MicronautTest
class SomeTest {
  // tests
}</pre>



<p>In practice, it means that <strong>when we run a test with that annotation, it will run a real application</strong>. It will use internal Micronaut features with no mocking. So at this point, we can clearly see that this will be the right solution for <strong>integration testing.</strong></p>



<p>Moreover, this annotation can be used not only with <strong>JUnit 5</strong>, but also with <strong>Spock</strong>, <strong>Kotest</strong>, and <strong>Kotest 5</strong>. </p>



<h3 class="wp-block-heading" id="h-micronauttest-configuration-options">@MicronautTest Configuration Options </h3>



<p>The @MicronautTest allows us to configure a few properties, like the environments we want to run our test with, the packages to scan, or whether the automatic transaction wrapping should be enabled, or not:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.micronaut.test.extensions.junit5.annotation.MicronautTest

@MicronautTest(
  environments = ["env-1, env-2"],
  packages = ["com.codersee.somepackage"],
  transactional = false
)
class SomeTest {
  // tests
}</pre>



<p>For the full list of options, I highly encourage you to check out the docs (we can do that for instance in IntelliJ IDEA by clicking on the annotation with the left mouse button when we keep the left ctrl pressed). </p>



<h3 class="wp-block-heading" id="h-what-are-micronaut-test-resources">What Are Micronaut Test Resources? </h3>



<p>At the beginning of this tutorial, I mentioned the Micronaut Test Resources and I would like to make sure that we are on the same page with them. </p>



<p>So basically, Micronaute Test Resources <strong>integrates seamlessly with Testcontainers to provide throwaway containers for testing</strong>. But, we need to remember that Testcontainers <strong>require Docker-API compatible container runtime</strong> (in simple words- either Docker installed locally, or the Testcontainers Cloud). </p>



<p>In our case, we would like to do integration tests that require MongoDB. We could make use of the real database (which sometimes may be the case for you), provide some H2 database (not the best approach), or integrate Testcontainers. Thankfully, we don&#8217;t need to do that and the only thing we need is the appropriate import in our project: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">plugins {
    id("io.micronaut.test-resources") version "4.2.1"
}</pre>



<p>As a reminder, I want to emphasize that <strong>test resources will be used only when the <code>datasources.default.url</code> is missing.</strong> (and that&#8217;s why we removed the URI for MongoDB from tests). <strong> </strong> </p>



<h2 class="wp-block-heading" id="h-integration-tests-with-rest-assured">Integration Tests With REST Assured</h2>



<p>With all of that said, let&#8217;s combine everything together and test our Kotlin Micronaut application with JUnit 5 and REST Assured. </p>



<p>To do so, we must remember to add the necessary import: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">testImplementation("io.micronaut.test:micronaut-test-rest-assured")</pre>



<h3 class="wp-block-heading" id="h-verify-status-codes-and-headers">Verify Status Codes and Headers</h3>



<p>Let&#8217;s start with a simple request to the <code>GET /users</code> endpoint: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@MicronautTest
class AppUserControllerTestWithoutMocking {

  @Test
  fun `should return 200 OK on GET users`(spec: RequestSpecification) {
    spec
      .`when`()
      .get("/users")
      .then()
      .statusCode(200)
      .header("Content-Type", "application/json")
  }

}</pre>



<p>As I mentioned previously- by default, with @MicronautTest the real application is started. Moreover, nothing here is mocked, so the Mongo test container delivered by the Micronaut Test Resources is used.</p>



<p>Thanks to the dedicated module we added, we can inject the <code>RequestSpecification</code> into our tests and easily validate our endpoint. </p>



<p>In this case, we <strong>perform a GET request</strong> and verify that the <strong>response status code is 200 OK</strong> and the response <strong><code>Content-Type</code> header value is <code>application/json</code></strong>. </p>



<h3 class="wp-block-heading" id="h-verify-404-not-found">Verify 404 Not Found</h3>



<p>Similarly, we can verify that no entry is present in the database: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Test
fun `should return 404 Not Found on GET user by ID`(spec: RequestSpecification) {
  spec
    .`when`()
    .get("/users/123123123123123123121231")
    .then()
    .statusCode(404)
}</pre>



<p>This test will be also good proof that entries inserted to MongoDB in other tests do not affect other tests. </p>



<h3 class="wp-block-heading" id="h-extract-and-json-array-in-rest-assured">Extract and JSON Array in REST Assured</h3>



<p>Nextly, let&#8217;s take a look at the more complicated example:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Test
fun `should create a user`(spec: RequestSpecification) {
  val request = AppUserRequest(
    firstName = "Piotr",
    lastName = "Wolak",
    email = "contact@codersee.com",
    street = "Street",
    city = "City",
    code = 123,
  )

  spec
    .`when`()
    .contentType(ContentType.JSON)
    .body(request)
    .post("/users")
    .then()
    .statusCode(201)

  val list = spec
    .`when`()
    .get("/users")
    .then()
    .statusCode(200)
    .body("size()", `is`(1))
    .extract()
    .`as`(object : TypeRef&lt;List&lt;AppUser>>() {})

  assertEquals(1, list.size)

  val createdUser = list.first()

  assertEquals(request.firstName, createdUser.firstName)
  assertEquals(request.street, createdUser.address.street)
}</pre>



<p>This time, we do a bunch of more interesting things. </p>



<p>Firstly, we make the <strong>POST request </strong>with a request body and <strong>verify that 201 Creates is returned</strong>. </p>



<p>After that, we call the <code>GET /users</code> endpoint to get a list of users. When we get the response, we <strong>verify that the JSON array size is equal to 1</strong> (with the <code>.body("size()", is(1))</code> call). Lastly, we <strong>make use of the extract().`as</strong>` to convert the payload into the List of AppUser instances. Thanks to that we can easily perform assertions with JUnit 5 functions. </p>



<h3 class="wp-block-heading" id="h-add-kotlin-utils-for-rest-assured">Add Kotlin Utils For REST Assured</h3>



<p>As you probably know, <code>when</code> and <code>as</code> are keywords in Kotlin and that&#8217;s why <strong>we had to use backticks (&#8220;)</strong>.</p>



<p>And to make our lives easier, let&#8217;s add the <code>util</code> package in <code>test</code> and create the <code>TestUtil.kt</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.restassured.common.mapper.TypeRef
import io.restassured.response.ValidatableResponse
import io.restassured.specification.RequestSpecification


fun RequestSpecification.whenever(): RequestSpecification {
  return this.`when`()
}

fun &lt;T> ValidatableResponse.extractAs(clazz: Class&lt;T>) =
  this.extract()
    .`as`(clazz)

fun &lt;T> ValidatableResponse.extractAs(typeRef: TypeRef&lt;T>) =
  this.extract()
    .`as`(typeRef)</pre>



<p>As a result, from now on we can simply use the <em>whenever() </em>instead of <em>`when`()</em> and <em>extractAs() </em>instead of <em>extract().`</em>as`(). </p>



<h2 class="wp-block-heading" id="h-reference-the-server-context">Reference The Server / Context </h2>



<p>As the next step, let&#8217;s take a look at how to <strong>reference the server</strong> or <strong>current application context</strong>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@MicronautTest
class InjectingExample{

  @Inject
  private lateinit var context: ApplicationContext

  @Inject
  private lateinit var server: EmbeddedServer

  // tests
}</pre>



<p>Sometimes it can be useful, and as we can see, we can simply inject them using the <strong>@Inject</strong> annotation. </p>



<h2 class="wp-block-heading" id="h-conditionally-run-tests">Conditionally Run Tests</h2>



<p>After that, let&#8217;s see <strong>how we can skip tests execution.</strong> </p>



<p>Why would we need that? </p>



<p>Well, integration tests sometimes can take a lot of time, and the bigger our project becomes, the bigger the chance they become annoying. And because of that, generally, it&#8217;s a good approach to somehow separate them from the faster unit tests. </p>



<p> When testing in Micronaut, we can easily achieve that using the <strong><em>@Requires</em></strong> annotation:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.micronaut.context.annotation.Requires
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

@MicronautTest
@Requires(env = ["integration-test"])
class SomeIntegrationTest {

  @Test
  fun `should perform integration test`() {
    assertTrue(false)
  }

}</pre>



<p>The <strong><em>@MicronautTest</em></strong> annotation <strong>turns tests into beans</strong>. And that&#8217;s why we can use the <strong>@Requires</strong>, just like we would do with a &#8220;standard&#8221; bean.</p>



<p>As a result, tests inside the <code>SomeIntegrationTest</code> will run only, when the <code>integration-test</code> the environment is active. (And we can set that for example by setting the environment variable- <code>MICRONAUT_ENVIRONMENTS=integration-test</code>). </p>



<h2 class="wp-block-heading" id="h-testing-micronaut-with-mockk-mocks">Testing Micronaut With MockK Mocks</h2>



<p>As the last thing in our tutorial about testing Micronaut with Kotlin, let&#8217;s take a look at <strong>how we can mock things using the MockK</strong>.</p>



<p>Could we use Mockito? Yes. However, I find MockK better for Kotlin (DSLs &lt;3 ).</p>



<p>In order to mock a bean in Micronaut, the only thing we need to do is <strong>annotate the method/inner class with the @MockBean annotation</strong>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@MicronautTest
class AppUserControllerTestWithMocking {

  private val repo: AppUserRepository = mockk&lt;AppUserRepository>()

  @MockBean(AppUserRepository::class)
  fun appUserRepository(): AppUserRepository = repo
}</pre>



<p>As we can see, we introduce the <code>AppUserRepository</code> mock as the <code>repo</code> property, so that later, we will be able to refer to it easily in our test cases. Additionally, we add the function annotated with <code>@MockBean</code>, thus informing Micronaut that the <code>AppUserRepository</code> is the type we want to replace with our mock. </p>



<p>Moreover, thanks to this approach <strong>this mock will be limited only to tests defined in this class</strong>. So we can &#8220;rest assured&#8221; (hue hue) it won&#8217;t interfere with other classes. </p>



<p>As a result, our tests will look, as follows: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@MicronautTest
class AppUserControllerTestWithMocking {

  private val repo: AppUserRepository = mockk&lt;AppUserRepository>()

  @Test
  fun `should return 200 OK on GET users`(spec: RequestSpecification) {
    every {
      repo.findAll()
    } returns emptyList()

    spec
      .whenever()
      .get("/users")
      .then()
      .statusCode(200)
      .header("Content-Type", "application/json")
  }

  @Test
  fun `should return user by ID`(spec: RequestSpecification) {
    val foundUser = AppUser(
      id = "123",
      firstName = "Piotr",
      lastName = "Wolak",
      email = "contact@codersee.com",
      address = Address(
        street = "street",
        city = "city",
        code = 123
      )
    )

    every {
      repo.findById("123")
    } returns Optional.of(foundUser)

    spec
      .whenever()
      .get("/users/123")
      .then()
      .statusCode(200)
      .body("id", equalTo("123"))
      .body("firstName", equalTo("Piotr"))
      .body("address.street", equalTo("street"))
      .body("address.code", equalTo(123))
  }

  @Test
  fun `should return user by ID and verify with extract`(spec: RequestSpecification) {
    val foundUser = AppUser(
      id = "123",
      firstName = "Piotr",
      lastName = "Wolak",
      email = "contact@codersee.com",
      address = Address(
        street = "street",
        city = "city",
        code = 123
      )
    )

    every {
      repo.findById("123")
    } returns Optional.of(foundUser)

    val extracted = spec
      .whenever()
      .get("/users/123")
      .then()
      .statusCode(200)
      .extractAs(AppUser::class.java)

    assertEquals(foundUser, extracted)
  }

  @Test
  fun `should create a user`(spec: RequestSpecification) {
    val request = AppUserRequest(
      firstName = "Piotr",
      lastName = "Wolak",
      email = "contact@codersee.com",
      street = "Street",
      city = "City",
      code = 123,
    )

    val createdUser = AppUser(
      id = "123",
      firstName = "Piotr",
      lastName = "Wolak",
      email = "contact@codersee.com",
      address = Address(
        street = "street",
        city = "city",
        code = 123
      )
    )

    every {
      repo.save(any())
    } returns createdUser

    val extracted = spec
      .whenever()
      .contentType(ContentType.JSON)
      .body(request)
      .post("/users")
      .then()
      .statusCode(201)
      .extractAs(AppUser::class.java)

    assertEquals(createdUser, extracted)
  }

  @MockBean(AppUserRepository::class)
  fun appUserRepository(): AppUserRepository = repo
}</pre>



<h2 class="wp-block-heading" id="h-summary">Summary</h2>



<p>And that&#8217;s all for this tutorial on <strong>how to perform testing in Micronaut with Kotlin.</strong></p>



<p>Together, we&#8217;ve discovered a bunch of interesting things that I hope will be useful in your projects. </p>



<p>If you would like to see the codebase for this tutorial, then check out <a href="https://github.com/codersee-blog/kotlin-micronaut-testing" target="_blank" rel="noreferrer noopener">this GitHub repository</a>. Or, if you are interested in learning more about Micronaut, then check out <a href="https://blog.codersee.com/category/micronaut/">my other posts</a>. </p>



<p>Let me know your thoughts in the comments section below! 🙂 </p>
<p>The post <a href="https://blog.codersee.com/testing-micronaut-appplication-kotlin/">Testing Micronaut Application in Kotlin</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/testing-micronaut-appplication-kotlin/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Easily Deploy Your Ktor Server with Docker</title>
		<link>https://blog.codersee.com/easily-deploy-ktor-server-with-docker/</link>
					<comments>https://blog.codersee.com/easily-deploy-ktor-server-with-docker/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 17 Jan 2023 07:40:26 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[Exposed]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=5504250</guid>

					<description><![CDATA[<p>If you've ever been wondering how to deploy your Ktor Server app using Docker, then this step-by-step guide will be the right choice.</p>
<p>The post <a href="https://blog.codersee.com/easily-deploy-ktor-server-with-docker/">Easily Deploy Your Ktor Server with Docker</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading" id="h-introduction">Introduction</h2>



<p>Hello and welcome to my next step-by-step guide, in which we will learn <strong>how to deploy a Ktor Server application using Docker</strong>.</p>



<p>Firstly, we will prepare a simple app with a <strong>REST endpoint</strong> and a <strong>database connection</strong>. This way, we will see one of the most common issues and how we can deal with it.</p>



<p>Finally, I will show you three approaches you can use in your project.</p>



<h2 class="wp-block-heading" id="h-create-a-ktor-server-skeleton">Create a Ktor Server Skeleton</h2>



<p>So, as the first step, let&#8217;s navigate to the <a href="https://start.ktor.io/">https://start.ktor.io/</a> page and generate a Ktor Server project.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Note: If you are using IntelliJ IDEA Ultimate Edition, then you can generate a new project with it, as well.</p>
</blockquote>



<p>Please specify whatever values you want, except the &#8220;Configuration in&#8221;- let&#8217;s stick to the HOCON file to be on the same page:</p>



<figure class="wp-block-image aligncenter"><img fetchpriority="high" decoding="async" width="765" height="825" src="http://blog.codersee.com/wp-content/uploads/2023/01/deploy_ktor_server_using_docker_project_generation.png" alt="Image is a screenshot from the Ktor Project Generator page showing the values author put in his project. " class="wp-image-5504252" srcset="https://blog.codersee.com/wp-content/uploads/2023/01/deploy_ktor_server_using_docker_project_generation.png 765w, https://blog.codersee.com/wp-content/uploads/2023/01/deploy_ktor_server_using_docker_project_generation-278x300.png 278w" sizes="(max-width: 765px) 100vw, 765px" /></figure>



<p>Nextly, let&#8217;s click the &#8220;Add plugins&#8221; button and add the &#8220;Routing&#8221; plugin:</p>



<figure class="wp-block-image aligncenter"><img decoding="async" width="757" height="748" src="http://blog.codersee.com/wp-content/uploads/2023/01/deploy_ktor_server_using_docker_project_generation_add_plugins.png" alt="Image is a screenshot showing the Routing plugin selected when generating a new Ktor Server project." class="wp-image-5504253" srcset="https://blog.codersee.com/wp-content/uploads/2023/01/deploy_ktor_server_using_docker_project_generation_add_plugins.png 757w, https://blog.codersee.com/wp-content/uploads/2023/01/deploy_ktor_server_using_docker_project_generation_add_plugins-300x296.png 300w" sizes="(max-width: 757px) 100vw, 757px" /></figure>



<p>After that, let&#8217;s hit the &#8220;Generate project&#8221; button, save it to our machine, and import it to the IDE (for example, IntelliJ IDEA).</p>



<h2 class="wp-block-heading" id="h-configure-database-connection">Configure Database Connection</h2>



<p>Although with all of that done we are ready to learn how to deploy a Ktor server with Docker, let me show you one more thing. Most of the time, when creating server-side applications, we will have to connect to some database. And then, we can easily end up with connection issues, which I would like to show you in this tutorial.</p>



<p>Of course, if that&#8217;s not the case in your project, then please feel free to skip this part. Otherwise, I do recommend going through this part and adjusting it to your needs.</p>



<h3 class="wp-block-heading" id="h-configure-imports">Configure Imports</h3>



<p>Firstly, let&#8217;s modify the <strong>build.gradle.kts</strong> file:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">val mysql_connector_version: String by project
val exposed_version: String by project

dependencies {
  // other dependencies
  implementation("org.jetbrains.exposed:exposed-core:$exposed_version")
  implementation("org.jetbrains.exposed:exposed-dao:$exposed_version")
  implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version")

  implementation("com.mysql:mysql-connector-j:$mysql_connector_version")
}
</pre>



<p>Of course, with this approach we need to add two more lines to <strong>gradle.properties</strong>, as well:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">mysql_connector_version=8.0.31
exposed_version = 0.41.1
</pre>



<p>As we can see, we just added the <a href="https://github.com/JetBrains/Exposed" target="_blank" rel="noopener">Exposed</a>&#8211; an ORM framework for Kotlin- and <a href="https://github.com/mysql/mysql-connector-j" target="_blank" rel="noopener">MySQL Connector/J</a>&#8211; a JDBC Type 4 driver for MySQL.</p>



<figure class="wp-block-image aligncenter"><a href="https://codersee.com/newsletter/"><img decoding="async" width="1024" height="576" src="http://blog.codersee.com/wp-content/uploads/2022/05/join_newsletter-1024x576.png" alt="Image shows two ebooks people can get for free after joining newsletter" class="wp-image-3002956" srcset="https://blog.codersee.com/wp-content/uploads/2022/05/join_newsletter-1024x576.png 1024w, https://blog.codersee.com/wp-content/uploads/2022/05/join_newsletter-300x169.png 300w, https://blog.codersee.com/wp-content/uploads/2022/05/join_newsletter-768x432.png 768w, https://blog.codersee.com/wp-content/uploads/2022/05/join_newsletter.png 1440w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<h3 class="wp-block-heading" id="h-database-config">Database Config</h3>



<p>As the next step, let&#8217;s add a couple of lines to the <strong>application.conf</strong>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">db {
  url = ${DB_URL}
  user = ${DB_USER}
  password = ${DB_PASSWORD}
  driver = "com.mysql.cj.jdbc.Driver"
}
</pre>



<p>As can be seen, we externalized the <strong>URL</strong>, <strong>user</strong>, and <strong>password</strong> settings to the environment variables.</p>



<p>This way, we can easily provide different values (for example, for different environments) without modifying the code. Nevertheless, we don&#8217;t do that for the driver, as this should be constant across all envs.</p>



<p>Following, let&#8217;s head to the <strong>plugins</strong> directory and create the <strong>Database.kt</strong> file:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Application.configureDatabase() {
  val url = environment.config.property("db.url").getString()
  val user = environment.config.property("db.user").getString()
  val password = environment.config.property("db.password").getString()
  val driverClassName = environment.config.property("db.driver").getString()

  Database.connect(
      url = url,
      driver = driverClassName,
      user = user,
      password = password
  )

  transaction {
      AppUser.all()
          .toList()
  }
}

object AppUsers : IntIdTable(name = "app_user") {
  val email = varchar("email", length = 255).uniqueIndex()
}

class AppUser(id: EntityID) : IntEntity(id) {
  companion object : IntEntityClass(AppUsers)

  var email by AppUsers.email
}
</pre>



<p>To put it simply, the above code is responsible for making a connection to the database based on values from <strong>application.conf</strong> file and fetching all users from the &#8220;app_user&#8221; table.</p>



<h3 class="wp-block-heading" id="h-verification">Verification</h3>



<p>With all of that being done, we can run the application and verify the output.</p>



<p>If we don&#8217;t pass any environment variables, we will see the following:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Exception in thread &#8220;main&#8221; com.typesafe.config.ConfigException$UnresolvedSubstitution: application.conf Could not resolve substitution to a value: ${DB_PASSWORD}</p>



<p>Process finished with exit code 1</p>
</blockquote>



<p>In IntelliJ, we can set variables by editing the running configuration:</p>



<figure class="wp-block-image aligncenter"><img loading="lazy" decoding="async" width="1053" height="676" src="http://blog.codersee.com/wp-content/uploads/2023/01/ktor_environment_variables_intelliJ.png" alt="Screenshot shows how to set up environment variables for project in IntelliJ." class="wp-image-5504257" srcset="https://blog.codersee.com/wp-content/uploads/2023/01/ktor_environment_variables_intelliJ.png 1053w, https://blog.codersee.com/wp-content/uploads/2023/01/ktor_environment_variables_intelliJ-300x193.png 300w, https://blog.codersee.com/wp-content/uploads/2023/01/ktor_environment_variables_intelliJ-1024x657.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/01/ktor_environment_variables_intelliJ-768x493.png 768w" sizes="auto, (max-width: 1053px) 100vw, 1053px" /></figure>



<p>Please note that values should not be quoted (example: &#8220;value&#8221;), otherwise, we can end up with something like this:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Exception in thread &#8220;main&#8221; java.lang.IllegalStateException: Can&#8217;t resolve dialect for connection: &#8230;<br>Process finished with exit code 1</p>
</blockquote>



<p>So, if everything is set correctly, we should see the following:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># Logs:
INFO Application - Autoreload is disabled because the development mode is off.
DEBUG Exposed - SELECT app_user.id, app_user.email FROM app_user
INFO Application - Application started in 0.801 seconds.
INFO Application - Responding at http://127.0.0.1:8080

# And for request:
GET http://localhost:8080/

# Response:
Hello World!
</pre>



<p>And although we didn&#8217;t do anything, the <strong>Routing.kt</strong> file with this endpoint was added automatically when generating the project.</p>



<h2 class="wp-block-heading" id="h-deploy-ktor-server-with-docker-manual-approach">Deploy Ktor Server With Docker &#8211; Manual Approach</h2>



<p>So finally, we can see the first- manual- approach with which we can deploy the Ktor app with Docker.</p>



<h3 class="wp-block-heading" id="h-create-dockerfile">Create Dockerfile</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>In my articles, I focus on the practice and getting things done. <br><br>However, if you would like to get a strong understading of DevOps concepts, which are more and more in demand nowadays, then I highly recommend to check out KodeKloud courses, like this <a href="https://shrsl.com/46wvo" rel="sponsored nofollow">Docker</a> and <a href="https://shareasale.com/r.cfm?b=2183704&amp;u=3507867&amp;m=132199&amp;urllink=&amp;afftrack=" rel="sponsored nofollow">Kubernetes</a> learning paths. </cite></blockquote>



<p>To do so, let&#8217;s a file named <strong>Dockerfile</strong> to the root of the project:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="dockerfile" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">FROM openjdk:17-jdk-alpine3.14
RUN mkdir /app
COPY ./build/libs/com.codersee.ktor-docker-all.jar /app/app.jar
ENTRYPOINT ["java","-jar","/app/app.jar"]</pre>



<p>As we can see, we will use the OpenJDK alpine image and run a built <strong>jar</strong>.</p>



<h3 class="wp-block-heading" id="h-build-the-image">Build The Image</h3>



<p>As the next step, let&#8217;s build a jar file using a gradle wrapper:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">./gradlew build</pre>



<p><em>Note:</em> if the build is failing, then please navigate to the test directory and comment out the test.</p>



<p>If everything is fine, we should see the &#8220;BUILD SUCCESSFUL&#8221; message and we can create a Docker image:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker build -t my-example-app .

# Verification: 
docker images

# Result:
REPOSITORY     TAG    IMAGE ID     CREATED       SIZE
my-example-app latest f50b5432ea63 9 seconds ago 346MB</pre>



<p>As we can see, the image was created successfully and we can proceed.</p>



<h3 class="wp-block-heading" id="h-docker-run">Docker Run</h3>



<p>With all of that done, we can deploy our Ktor Server using the<strong> docker run</strong> command:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker run -p 8080:8080 --name example-container -e DB_URL=jdbc:mysql://host.docker.internal:3306/boards -e DB_USER=root -e DB_PASSWORD=p@ssword my-example-app

# Output: 
2023-01-13 16:30:43.854 [main] INFO Application - Autoreload is disabled because the development mode is off.
2023-01-13 16:30:44.537 [main] DEBUG Exposed - SELECT app_user.id, app_user.email FROM app_user
2023-01-13 16:30:44.543 [main] INFO Application - Application started in 0.719 seconds.
2023-01-13 16:30:44.746 [DefaultDispatcher-worker-1] INFO Application - Responding at http://0.0.0.0:8080</pre>



<p>As can be seen, the server is up and running, which means that it successfully connected to our MySQL instance.</p>



<p><strong>Note: </strong>Please note that when working with Docker on your local machine, you can&#8217;t provide localhost as a host value for the connection anymore.</p>



<p>Nevertheless, the <code class="EnlighterJSRAW" data-enlighter-language="raw">host.docker.internal</code> is supported for:</p>



<ul class="wp-block-list">
<li>Docker-for-mac or Docker-for-Windows <strong>18.03+</strong></li>



<li>Docker-for-Linux <strong>20.10.0+</strong> (if you started your Docker container with <code class="EnlighterJSRAW" data-enlighter-language="raw">--add-host host.docker.internal:host-gateway</code>)</li>
</ul>



<p>Of course, there are more ways to go in such a case, but this is the simplest one in my opinion.</p>



<h2 class="wp-block-heading" id="h-deploy-ktor-server-with-docker-hybrid-approach">Deploy Ktor Server With Docker &#8211; Hybrid Approach</h2>



<p>With that covered, let&#8217;s figure out another approach to deploy the Ktor app using Docker.</p>



<p>This time, we will build a docker image to a .tar file using the Ktor plugin. If you generated the project with me, then we don&#8217;t need to do anything. Otherwise, please remember to add it to the <strong>build.gradle.kts</strong> file:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">plugins {
  // Other plugins
  id("io.ktor.plugin") version "2.2.2"
}
</pre>



<h3 class="wp-block-heading" id="h-create-new-image">Create New Image</h3>



<p>And as the next step, let&#8217;s run the <strong>buildImage</strong> command:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">./gradlew buildImage
</pre>



<p>As a result, we should see the jib-image.tar file inside the <strong>build</strong> directory.</p>



<p>If that&#8217;s the case, then let&#8217;s load an image from a tar archive with docker load:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker load -i build/jib-image.tar

# Verification: docker images # Result: 
REPOSITORY        TAG    IMAGE ID     CREATED      SIZE
ktor-docker-image latest 31b35e55ec24 53 years ago 286MB
</pre>



<p>And although it was not created 53 years ago 🙂 , we can clearly see that <strong>the size is reduced compared to the previous approach.</strong></p>



<h3 class="wp-block-heading" id="h-deploy-server">Deploy Server</h3>



<p>And finally, let&#8217;s verify again.</p>



<p>But this time, we need to specify the <code class="EnlighterJSRAW" data-enlighter-language="raw">ktor-docker-image</code> as the image name:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker run -p 8080:8080 --name example-container -e DB_URL=jdbc:mysql://host.docker.internal:3306/boards -e DB_USER=root -e DB_PASSWORD=p@ssword ktor-docker-image

# Output: 
2023-01-13 16:30:43.854 [main] INFO Application - Autoreload is disabled because the development mode is off.
2023-01-13 16:30:44.537 [main] DEBUG Exposed - SELECT app_user.id, app_user.email FROM app_user
2023-01-13 16:30:44.543 [main] INFO Application - Application started in 0.719 seconds.
2023-01-13 16:30:44.746 [DefaultDispatcher-worker-1] INFO Application - Responding at http://0.0.0.0:8080</pre>



<h3 class="wp-block-heading" id="h-customization">Customization</h3>



<p>Of course, we can customize the image name and tag, as well.</p>



<p>To do so, let&#8217;s add the following inside the <strong>build.gradle.kts</strong>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">ktor {
  docker {
    localImageName.set("my-custom-image-name")
    imageTag.set("alpha")
  }
}
</pre>



<p>So this time, when we repeat the previous steps, we should see the following image <code class="EnlighterJSRAW" data-enlighter-language="raw">my-custom-image-name:alpha</code>.</p>



<h2 class="wp-block-heading" id="h-deploy-ktor-server-with-docker-built-in-approach">Deploy Ktor Server With Docker &#8211; Built-in Approach</h2>



<p>Lastly, I just wanted to mention that the Ktor plugin comes not only with the <code class="EnlighterJSRAW" data-enlighter-language="raw">buildImage</code> task when it comes to Docker.</p>



<p>With this plugin, we can make use of 3 more tasks:</p>



<ul class="wp-block-list">
<li><strong>publishImageToLocalRegistry</strong> &#8211; which is a combination of the <code class="EnlighterJSRAW" data-enlighter-language="raw">buildImage</code> and gradle load from the previous step paragraph.</li>



<li><strong>publishImage</strong>&#8211; which takes care of pushing to an external registry.</li>



<li><strong> runDocker</strong> &#8211; which additionally launches the Ktor server to listen on the 8080 port of localhost (unless we specify otherwise).</li>
</ul>



<p>And although I show this approach as the last one in this tutorial, in my opinion, we should delegate the deployment process to these tasks as often, as it is possible.</p>



<h2 class="wp-block-heading" id="h-summary">Summary</h2>



<p>And that&#8217;s all for this tutorial on <strong>how to deploy a Ktor Server with Docker</strong>. Together, we&#8217;ve learned 3 approaches, which can help us do that with ease and as always, you can find the source code on <a href="https://github.com/codersee-blog/kotlin-ktor-docker-deployment" target="_blank" rel="noopener">GitHub</a>.</p>



<p>If you enjoyed the content or just would like to share your feedback or thoughts, then <strong>let me know in the comments section</strong> 🙂</p>



<p>Lastly, if you would like to learn a bit more about Ktor, then check out my other articles about <a href="https://blog.codersee.com/a-guide-to-ktor-with-mongodb/">Ktor with MongoDB</a>, or <a href="https://blog.codersee.com/rest-api-ktor-ktorm-postgresql/">Ktor with PostgreSQL</a>.</p>
<p>The post <a href="https://blog.codersee.com/easily-deploy-ktor-server-with-docker/">Easily Deploy Your Ktor Server with Docker</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/easily-deploy-ktor-server-with-docker/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How To Set Up Kafka Without Zookeeper using Docker Compose?</title>
		<link>https://blog.codersee.com/how-to-set-up-kafka-without-zookeeper-using-docker-compose/</link>
					<comments>https://blog.codersee.com/how-to-set-up-kafka-without-zookeeper-using-docker-compose/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Thu, 29 Sep 2022 06:00:51 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[kafka]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=5503746</guid>

					<description><![CDATA[<p>In this article, I would like to show you how to set up Apache Kafka Without Zookeeper using Docker Compose.</p>
<p>The post <a href="https://blog.codersee.com/how-to-set-up-kafka-without-zookeeper-using-docker-compose/">How To Set Up Kafka Without Zookeeper using Docker Compose?</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading" id="h-1-introduction">1. Introduction</h2>



<p>In this last of the 3 articles about setting up Apache Kafka, I would like to show you how to set up Apache Kafka <strong>Without Zookeeper</strong> using Docker Compose.</p>



<p>After reading it, you will know precisely:</p>



<ul class="wp-block-list">
<li>what does the Docker Compose configuration look like to set up <strong>3 Kafka Brokers</strong>,</li>



<li>what <strong>environment variables</strong> we have to set,</li>



<li>and what <strong>workaround</strong> we have to do in order to make it run.</li>
</ul>



<p>If after finishing it, you will still be eager to broaden your knowledge, then you can find the previous two articles right here:</p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/how-to-deploy-multiple-kafka-brokers-with-docker-compose/">How To Deploy Multiple Kafka Brokers With Docker Compose?</a></li>



<li><a href="https://blog.codersee.com/how-to-set-up-apache-kafka-with-docker/" target="_blank" rel="noopener">How To Set Up Apache Kafka With Docker?</a></li>
</ul>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>In my articles, I focus on the practice and getting things done. <br><br>However, if you would like to get a strong understading of DevOps concepts, which are more and more in demand nowadays, then I highly recommend to check out KodeKloud courses, like this <a href="https://shrsl.com/46wvo" rel="sponsored nofollow">Docker</a> and <a href="https://shareasale.com/r.cfm?b=2183704&amp;u=3507867&amp;m=132199&amp;urllink=&amp;afftrack=" rel="sponsored nofollow">Kubernetes</a> learning paths. </cite></blockquote>



<h2 class="wp-block-heading" id="h-2-apache-kafka-without-zookeeper">2. Apache Kafka Without Zookeeper</h2>



<p>Before we dive into the practice part, let&#8217;s take a second to understand the motivation behind the Zookeeper removal (I&#8217;ve already written about Zookeeper responsibilities in the first part, so I&#8217;ll skip it here).</p>



<p>To put it simply, the motivation behind it can be summarized with these points:</p>



<ul class="wp-block-list">
<li>simplifying the deployment and configuration process (Zookeeper is a separate system, with its own syntax, configs, and deployment patterns),</li>



<li>scalability improvement,</li>



<li>enabling support for more partitions.</li>
</ul>



<p>The whole change has been introduced for the first time as a <a href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-500%3A+Replace+ZooKeeper+with+a+Self-Managed+Metadata+Quorum" target="_blank" rel="noopener">KIP-500</a> (Kafka Improvement Proposal) and has been offered as an early access version with the <strong>2.8.0 release</strong>.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>If you would like to understand deeply the reasons and arguments behind the Zookeeper removal, I highly encourage you to visit the KIP-500 ticket page linked above.</p>
</blockquote>


<p>[elementor-template id=&#8221;9007393&#8243;]</p>



<h2 class="wp-block-heading" id="h-3-kafka-without-zookeeper-docker-compose-file">3. Kafka Without Zookeeper Docker Compose File</h2>



<p>With that being said, let&#8217;s take a look at the <strong>docker-compose.yml</strong> file:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">version: '3'
services:
  kafka1:
    image: confluentinc/cp-kafka:7.2.1
    container_name: kafka1
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
      KAFKA_LISTENERS: PLAINTEXT://kafka1:9092,CONTROLLER://kafka1:9093
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka1:9092
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka1:9093,2@kafka2:9093,3@kafka3:9093'
      KAFKA_PROCESS_ROLES: 'broker,controller'
    volumes:
      - ./run_workaround.sh:/tmp/run_workaround.sh
    command: "bash -c '/tmp/run_workaround.sh &amp;&amp; /etc/confluent/docker/run'"
  kafka2:
    image: confluentinc/cp-kafka:7.2.1
    container_name: kafka2
    environment:
      KAFKA_NODE_ID: 2
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
      KAFKA_LISTENERS: PLAINTEXT://kafka2:9092,CONTROLLER://kafka2:9093
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka2:9092
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka1:9093,2@kafka2:9093,3@kafka3:9093'
      KAFKA_PROCESS_ROLES: 'broker,controller'
    volumes:
      - ./run_workaround.sh:/tmp/run_workaround.sh
    command: "bash -c '/tmp/run_workaround.sh &amp;&amp; /etc/confluent/docker/run'"
  kafka3:
    image: confluentinc/cp-kafka:7.2.1
    container_name: kafka3
    environment:
      KAFKA_NODE_ID: 3
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
      KAFKA_LISTENERS: PLAINTEXT://kafka3:9092,CONTROLLER://kafka3:9093
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka3:9092
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka1:9093,2@kafka2:9093,3@kafka3:9093'
      KAFKA_PROCESS_ROLES: 'broker,controller'
    volumes:
      - ./run_workaround.sh:/tmp/run_workaround.sh
    command: "bash -c '/tmp/run_workaround.sh &amp;&amp; /etc/confluent/docker/run'"
</pre>



<p>Don&#8217;t worry if you feel overwhelmed at this point, I will walk you step by step through the configuration values in the following paragraphs.</p>



<p>Nevertheless, don&#8217;t run the <code class="EnlighterJSRAW" data-enlighter-language="raw">docker compose up</code> command yet, we will need one more file to run it 🙂</p>



<h2 class="wp-block-heading" id="h-4-add-necessary-workarounds">4. Add Necessary Workarounds</h2>



<p>As the next step, we need to run a few commands when containers start.</p>



<p>In my example, I will call this file <strong>run_workaround.sh</strong>. And although the name is not important, it has to match values specified when mounting volumes and running the command from within the containers:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#!/bin/sh

sed -i '/KAFKA_ZOOKEEPER_CONNECT/d' /etc/confluent/docker/configure

sed -i 's/cub zk-ready/echo ignore zk-ready/' /etc/confluent/docker/ensure

echo "kafka-storage format --ignore-formatted -t NqnEdODVKkiLTfJvqd1uqQ== -c /etc/kafka/kafka.properties" >> /etc/confluent/docker/ensure
</pre>



<p>As we can see, the above script contains exactly 3 commands.</p>



<p>The first one is responsible for turning off the check for the <code class="EnlighterJSRAW" data-enlighter-language="raw">KAFKA_ZOOKEEPER_CONNECT</code> parameter. Without this line, the error will be thrown: C<em>ommand [/usr/local/bin/dub ensure KAFKA_ZOOKEEPER_CONNECT] FAILED !</em></p>



<p>The second one ignores the cub zk-ready. Without it, everything will end up with another exception: <em>zk-ready: error: too few arguments</em>.</p>



<p>And finally, the last line is a KRaft required step, which formats the storage directory with a new cluster identifier. Please keep in mind that the specified UUID value (<code class="EnlighterJSRAW" data-enlighter-language="raw">NqnEdODVKkiLTfJvqd1uqQ==</code> in my case) has to be <strong>16 bytes</strong> of a <strong>base64-encoded UUID</strong>.</p>



<h2 class="wp-block-heading" id="h-5-docker-compose-instructions">5. Docker Compose Instructions</h2>



<p>With all of the above being said, we can get back to the <strong>docker-compose.yml</strong> file.</p>



<h3 class="wp-block-heading" id="h-5-1-volumes-and-commands">5.1. Volumes and Commands</h3>



<p>Although I will skip the things like the <em>services</em>, <em>images</em>, or <em>container</em> names (I&#8217;ve covered them in the previous articles), let&#8217;s take a look at these 3 lines:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">volumes:
  - ./run_workaround.sh:/tmp/run_workaround.sh
command: "bash -c '/tmp/run_workaround.sh &amp;&amp; /etc/confluent/docker/run'"
</pre>



<p><em>Volumes</em> are the preferred mechanism for persisting data <strong>generated by and used</strong> by Docker containers. With the above setting, we inform Docker that we want the <strong>run_workaround.sh </strong>file to be mounted inside the <strong>tmp</strong> directory inside the container with the same name. Of course, we could change the name of this file inside, but then, the change should also be reflected in the command.</p>



<p>When it comes to the <em>command</em>, it simply <strong>overrides the default command</strong>. With the above config, we instruct Docker to run our container with our custom script.</p>



<h2 class="wp-block-heading" id="h-6-apache-kafka-brokers-environment-variables">6. Apache Kafka Brokers Environment Variables</h2>



<p>Finally, let&#8217;s go through all the environments we had to set in order to make everything work.</p>



<h3 class="wp-block-heading" id="h-6-1-kafka-node-id-environment-variable">6.1. KAFKA_NODE_ID Environment Variable</h3>



<p>As the name suggest, the <code class="EnlighterJSRAW" data-enlighter-language="raw">KAFKA_NODE_ID</code> variable is used to specify a unique identifier of our node in the cluster.</p>



<h3 class="wp-block-heading" id="h-6-2-kafka-listeners-and-protocol-mapping">6.2. Kafka Listeners and Protocol Mapping</h3>



<p>Nextly, let&#8217;s see the settings responsible for appropriate hosts and ports mapping:</p>



<ul class="wp-block-list">
<li><strong>KAFKA_LISTENER_SECURITY_PROTOCOL_MAP</strong> &#8211; with this one, we simply define the key-value pairs of listeners&#8217; names with security protocols used with them</li>



<li><strong>KAFKA_LISTENERS</strong> &#8211; with this variable, we define all exposed listeners</li>



<li><strong>KAFKA_ADVERTISED_LISTENERS</strong> &#8211; this one, on the other hand, contains all listeners used by clients</li>
</ul>



<p>It&#8217;s worth mentioning here that when working with Docker Compose, the container name becomes a hostname- like <em>kafka1</em>, <em>kafka2</em>, and <em>kafka3</em> in our examples.</p>



<h3 class="wp-block-heading" id="h-6-3-kafka-advertised-listeners-setting">6.3. KAFKA_ADVERTISED_LISTENERS Setting</h3>



<p>As the next step, let&#8217;s see the <code class="EnlighterJSRAW" data-enlighter-language="raw">KAFKA_ADVERTISED_LISTENER</code>. This one, simply allows us to specify a list of coma-separated listeners&#8217; names used by the controller. It&#8217;s necessary when running in a KRaft mode.</p>



<p>Additionally, none of the values specified here can be put inside the advertised listeners covered in the previous paragraph. Otherwise, the following error will be thrown:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The advertised.listeners config must not contain KRaft controller listeners from controller.listener.names when process.roles contains the broker role because Kafka clients that send requests via advertised listeners do not send requests to KRaft controllers &#8212; they only send requests to KRaft brokers.</p>
</blockquote>



<h3 class="wp-block-heading" id="h-6-4-kafka-controller-quorum-voters">6.4. KAFKA_CONTROLLER_QUORUM_VOTERS</h3>



<p>As the next one comes the <code class="EnlighterJSRAW" data-enlighter-language="raw">KAFKA_CONTROLLER_QUORUM_VOTERS</code> environment variable. And this one allows us to specify the set of voters in our node. When it comes to the format, values have to be put in the following order: <code class="EnlighterJSRAW" data-enlighter-language="raw">{id}@{host}:{port}</code> .</p>



<p>So, in our case, we do specify <code class="EnlighterJSRAW" data-enlighter-language="raw">9093</code> of our nodes, because this is the port exposed as a controller.</p>



<h3 class="wp-block-heading" id="h-6-5-kafka-process-roles-variable">6.5. KAFKA_PROCESS_ROLES Variable</h3>



<p>And the last one- the <code class="EnlighterJSRAW" data-enlighter-language="raw">KAFKA_PROCESS_ROLES</code> variable lets us specify the role of the particular broker. The value can be set to either <strong>broker</strong>, <strong>controller</strong>, or <strong>both of them</strong> (just like above).</p>



<h2 class="wp-block-heading" id="h-7-testing">7. Testing</h2>



<p>And finally, we can proceed to the part where we will verify if everything is working, as expected. In my example, I will show you how to use Console Producer/Consumer, but feel free to connect with anything you like.</p>



<p>Firstly, let&#8217;s start our services with the <code class="EnlighterJSRAW" data-enlighter-language="raw">docker compose up</code>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">The broker has been unfenced. Transitioning from RECOVERY to RUNNING.</pre>



<p>After some time, we should see the above for each of our containers.</p>



<p>Additionally, we can validate with a <code class="EnlighterJSRAW" data-enlighter-language="raw">docker ps</code> command:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">CONTAINER ID IMAGE                       COMMAND                CREATED            STATUS            PORTS    NAMES
dc3966ba58a4 confluentinc/cp-kafka:7.2.1 "bash -c '/tmp/run_w…" About a minute ago Up About a minute 9092/tcp kafka3
10242d9d1e6a confluentinc/cp-kafka:7.2.1 "bash -c '/tmp/run_w…" About a minute ago Up About a minute 9092/tcp kafka2
c404c866cde6 confluentinc/cp-kafka:7.2.1 "bash -c '/tmp/run_w…" About a minute ago Up About a minute 9092/tcp kafka1</pre>



<p>As we can clearly see, every container is up and running. Moreover, the desired <code class="EnlighterJSRAW" data-enlighter-language="raw">9092</code> port is exposed for each of them.</p>



<p>When it comes to the testing strategy, I would like to connect to each broker separately and perform a different action:</p>



<ul class="wp-block-list">
<li>firstly, connect to the <code class="EnlighterJSRAW" data-enlighter-language="raw">kafka1</code> and create a new topic called <code class="EnlighterJSRAW" data-enlighter-language="raw">exampleTopic</code></li>



<li>secondly, to <code class="EnlighterJSRAW" data-enlighter-language="raw">kafka2</code> and produce some messages</li>



<li>and finally, read all of the messages from within the <code class="EnlighterJSRAW" data-enlighter-language="raw">kafka3</code></li>
</ul>



<p>To do so, let&#8217;s start with the following:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker exec -it kafka1 /bin/bash
kafka-topics --bootstrap-server kafka1:9092 --create --topic exampleTopic

# Output:
Created topic exampleTopic.

# To exit the container, please specify exit</pre>



<p>As we can see, the <code class="EnlighterJSRAW" data-enlighter-language="raw">exampleTopic</code> was created successfully.</p>



<p>So as the next thing, let&#8217;s produce some messages from <code class="EnlighterJSRAW" data-enlighter-language="raw">kafka2</code> broker:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker exec -it kafka2 /bin/bash
kafka-console-producer --bootstrap-server kafka2:9092 --topic exampleTopic

>codersee
>rules 

# To exit the producer, please hit Ctrl + D. Then to exit the container please type 'exit'.</pre>



<p>No errors have been thrown, which let us assume that everything is good so far.</p>



<p>Nevertheless, to fully make sure, let&#8217;s consume these messages:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker exec -it kafka3 /bin/bash
kafka-console-consumer --bootstrap-server kafka3:9092 --topic exampleTopic --from-beginning

# Output:
codersee
rules

# To exit the consumer, please hit Ctrl + C. As the output, we should see:
^CProcessed a total of 2 messages

# And again, to exit the container please type 'exit'.</pre>



<p>As we can see, everything worked as expected and our brokers are correctly communicating within the cluster.</p>



<h2 class="wp-block-heading" id="h-8-kafka-without-zookeeper-using-docker-compose-summary">8. Kafka Without Zookeeper Using Docker Compose Summary</h2>



<p>And that&#8217;s all for today&#8217;s article on how to<strong> set up Kafka Without Zookeeper using Docker Compose</strong>.</p>



<p>I really hope that you enjoyed this and my previous articles in an Apache Kafka series and I will be forever thankful if you would like to share your feedback with me in the comments section below (positive and negative, as well).</p>



<p>Take care and have a great week! 🙂</p>
<p>The post <a href="https://blog.codersee.com/how-to-set-up-kafka-without-zookeeper-using-docker-compose/">How To Set Up Kafka Without Zookeeper using Docker Compose?</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/how-to-set-up-kafka-without-zookeeper-using-docker-compose/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		
			</item>
		<item>
		<title>How To Deploy Multiple Kafka Brokers With Docker Compose?</title>
		<link>https://blog.codersee.com/how-to-deploy-multiple-kafka-brokers-with-docker-compose/</link>
					<comments>https://blog.codersee.com/how-to-deploy-multiple-kafka-brokers-with-docker-compose/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Mon, 26 Sep 2022 06:00:31 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[kafka]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=5503730</guid>

					<description><![CDATA[<p>In this step-by-step article, I will teach you how to set up multiple Kafka Brokers using Docker Compose, aka multi-node cluster.</p>
<p>The post <a href="https://blog.codersee.com/how-to-deploy-multiple-kafka-brokers-with-docker-compose/">How To Deploy Multiple Kafka Brokers With Docker Compose?</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading" id="h-1-introduction">1. Introduction</h2>



<p>Hello, in this step-by-step guide, I will show you how to set up multiple Kafka brokers with Docker Compose 🙂</p>



<p>In this article, together we will:</p>



<ul class="wp-block-list">
<li>see the minimum Docker Compose config required to spin up <strong>3 Kafka brokers</strong>,</li>



<li>take a closer look at the <strong>configuration</strong>,</li>



<li>get information about active brokers with the <strong>zookeeper-shell</strong> command,</li>



<li>verify whether everything is working as expected with <strong>Console Producer and Consumer tools</strong>.</li>
</ul>



<p>Unlike the previous article about Kafka, I would like to start this one by showing the configuration you&#8217;ll need to set up 3 Kafka brokers. After that, we will go through it as thoroughly as possible to understand it even better and in the end, we will learn how to verify and test our config.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>In my articles, I focus on the practice and getting things done. <br><br>However, if you would like to get a strong understading of DevOps concepts, which are more and more in demand nowadays, then I highly recommend to check out KodeKloud courses, like this <a href="https://shrsl.com/46wvo" rel="sponsored nofollow">Docker</a> and <a href="https://shareasale.com/r.cfm?b=2183704&amp;u=3507867&amp;m=132199&amp;urllink=&amp;afftrack=" rel="sponsored nofollow">Kubernetes</a> learning paths. </cite></blockquote>



<h2 class="wp-block-heading" id="h-2-multiple-kafka-brokers-docker-compose">2. Multiple Kafka Brokers Docker Compose</h2>



<p>With that being said, let&#8217;s see the <strong>docker-compose.yaml</strong>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">version: '3'
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.2.1
    container_name: zookeeper
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
  kafka1:
    image: confluentinc/cp-kafka:7.2.1
    container_name: kafka1
    ports:
      - "8097:8097"
    depends_on:
      - zookeeper
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: EXTERNAL:PLAINTEXT,INTERNAL:PLAINTEXT
      KAFKA_ADVERTISED_LISTENERS: EXTERNAL://localhost:8097,INTERNAL://kafka1:9092
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
  kafka2:
    image: confluentinc/cp-kafka:7.2.1
    container_name: kafka2
    ports:
      - "8098:8098"
    depends_on:
      - zookeeper
    environment:
      KAFKA_BROKER_ID: 2
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: EXTERNAL:PLAINTEXT,INTERNAL:PLAINTEXT
      KAFKA_ADVERTISED_LISTENERS: EXTERNAL://localhost:8098,INTERNAL://kafka2:9092
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
  kafka3:
    image: confluentinc/cp-kafka:7.2.1
    container_name: kafka3
    ports:
      - "8099:8099"
    depends_on:
      - zookeeper
    environment:
      KAFKA_BROKER_ID: 3
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: EXTERNAL:PLAINTEXT,INTERNAL:PLAINTEXT
      KAFKA_ADVERTISED_LISTENERS: EXTERNAL://localhost:8099,INTERNAL://kafka3:9092
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
</pre>


<p>[elementor-template id=&#8221;9007393&#8243;]</p>



<h3 class="wp-block-heading" id="h-2-1-docker-images-versions-and-containers-names">2.1. Docker Images Versions and Containers Names</h3>



<p>Firstly, let&#8217;s take a look at docker images versions and containers names:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.2.1
    container_name: zookeeper
...
  kafka1:
    image: confluentinc/cp-kafka:7.2.1
    container_name: kafka1
...
  kafka2:
    image: confluentinc/cp-kafka:7.2.1
    container_name: kafka2
...
  kafka3:
    image: confluentinc/cp-kafka:7.2.1
    container_name: kafka3
</pre>



<p>As we can see, in our example we will use <strong>Confluent Community Docker Image for Apache Kafka</strong> and <strong>Confluent Docker Image for Zookeeper</strong>&#8211; both in version <strong>7.2.1</strong>.</p>



<p>Additionally, we explicitly set names for our containers, which are discoverable at a hostname identical to the container name. Simply put, container exposed port <code class="EnlighterJSRAW" data-enlighter-language="raw">9092</code> of&nbsp;<code class="EnlighterJSRAW" data-enlighter-language="raw">kafka1</code> will be visible for other containers at <code class="EnlighterJSRAW" data-enlighter-language="raw">kafka1:9092</code>.</p>



<h3 class="wp-block-heading" id="h-2-2-containers-ports">2.2. Containers Ports</h3>



<p>Nextly, let&#8217;s have a look at the exposed ports:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">...
  kafka1:
    ports:
      - "8097:8097"
...
  kafka2:
    ports:
      - "8098:8098"
...
  kafka3:
    ports:
      - "8099:8099"
</pre>



<p>As can be seen, we did it for each service except the <code class="EnlighterJSRAW" data-enlighter-language="raw">zookeeper</code>.</p>



<p>You might already know this, but <code class="EnlighterJSRAW" data-enlighter-language="raw">ports</code> is responsible for exposing container ports to the host machine. Simply put, when running docker-compose on our computer with a <code class="EnlighterJSRAW" data-enlighter-language="raw">123:456</code> setting, we will be able to access a container&#8217;s <code class="EnlighterJSRAW" data-enlighter-language="raw">456</code> port through <code class="EnlighterJSRAW" data-enlighter-language="raw">localhost:123</code>.</p>



<h3 class="wp-block-heading" id="h-2-3-docker-compose-depends-on">2.3. Docker Compose Depends On</h3>



<p>The last thing, I would like to cover before we head to the environment settings of our Kafka brokers is the <code class="EnlighterJSRAW" data-enlighter-language="raw">depends_on</code> .</p>



<p>As we can see, we put it for each Kafka service specifying the <code class="EnlighterJSRAW" data-enlighter-language="raw">zookeeper</code> service as an argument. With this one, we simply make sure that when running a <code class="EnlighterJSRAW" data-enlighter-language="raw">docker compose up</code> command, the <code class="EnlighterJSRAW" data-enlighter-language="raw">zookeeper</code> service will be started before the brokers.</p>



<h2 class="wp-block-heading" id="h-3-multiple-kafka-brokers-environment-variables">3. Multiple Kafka Brokers Environment Variables</h2>



<p>If you&#8217;ve seen my previous article on <a href="https://blog.codersee.com/how-to-set-up-apache-kafka-with-docker/" target="_blank" rel="noopener">how to set up a single-node cluster</a> with docker-compose, then you will notice that it looks a bit different. The main reason behind this is that with multiple Kafka brokers in our node, they have to know how to communicate with each other.</p>



<h3 class="wp-block-heading" id="h-3-1-zookeeper-client-port">3.1. Zookeeper Client Port</h3>



<p>As the first step, let&#8217;s take a look at the <strong>ZOOKEEPER_CLIENT_PORT</strong>.</p>



<p>To put it simply, this one instructs Zookeeper where to listen for connections by clients- Kafka brokers in our example.</p>



<h3 class="wp-block-heading" id="h-3-2-kafka-broker-id">3.2. Kafka Broker ID</h3>



<p>Following, check the <strong>KAFKA_BROKER_ID</strong> variable.</p>



<p>By default, when we don&#8217;t set this one, the Zookeeper will automatically assign a unique broker identifier. Nevertheless, if we would like to specify it explicitly, then we can take care of it with this environment variable. Of course, please keep in mind that this value <strong>has to be unique</strong> across our Kafka brokers.</p>



<h3 class="wp-block-heading" id="h-3-3-kafka-zookeeper-connect">3.3. Kafka Zookeeper Connect</h3>



<p>As the next thing, we set the <strong>KAFKA_ZOOKEEPER_CONNECT</strong> in each service to point to the Zookeeper.</p>



<p>As the value, we have to set our Zookeeper container name with a port set with the ZOOKEEPER_CLIENT_PORT directive.</p>



<h3 class="wp-block-heading" id="h-3-4-kafka-listener-settings">3.4. Kafka Listener Settings</h3>



<p>Finally, let&#8217;s see the rest of the environment variables responsible for communication:</p>



<ul class="wp-block-list">
<li>KAFKA_LISTENER_SECURITY_PROTOCOL_MAP</li>



<li>KAFKA_ADVERTISED_LISTENERS</li>



<li>KAFKA_INTER_BROKER_LISTENER_NAME</li>
</ul>



<p>Unlike the previous ones, I believe it has more sense to take a look at all of them at once to get a better understanding.</p>



<p>With a <strong>KAFKA_ADVERTISED_LISTENERS</strong>, we set a comma-separated list of listeners with their host/IP and port. This value <strong>must be set</strong> in a multi-node environment because otherwise, clients will attempt to connect to the internal host address. Please keep in mind that the listeners&#8217; values (EXTERNAL and INTERNAL in our case), <strong>must be unique here</strong>.</p>



<p>When it comes to the naming here, we can specify our own values, just as I did with EXTERNAL/INTERNAL. Nevertheless, in such a case, we have to specify the correct mapping with <strong>KAFKA_LISTENER_SECURITY_PROTOCOL_MAP</strong>:</p>



<ul class="wp-block-list">
<li>EXTERNAL will be mapped to PLAINTEXT protocol,</li>



<li>INTERNAL will be mapped the same way, as well</li>
</ul>



<p>If you are wondering why have we done it, then the reason is mentioned above- listeners&#8217; values <strong>have to be unique</strong>, so we can&#8217;t specify <code class="EnlighterJSRAW" data-enlighter-language="raw">PLAINTEXT://localhost:8099,PLAINTEXT://kafka3:9092</code> .</p>



<p>Finally, with <strong>KAFKA_INTER_BROKER_LISTENER_NAME</strong> we explicitly specify which listener to use for inter-broker communication.</p>



<h2 class="wp-block-heading" id="h-4-multiple-kafka-with-docker-compose-testing">4. Multiple Kafka With Docker Compose Testing</h2>



<p>With all of that being said, we can finally make use of our docker-compose.yaml file.</p>



<p>To do so, let&#8217;s build and run our services with the following command:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker compose up</pre>



<p>Depending on our machine, it may take a while until everything is up and running.</p>



<p>In the end, we should see the following:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">...Recorded new controller, from now on will use broker...</pre>



<h3 class="wp-block-heading" id="h-4-1-verify-all-containers-running">4.1. Verify All Containers Running</h3>



<p>As the next step, let&#8217;s open up a new terminal window and list up and running containers with <code class="EnlighterJSRAW" data-enlighter-language="raw">docker ps</code>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># Output:
CONTAINER ID IMAGE                           COMMAND                CREATED        STATUS        PORTS                            NAMES
cf892aeb5793 confluentinc/cp-kafka:7.2.1     "/etc/confluent/dock…" 54 minutes ago Up 54 minutes 0.0.0.0:8098->8098/tcp, 9092/tcp kafka2
a10bef7b6a54 confluentinc/cp-kafka:7.2.1     "/etc/confluent/dock…" 54 minutes ago Up 54 minutes 0.0.0.0:8099->8099/tcp, 9092/tcp kafka3
46c9ca4862b6 confluentinc/cp-kafka:7.2.1     "/etc/confluent/dock…" 54 minutes ago Up 54 minutes 0.0.0.0:8097->8097/tcp, 9092/tcp kafka1
eef5ebec8519 confluentinc/cp-zookeeper:7.2.1 "/etc/confluent/dock…" 54 minutes ago Up 54 minutes 2181/tcp, 2888/tcp, 3888/tcp     zookeeper</pre>



<p>As we can clearly see, all Kafka containers have their ports exposed to our host and Zookeeper is reachable through <code class="EnlighterJSRAW" data-enlighter-language="raw">2181</code>, as well.</p>



<h3 class="wp-block-heading" id="h-4-2-verify-active-kafka-brokers-using-zookeeper-shell">4.2. Verify Active Kafka Brokers Using zookeeper-shell</h3>



<p>Following, let&#8217;s run a <code class="EnlighterJSRAW" data-enlighter-language="raw">zookeeper-shell</code> on the <code class="EnlighterJSRAW" data-enlighter-language="raw">zookeeper</code> container:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker exec -it zookeeper /bin/zookeeper-shell localhost:2181

# Output:
Connecting to localhost:2181
Welcome to ZooKeeper!
JLine support is disabled

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
</pre>



<p>What&#8217;s important to mention here is that we run a <code class="EnlighterJSRAW" data-enlighter-language="raw">zookeeper-shell</code> command <strong>from within the container</strong>. That&#8217;s the reason why the <code class="EnlighterJSRAW" data-enlighter-language="raw">zookeeper_host</code> value is set to <code class="EnlighterJSRAW" data-enlighter-language="raw">localhost</code> and accessible, even though we haven&#8217;t exposed it.</p>



<p>Thanks to the <code class="EnlighterJSRAW" data-enlighter-language="raw">-it</code> flags, we can use the shell from our terminal.</p>



<h3 class="wp-block-heading" id="h-4-3-get-ids-of-currently-active-brokers">4.3. Get IDs of Currently Active Brokers</h3>



<p>So, to get the identifiers of currently active Kafka Brokers, we can use the following:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">ls /brokers/ids

# Output:
[1, 2, 3]
</pre>



<p>The above output clearly indicates that everything is working, as expected.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Note: to close the zookeeper-shell, please hit <strong>Ctrl + C </strong>(and please do it before heading to the Producer/Consumer parts)</p>
</blockquote>



<h2 class="wp-block-heading" id="h-5-publish-messages-with-console-producer">5. Publish Messages With Console Producer</h2>



<p>After we know all of the above, we can finally send messages using the Console Producer.</p>



<p>As the first step, let&#8217;s run bash from a randomly picked Kafka broker container:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker exec -it kafka2 /bin/bash
</pre>



<p>Nextly, let&#8217;s create a new topic called <code class="EnlighterJSRAW" data-enlighter-language="raw">randomTopic</code>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">kafka-topics --bootstrap-server localhost:9092 --create --topic randomTopic

# Output:
Created topic randomTopic.
</pre>



<p>Please note, that given the <code class="EnlighterJSRAW" data-enlighter-language="raw">kafka-topics</code> script is run inside of the container, we point to the <code class="EnlighterJSRAW" data-enlighter-language="raw">9092</code> port of <code class="EnlighterJSRAW" data-enlighter-language="raw">localhost</code>.</p>



<p>Following, we can produce some messages to our new topic: <code class="EnlighterJSRAW" data-enlighter-language="raw"></code></p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">kafka-console-producer --bootstrap-server localhost:9092 --topic randomTopic 

> lorem
> ipsum

# When we finish, we have to hit ctrl + d</pre>



<p>Of course, we could do that from any other broker container (and in order to exit bash, we need to type <code class="EnlighterJSRAW" data-enlighter-language="raw">exit</code> in the command prompt).</p>



<h2 class="wp-block-heading" id="h-6-read-messages-with-console-consumer">6. Read Messages With Console Consumer</h2>



<p>As the last step, let&#8217;s connect to another Apache Kafka broker and read previously sent messages:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker exec -it kafka3 /bin/bash

kafka-console-consumer --bootstrap-server localhost:9092 --topic randomTopic --from-beginning

# Output:
lorem
ipsum
</pre>



<p>As we can clearly see, the output contains all messages, we&#8217;ve sent using Console Producer.</p>



<h2 class="wp-block-heading" id="h-7-summary">7. Summary</h2>



<p>And that would be all for this article on how to set up multiple Apache Kafka brokers using Docker Compose and produce/consume messages. As a follow-up, I would recommend checking out <a href="https://www.confluent.io/blog/kafka-client-cannot-connect-to-broker-on-aws-on-docker-etc/" target="_blank" rel="noopener">this Confluent article</a> about connecting to Kafka with different configurations.</p>



<p>Finally, let me know your thoughts in the comment section bellow, dear friend! 😀</p>
<p>The post <a href="https://blog.codersee.com/how-to-deploy-multiple-kafka-brokers-with-docker-compose/">How To Deploy Multiple Kafka Brokers With Docker Compose?</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/how-to-deploy-multiple-kafka-brokers-with-docker-compose/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How To Set Up Apache Kafka With Docker?</title>
		<link>https://blog.codersee.com/how-to-set-up-apache-kafka-with-docker/</link>
					<comments>https://blog.codersee.com/how-to-set-up-apache-kafka-with-docker/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 20 Sep 2022 05:00:52 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[kafka]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=5503717</guid>

					<description><![CDATA[<p>In this step by step guide, I will show you how to set up Apache Kafka with Docker Compose from scratch and test it with a command line.</p>
<p>The post <a href="https://blog.codersee.com/how-to-set-up-apache-kafka-with-docker/">How To Set Up Apache Kafka With Docker?</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading" id="h-1-introduction">1. Introduction</h2>



<p>If you would like to learn how to set up<strong> Apache Kafka</strong> using<strong> Docker Compose</strong> then you&#8217;ve come to the right place 🙂</p>



<p>In this article, together we will:</p>



<ul class="wp-block-list">
<li>see the bare minimum Docker Compose configuration we must provide to spin up an environment with <strong>exactly one Kafka broker and Zookeeper instance</strong>,</li>



<li>take a closer look at the required configurations,</li>



<li>verify our setup using Console Producer and Consumer tools.</li>
</ul>



<p>If you will enjoy this tutorial, then I highly encourage you to check out the continuations:</p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/how-to-deploy-multiple-kafka-brokers-with-docker-compose/">How To Deploy Multiple Kafka Brokers With Docker Compose?</a></li>



<li><a href="https://blog.codersee.com/how-to-set-up-kafka-without-zookeeper-using-docker-compose/">How To Set Up Kafka Without Zookeeper using Docker Compose</a></li>
</ul>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>In my articles, I focus on the practice and getting things done. <br><br>However, if you would like to get a strong understading of DevOps concepts, which are more and more in demand nowadays, then I highly recommend to check out KodeKloud courses, like this <a href="https://shrsl.com/46wvo" rel="sponsored nofollow">Docker</a> and <a href="https://shareasale.com/r.cfm?b=2183704&amp;u=3507867&amp;m=132199&amp;urllink=&amp;afftrack=" rel="sponsored nofollow">Kubernetes</a> learning paths. </cite></blockquote>



<h2 class="wp-block-heading" id="h-2-what-is-apache-kafka">2. What is Apache Kafka?</h2>



<p>Before we start, I feel obliged to provide at least a brief explanation of what Apache Kafka and Zookeeper are. Please skip the following two paragraphs if you want to get straight to the practice part of this tutorial.</p>



<p>Well, Apache Kafka by definition is an open-source distributed event streaming platform. Simply put, it&#8217;s a platform widely used to work with real-time streaming data pipelines and to integrate applications to work with such streams. It&#8217;s mainly responsible for:</p>



<ul class="wp-block-list">
<li>publishing/subscribing to the stream of records,</li>



<li>their real-time processing,</li>



<li>and their ordered storage</li>
</ul>



<p>But let&#8217;s just be honest- it&#8217;s almost impossible to describe Kafka in a couple of sentences and I highly encourage you to visit the Apache Intro page after this tutorial <a href="https://kafka.apache.org/intro" target="_blank" rel="noopener">right here</a>.</p>



<h2 class="wp-block-heading" id="h-3-what-is-apache-zookeeper">3. What is Apache ZooKeeper?</h2>



<p>Or the question should rather be why do I even need another service to work with Apache Kafka?</p>



<p>Just like a zookeeper in a zoo takes care of the animals, Apache Zookeeper &#8220;takes care&#8221; of all the Kafka brokers in a cluster. It&#8217;s mainly responsible for:</p>



<ul class="wp-block-list">
<li>tracking the cluster state- like which brokers are a part of the cluster, sending notifications to Kafka in case of changes</li>



<li>topics configuration</li>



<li>controller election</li>



<li>access control lists and quotas</li>
</ul>



<p>Nevertheless, please keep in mind that the Kafka team is constantly working on <strong>replacing the ZooKeeper with Apache Kafka Raft (KRaft)</strong>. To be more specific- the 2.8.0 version introduced early access to this functionality, but at the moment of publishing this article, it&#8217;s still not production-ready.</p>



<figure class="wp-block-image aligncenter"><a href="https://codersee.com/newsletter/"><img loading="lazy" decoding="async" width="1024" height="576" src="http://blog.codersee.com/wp-content/uploads/2022/05/join_newsletter-1024x576.png" alt="Image shows two ebooks people can get for free after joining newsletter" class="wp-image-3002956" srcset="https://blog.codersee.com/wp-content/uploads/2022/05/join_newsletter-1024x576.png 1024w, https://blog.codersee.com/wp-content/uploads/2022/05/join_newsletter-300x169.png 300w, https://blog.codersee.com/wp-content/uploads/2022/05/join_newsletter-768x432.png 768w, https://blog.codersee.com/wp-content/uploads/2022/05/join_newsletter.png 1440w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure>



<h2 class="wp-block-heading" id="h-4-bare-minimum-kafka-docker-compose">4. Bare Minimum Kafka Docker Compose</h2>



<p>With all of that being said, let&#8217;s have a look at the bare minimum version of the docker-compose.yml file. This configuration is necessary to spin up exactly 1 Kafka and Zookeeper instance:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">version: '3'
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.2.1
    container_name: zookeeper
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
  kafka:
    image: confluentinc/cp-kafka:7.2.1
    container_name: kafka
    ports:
      - "8098:8098"
    depends_on:
      - zookeeper
    environment:
      KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:8098
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1</pre>



<p>Again, before we head to the next commands, let&#8217;s take a couple of minutes to understand, what exactly is going on here.</p>



<h3 class="wp-block-heading" id="h-4-1-docker-images-version-and-containers">4.1. Docker Images Version And Containers</h3>



<p>Firstly, let&#8217;s take a look at this piece of code:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">version: '3'
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.2.1
    container_name: zookeeper
...   
  kafka:
    image: confluentinc/cp-kafka:7.2.1
    container_name: kafka</pre>



<p>As we can see, with this config we will spin up two services named <code class="EnlighterJSRAW" data-enlighter-language="raw">kafka</code> and <code class="EnlighterJSRAW" data-enlighter-language="raw">zookeeper</code>. The <code class="EnlighterJSRAW" data-enlighter-language="raw">container_name</code> will set specified names for our containers, instead of letting Docker generate them. Finally, we will use the 7.2.1 versions of <em>Confluent Community Docker Image for Apache Kafka</em> and <em>Confluent Docker Image for Zookeeper</em>.</p>



<h3 class="wp-block-heading" id="h-4-2-additional-kafka-container-configs">4.2. Additional Kafka Container Configs</h3>



<p>As the next step, let&#8217;s have a quick look at these four lines:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">ports:
  - "8098:8098"
depends_on:
  - zookeeper</pre>



<p>As the name suggests, the <strong>ports&nbsp;</strong>command is responsible for exposing ports, so with this setting, port 8098 of our container will be accessible through the 8098 port of the host machine. Additionally, the <strong>depends_on</strong> will make sure to start the zookeeper container before the kafka.</p>



<h2 class="wp-block-heading" id="h-5-required-settings">5. Required Settings</h2>



<p>As the next step, let&#8217;s take a closer look at the required settings for both Kafka and Zookeeper in our Docker Compose settings.</p>



<h3 class="wp-block-heading" id="h-5-1-required-zookeeper-settings">5.1. Required Zookeeper Settings</h3>



<p>As we can see in our example, the only environment we specified is <strong>ZOOKEEPER_CLIENT_PORT</strong>. This one instructs Zookeeper where it should listen for connections by clients- Kafka in our case.</p>



<p>Additionally, when running in clustered mode, we have to set the <strong>ZOOKEEPER_SERVER_ID</strong> (but it&#8217;s not the case here).</p>



<h3 class="wp-block-heading" id="h-5-2-required-confluent-kafka-settings">5.2. Required Confluent Kafka Settings</h3>



<p>When it comes to the Confluent Kafka Docker image, here is the list of the required environment variables:</p>



<ul class="wp-block-list">
<li><strong>KAFKA_ZOOKEEPER_CONNECT</strong> &#8211; instructing Kafka where it can find the Zookeeper</li>



<li><strong>KAFKA_ADVERTISED_LISTENERS</strong> &#8211; specifying the advertised hostname, which clients can reach out to. Moreover, this value is sent to the Zookeeper</li>
</ul>



<p>Please keep in mind, that in order to run only one instance of Kafka (aka single-node cluster), we have to additionally specify <strong>KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR</strong> set to <strong>1</strong>. Its default value is 3 and if we don&#8217;t do that, we will get into this error: <em>org.apache.kafka.common.errors.InvalidReplicationFactorException: Replication factor: 3 larger than available brokers: 1</em></p>



<h2 class="wp-block-heading" id="h-6-verify-kafka-docker-compose-config">6. Verify Kafka Docker Compose Config</h2>



<p>With all of that being said, we can finally check out if everything is working, as expected.</p>



<h3 class="wp-block-heading" id="h-6-1-start-containers">6.1. Start Containers</h3>



<p>As the first step, we have to start our containers:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker compose up -d</pre>



<p>The <strong>-d</strong> flag instructs the docker to run containers in a detached mode.</p>



<p>Of course, we can verify everything by running the <code class="EnlighterJSRAW" data-enlighter-language="raw">docker ps</code> command:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># example output:
CONTAINER ID IMAGE                           COMMAND                CREATED        STATUS       PORTS                            NAMES
38b41bc43676 confluentinc/cp-kafka:7.2.1     "/etc/confluent/dock…" 42 seconds ago Up 4 seconds 0.0.0.0:8098->8098/tcp, 9092/tcp kafka
45484184f330 confluentinc/cp-zookeeper:7.2.1 "/etc/confluent/dock…" 42 seconds ago Up 5 seconds 2181/tcp, 2888/tcp, 3888/tcp     zookeeper

</pre>



<h3 class="wp-block-heading" id="h-6-2-create-kafka-topic">6.2. Create Kafka Topic</h3>



<p>Nextly, let&#8217;s create a new Kafka topic:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker exec kafka kafka-topics --bootstrap-server localhost:8098 --create --topic example</pre>



<p>As a word of explanation, the <strong>kafka-topics</strong> is a shell script that allows us to execute a <strong>TopicCommand</strong> tool. It&#8217;s a command-line tool that can manage and list topics in a Kafka cluster. Additionally, we have to specify the listener hostname (specified with KAFKA_ADVERTISED_LISTENERS) with <strong>&#8211;bootstrap-server</strong>.</p>



<p>When the topic is created we should see the following message:<strong> &#8220;Created topic example.&#8221;</strong></p>



<h3 class="wp-block-heading" id="h-6-3-run-kafka-producer">6.3. Run Kafka Producer</h3>



<p>Following, let&#8217;s run a console producer with the <strong>kafka-console-producer:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker exec --interactive --tty kafka kafka-console-producer --bootstrap-server localhost:8098 --topic example</pre>



<p>Please keep in mind that this command will start the producer and it will wait for our input (and you should notice the <code class="EnlighterJSRAW" data-enlighter-language="raw">&gt;</code> sign). Please specify a couple of messages and hit <strong>Ctrl + D</strong> after you finish:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">>lorem
>ipsum
# hit Ctrl + D</pre>



<h3 class="wp-block-heading" id="h-6-4-run-kafka-consumer">6.4. Run Kafka Consumer</h3>



<p>As the last step, let&#8217;s run a console consumer with the <strong>kafka-console-consumer </strong>command:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker exec --interactive --tty kafka kafka-console-consumer --bootstrap-server localhost:8098 --topic example --from-beginning</pre>



<p>This time, we should see that our messages are printed to the output successfully (and to finish please hit <strong>Ctrl+ C</strong>):</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">lorem
ipsum</pre>



<h2 class="wp-block-heading" id="h-7-kafka-with-docker-compose-summary">7. Kafka With Docker Compose Summary</h2>



<p>And that would be all for this step-by-step guide on <strong>How To Set Up Apache Kafka With Docker Compose</strong>.</p>



<p>Let me know about your thoughts in the comment section, or reach out through the <a href="https://codersee.com/contact/">contact</a> form- I highly appreciate your thoughts, comments and feedback.</p>



<p>Take care brother/sister!</p>
<p>The post <a href="https://blog.codersee.com/how-to-set-up-apache-kafka-with-docker/">How To Set Up Apache Kafka With Docker?</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/how-to-set-up-apache-kafka-with-docker/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: blog.codersee.com @ 2026-05-11 10:02:00 by W3 Total Cache
-->