<?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>Uncategorized Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/category/uncategorized/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.codersee.com/category/uncategorized/</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>Uncategorized Archives - Codersee blog- Kotlin on the backend</title>
	<link>https://blog.codersee.com/category/uncategorized/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>What are HTTP ETags?</title>
		<link>https://blog.codersee.com/what-are-http-etags/</link>
					<comments>https://blog.codersee.com/what-are-http-etags/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Sun, 19 Jan 2025 06:00:00 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[REST]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=16011368</guid>

					<description><![CDATA[<p>In this article, we will learn what ETags are and how they relate to If-None-Match, If-Match headers, and 304 and 412 HTTP status codes. </p>
<p>The post <a href="https://blog.codersee.com/what-are-http-etags/">What are HTTP ETags?</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>At the end of this blog post, you will know:</p>



<ul class="wp-block-list">
<li>what problems do they solve?</li>



<li>what is an ETag and how is it calculated?</li>



<li>how do they relate to <code data-enlighter-language="generic" class="EnlighterJSRAW">If-None-Match</code> and <code data-enlighter-language="generic" class="EnlighterJSRAW">If-Match</code> headers?</li>



<li>what do <code data-enlighter-language="generic" class="EnlighterJSRAW">412 Precondition Failed</code> and <code data-enlighter-language="generic" class="EnlighterJSRAW">304 Not Modified</code> got do to with all of that?</li>
</ul>



<p>Of course, this is more theoretical content, and you can expect some pragmatic examples with Spring Boot and Ktor soon😉<a href="https://blog.codersee.com/what-are-http-etags/" target="_blank" rel="noreferrer noopener"></a></p>



<h2 class="wp-block-heading" id="h-video-content">Video Content</h2>



<p>As always, if you prefer a video content, then please check out my latest YouTube video:</p>



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



<p>If you find this content useful,<strong> please leave a subscription</strong> 🙂</p>



<h2 class="wp-block-heading" id="h-what-problems-etags-solve">What Problems ETags Solve?</h2>



<p>Let&#8217;s start everything by figuring out the most important thing- <strong>what actual problems Etags help us solve? </strong>At the end of the day, what is the point of any additional work if it does not bring any value?😉</p>



<p>Well, long story short, the Entity Tags help us to deal with two problems:</p>



<ul class="wp-block-list">
<li>waste of bandwidth and resources to consume the response of unchanged data,</li>



<li>updating data that changed since we last fetched them.</li>
</ul>



<h2 class="wp-block-heading" id="h-problem-1-waste-of-bandwidth-amp-resources">Problem #1- Waste of Bandwidth &amp; Resources</h2>



<p>So what do we exactly mean by problem #1- the waste of bandwidth? Let&#8217;s imagine a pretty standard client &lt;-&gt; server communication. The client asks the server for some data, like product details (also known as <em>entity</em>, or <em>resource </em>in terms of REST). The server fetches necessary data from the database, sometimes performs additional actions, and eventually returns them to the client (let&#8217;s say a mobile application) with <code data-enlighter-language="generic" class="EnlighterJSRAW">200 OK</code> status code.</p>



<p>Then, our client consumes that response- for example, converts the returned JSON and displays that to the user.</p>



<p>And up until now, everything is perfectly fine. But, let&#8217;s imagine a situation, in which our product remains untouched- no price, no title, nothing changes- but our client keeps querying the server. In such a case, the <strong>bandwidth </strong>used to transfer the exact same data over and over, and the client resources used to process that could be utilized better, right?</p>



<p>And as you might have guessed- that&#8217;s where ETags come with help🙂</p>



<h2 class="wp-block-heading" id="h-what-is-an-etag-and-how-it-is-calculated">What is an ETag and How It is Calculated?</h2>



<p>Long story short- ETag (aka Entity Tag)- <strong>uniquely identifies a version of the entity</strong>.</p>



<p>In other words, it is a bunch of characters (like <code data-enlighter-language="generic" class="EnlighterJSRAW">0633010ca130d326f83ef9fee25491e7e</code>) that remain constant for a particular version of an entity. If the entity (product in our example) is updated, a new tag is generated and remains constant until the next update.</p>



<p>From the implementational perspective, this can be anything from a version field in the database, a hashed timestamp of an update, or a hash of the response. Oftentimes, this hash is obtained using the <strong>MD5 algorithm</strong>.</p>



<h2 class="wp-block-heading" id="h-solve-problem-1-with-etags">Solve Problem #1 With ETags</h2>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="576" src="http://blog.codersee.com/wp-content/uploads/2025/01/codersee_etags_bandwidth_waste_solution-1024x576.png" alt="Image is a diagram explaining HTTP communication between client and server using ETags " class="wp-image-16011373" srcset="https://blog.codersee.com/wp-content/uploads/2025/01/codersee_etags_bandwidth_waste_solution-1024x576.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/01/codersee_etags_bandwidth_waste_solution-300x169.png 300w, https://blog.codersee.com/wp-content/uploads/2025/01/codersee_etags_bandwidth_waste_solution-768x432.png 768w, https://blog.codersee.com/wp-content/uploads/2025/01/codersee_etags_bandwidth_waste_solution-1536x864.png 1536w, https://blog.codersee.com/wp-content/uploads/2025/01/codersee_etags_bandwidth_waste_solution.png 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Alright, so how we can use this identifier/tag to fix the waste in our client &lt;-&gt; server scenario? Well, pretty easily.</p>



<p>From the server perspective, whenever clients ask us for the data, we fetch them, calculate the ETag, and return <code data-enlighter-language="generic" class="EnlighterJSRAW">200 OK</code> response with an <strong>additional </strong><code><strong>ETag</strong></code><strong> header. </strong>For example: <code data-enlighter-language="generic" class="EnlighterJSRAW">ETag: "0e927523c29b6de3a9e3653df0e11762d"</code> .</p>



<p>The client, on the other hand, is responsible for persisting ETags locally and later, sending them as an <strong>additional <code data-enlighter-language="generic" class="EnlighterJSRAW">If-None-Match</code> header</strong>, example: <code data-enlighter-language="generic" class="EnlighterJSRAW">If-None-Match: "0e927523c29b6de3a9e3653df0e11762d"</code> .</p>



<p>And whenever our server detects the <code data-enlighter-language="generic" class="EnlighterJSRAW">If-None-Match</code> , it fetches the data from the database, calculates the hash, and compares them. If they match, it means the resource has not changed since the last request. And in such a case, the server will send an <strong>empty response body </strong>with <code data-enlighter-language="generic" class="EnlighterJSRAW">304 Not Modified</code> HTTP status code. If they don&#8217;t match, then the request body is sent along with <code data-enlighter-language="generic" class="EnlighterJSRAW">200 OK</code> status code (and another ETag value).</p>



<p>As we can see, this allows us to fix the issue of wasted bandwidth, and potentially, the client resources. However, this does not prevent the client from making requests to the server, and the server from computing the data. For that purpose, we should lean towards <strong>client caching</strong> and proper <strong>cache control</strong>.</p>



<h3 class="wp-block-heading">Problem #2- Update Changed Entity</h3>



<p>At this point, we know both the issue and solution for repeated queries. So in this paragraph, let&#8217;s take a closer look into issue #2.</p>



<p>This time, let&#8217;s imagine we have 2 users that simultaneously fetched the same version of the entity- product in our case. Now, <em>user 1 </em>decides to update the product title. He hits update, his application sends a PUT request and the update succeeds. As we are using HTTP, <em>user 2</em> (client) is not aware of the update. The application still displays old data. So, if he decides now to update some other field, like description, the PUT request may override the title with old data!</p>



<h3 class="wp-block-heading">How ETags Can Help With Collisions?</h3>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="576" src="http://blog.codersee.com/wp-content/uploads/2025/01/codersee_etags_collision_solution-1024x576.png" alt="This image presents a HTTP communicate schema for the entity update and how ETags can help to avoid updating changed entity" class="wp-image-16011378" srcset="https://blog.codersee.com/wp-content/uploads/2025/01/codersee_etags_collision_solution-1024x576.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/01/codersee_etags_collision_solution-300x169.png 300w, https://blog.codersee.com/wp-content/uploads/2025/01/codersee_etags_collision_solution-768x432.png 768w, https://blog.codersee.com/wp-content/uploads/2025/01/codersee_etags_collision_solution-1536x864.png 1536w, https://blog.codersee.com/wp-content/uploads/2025/01/codersee_etags_collision_solution.png 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>In order to avoid this situation, we must make a small change in our API.</p>



<p>So, just like previously, when both clients make their 1st request, they get <code data-enlighter-language="generic" class="EnlighterJSRAW">200 OK</code> response with an <strong>additional <code data-enlighter-language="generic" class="EnlighterJSRAW">ETag</code> header. </strong>For example: <code data-enlighter-language="generic" class="EnlighterJSRAW">ETag: "0e927523c29b6de3a9e3653df0e11762d"</code> .</p>



<p>Now, whenever any client application wants to update the product, it sends a PUT request with an <strong>additional <code data-enlighter-language="generic" class="EnlighterJSRAW">If-Match</code> header</strong>, like: <code data-enlighter-language="generic" class="EnlighterJSRAW">If-Match: "0e927523c29b6de3a9e3653df0e11762d"</code><strong> . </strong>And this time, the server calculates the ETag of the desired entity <strong>before making any changes</strong>. If it does not match, it means that the entity was changed in the meantime, and the server notifies our client with <code data-enlighter-language="generic" class="EnlighterJSRAW">412 Precondition Failed</code> status code.</p>



<p>Thanks to that, the client application has the chance to inform users about the updated entity/resource and handle that appropriately (for example, by displaying the popup).</p>



<p>This time, we can clearly see that the solution is not about performance and resources- it is about <strong>consistency</strong>.</p>



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



<p>And basically that is all for this article about ETags.</p>



<p>I hope it helped you to understand them better and provide you with ideas on how to apply them in your project.</p>



<p>Let me know your thoughts in the comments section!🙂</p>
<p>The post <a href="https://blog.codersee.com/what-are-http-etags/">What are HTTP ETags?</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/what-are-http-etags/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Codersee Course Platform is Ready</title>
		<link>https://blog.codersee.com/codersee-course-platform-is-ready/</link>
					<comments>https://blog.codersee.com/codersee-course-platform-is-ready/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Sun, 08 Dec 2024 15:26:08 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=16011340</guid>

					<description><![CDATA[<p>I am writing this post, because I would like to share some exciting news with you- the Codersee course platform has been fully migrated to codersee.com 🎉 What does it look like? What if I already joined a course purchased through the Teachable platform? What is coming next? Let&#8217;s figure out answers to those questions [&#8230;]</p>
<p>The post <a href="https://blog.codersee.com/codersee-course-platform-is-ready/">Codersee Course Platform is Ready</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>I am writing this post, because I would like to share some exciting news with you- <strong>the Codersee course platform has been fully migrated to codersee.com</strong> 🎉</p>



<p>What does it look like? What if I already joined a course purchased through the Teachable platform? What is coming next? </p>



<p>Let&#8217;s figure out answers to those questions now 😉 </p>



<h2 class="wp-block-heading" id="h-what-if-i-already-joined-a-course-purchased-through-the-teachable-platform">What if I already joined a course purchased through the Teachable platform? </h2>



<p>Well, the answer is the most popular answer in the IT world- &#8220;it depends&#8221; 😉 </p>



<p>If you purchased &#8220;The Complete Kotlin Course&#8221; or &#8220;Kotlin Handbook&#8221;, then the only thing you need to do today is to navigate to the <a href="https://blog.codersee.com/login/">login page</a> and click the &#8220;Lost your password?&#8221; link: </p>



<figure class="wp-block-image aligncenter size-full"><img decoding="async" width="572" height="657" src="http://blog.codersee.com/wp-content/uploads/2024/12/image.png" alt="Image presents the sign in page with lost your password link" class="wp-image-16011342" srcset="https://blog.codersee.com/wp-content/uploads/2024/12/image.png 572w, https://blog.codersee.com/wp-content/uploads/2024/12/image-261x300.png 261w" sizes="(max-width: 572px) 100vw, 572px" /></figure>



<p>After that, you will be redirected to the &#8220;My Account&#8221; page and prompted for your email: </p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="524" src="http://blog.codersee.com/wp-content/uploads/2024/12/image-1-1024x524.png" alt="Image is a screenshot of &quot;my account&quot; with reset password form." class="wp-image-16011343" srcset="https://blog.codersee.com/wp-content/uploads/2024/12/image-1-1024x524.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/12/image-1-300x154.png 300w, https://blog.codersee.com/wp-content/uploads/2024/12/image-1-768x393.png 768w, https://blog.codersee.com/wp-content/uploads/2024/12/image-1.png 1176w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Please specify the e-mail you used when purchasing the course, and you will receive a reset link to your inbox. After you reset your password and successfully sign in, you will be automatically enrolled in the course on the platform.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>If you encounter any issues during this process, or if you simply have any questions, then please do not hesitate and let me know to <em>contact@codersee.com</em>  </p>
</blockquote>



<h2 class="wp-block-heading" id="h-how-does-it-look-like">How Does It Look Like? </h2>



<p>One of the most important points of the migration was to improve the User Experience. And I really hope I managed to do so 😉 </p>



<p>Just like above, if we already have our account, we can navigate to the login page using the button in the header: </p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="609" src="http://blog.codersee.com/wp-content/uploads/2024/12/image-2-1024x609.png" alt="Image is a screenshot of the main page and focuses on the sign in button in the header. " class="wp-image-16011344" srcset="https://blog.codersee.com/wp-content/uploads/2024/12/image-2-1024x609.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/12/image-2-300x178.png 300w, https://blog.codersee.com/wp-content/uploads/2024/12/image-2-768x457.png 768w, https://blog.codersee.com/wp-content/uploads/2024/12/image-2.png 1365w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>When we click on it, we will see the login form and when signed in successfully, we will reach the <strong>&#8220;My Learning&#8221;</strong> page: </p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="616" src="http://blog.codersee.com/wp-content/uploads/2024/12/image-3-1024x616.png" alt="" class="wp-image-16011345" srcset="https://blog.codersee.com/wp-content/uploads/2024/12/image-3-1024x616.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/12/image-3-300x180.png 300w, https://blog.codersee.com/wp-content/uploads/2024/12/image-3-768x462.png 768w, https://blog.codersee.com/wp-content/uploads/2024/12/image-3.png 1317w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>On this page, we will see all courses we are enrolled in with the progress bar and a link to the last step in the course. </p>



<p>Let&#8217;s click the &#8220;Go to course&#8221; button then: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="513" src="http://blog.codersee.com/wp-content/uploads/2024/12/image-4-1024x513.png" alt="Image presents the focus mode of the course lesson" class="wp-image-16011346" srcset="https://blog.codersee.com/wp-content/uploads/2024/12/image-4-1024x513.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/12/image-4-300x150.png 300w, https://blog.codersee.com/wp-content/uploads/2024/12/image-4-768x385.png 768w, https://blog.codersee.com/wp-content/uploads/2024/12/image-4-1536x770.png 1536w, https://blog.codersee.com/wp-content/uploads/2024/12/image-4.png 1920w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>As we can see we are redirected to the <strong>focus mode</strong> of our course. This is a great way to avoid any disruptions and focus on the learning process. </p>



<p>However, if we would like to get back, then the only thing we need to do is to <strong>click the logo in the left upper corner. </strong></p>



<p>Whenever we are not in the focus mode, we can navigate to the &#8220;My Learning&#8221; page using the option in the header, or with the dropdown. </p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="485" height="317" src="http://blog.codersee.com/wp-content/uploads/2024/12/image-5.png" alt="" class="wp-image-16011347" srcset="https://blog.codersee.com/wp-content/uploads/2024/12/image-5.png 485w, https://blog.codersee.com/wp-content/uploads/2024/12/image-5-300x196.png 300w" sizes="auto, (max-width: 485px) 100vw, 485px" /></figure>



<p>Additionally, we can see the link to <strong>Account Details</strong>:</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="613" src="http://blog.codersee.com/wp-content/uploads/2024/12/image-6-1024x613.png" alt="Image presents the my account page view " class="wp-image-16011348" srcset="https://blog.codersee.com/wp-content/uploads/2024/12/image-6-1024x613.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/12/image-6-300x180.png 300w, https://blog.codersee.com/wp-content/uploads/2024/12/image-6-768x460.png 768w, https://blog.codersee.com/wp-content/uploads/2024/12/image-6.png 1412w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>In here, we can find all the details related to our account, like orders, downloads (products), billing addresses, as well as the password reset form and logout. </p>



<p>And basically that is all for this introduction, stay tuned, because great news are coming in the beginning of 2025 🙂 </p>
<p>The post <a href="https://blog.codersee.com/codersee-course-platform-is-ready/">Codersee Course Platform is Ready</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/codersee-course-platform-is-ready/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Ktlint with pre-commit Hook: Git Hooks in Kotlin Made Easy</title>
		<link>https://blog.codersee.com/git-hooks-kotlin-automate-ktlint-pre-commit-hook/</link>
					<comments>https://blog.codersee.com/git-hooks-kotlin-automate-ktlint-pre-commit-hook/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 05 Dec 2023 05:30:00 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[code style]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[git hook]]></category>
		<category><![CDATA[ktlint]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9008405</guid>

					<description><![CDATA[<p>In this article, we will learn how easily we can automate our Kotlin project with git hooks on the example of Ktlint pre-commit check.</p>
<p>The post <a href="https://blog.codersee.com/git-hooks-kotlin-automate-ktlint-pre-commit-hook/">Ktlint with pre-commit Hook: Git Hooks in Kotlin Made Easy</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this article, we will learn how easily we can automate our Kotlin project with <strong>git hooks</strong> on the example of <strong>Ktlint pre-commit check</strong>. </p>



<p>At the end of this tutorial, you will know precisely: </p>



<ul class="wp-block-list">
<li>what are Git client-side Hooks, </li>



<li>what types of client-side hooks Git offers, </li>



<li>how to implement an automated code check before we commit our changes, </li>



<li>and how to combine that together with Kotlin and Gradle. </li>
</ul>



<p>And although this guide may seem a bit specific, you can trust me that after reading it you will be able to adjust this knowledge to many other cases in your real-life scenarios. </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/git-hooks-kotlin-automate-ktlint-pre-commit-hook/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FD_oQwhcWr4M%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-git-hooks-and-client-side-types">Git Hooks And Client-Side Types </h2>



<p>Before we see how to do a Ktlint check as a pre-commit hook, let&#8217;s learn a bit about Git Hooks.</p>



<p>Git Hooks, in simple words, are a way to trigger custom scripts, whenever a particular action happens in our repository. An action can be a <em>rebase</em>, a <em>checkout</em>, a <em>merge</em>, etc.  </p>



<p>And <strong>in order to run our script</strong>, the only thing we must do is to put it inside the <strong>hooks</strong> directory, which we can find in the <strong>.git</strong> directory. </p>



<p>Moreover, when we navigate there, we will see that it <strong>already contains </strong>a bunch of useful examples: </p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="570" height="453" src="http://blog.codersee.com/wp-content/uploads/2023/11/ktlint_pre-commit_default_git_hooks.png" alt="Image is a screenshot presenting a directory where Git Hooks are put and a list of sample, already predefined hooks. This is the directory where we will put our ktlint pre-commit hook." class="wp-image-9008406" srcset="https://blog.codersee.com/wp-content/uploads/2023/11/ktlint_pre-commit_default_git_hooks.png 570w, https://blog.codersee.com/wp-content/uploads/2023/11/ktlint_pre-commit_default_git_hooks-300x238.png 300w" sizes="auto, (max-width: 570px) 100vw, 570px" /></figure>



<p>If we would like to try any of the predefined samples, then we must simply remove the <code>.sample</code> suffix from the filename. </p>



<p>And what client-side Git Hooks can we work with? Well: </p>



<ul class="wp-block-list">
<li><strong>pre-commit</strong> &#8211; run before creating a commit,</li>



<li><strong>prepare-commit-msg </strong>&#8211; run before the commit message editor is opened,</li>



<li><strong>commit-msg </strong>&#8211; triggered after the commit message is created but before the commit is finalized,</li>



<li><strong>post-commit </strong>&#8211; run after a commit is made,</li>



<li><strong>applypatch-msg</strong> &#8211; invoked during the git apply operation to edit patch messages, </li>



<li><strong>pre-applypatch / post-applypatch</strong> &#8211; run before/after applying changes from a patch, </li>



<li><strong>pre-rebase </strong>&#8211; run before starting a rebase operation,</li>



<li><strong>post-rewrite </strong>&#8211; triggered after commands that rewrite commit history,</li>



<li><strong>post-checkout </strong>&#8211; invoked after a successful git checkout operation,</li>



<li><strong>post-merge</strong> &#8211; run after a successful git merge operation,</li>



<li><strong>pre-push</strong> &#8211; run before a push to a remote repository.</li>
</ul>



<p>The <code>pre-receive</code>, <code>update</code>, <code>and</code> <code>post-receive</code> are server-side hooks, which we won&#8217;t cover here. </p>



<h2 class="wp-block-heading" id="h-import-ktlint">Import Ktlint</h2>



<p>Excellent! </p>



<p>At this point, we know what we&#8217;re dealing with today and we can start the practice part of the Ktlint pre-commit hook implementation.</p>



<p>As the first thing, let&#8217;s navigate to the <code>build.gradle.kts</code> and import the library: </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="">plugins {
    kotlin("jvm") version "1.9.10"
    application

    id("org.jlleitschuh.gradle.ktlint") version "11.6.1"
}</pre>



<p>At the moment of writing, the most recent version is <code>11.6.1</code> and you can always figure out what&#8217;s the current one <a href="https://github.com/JLLeitschuh/ktlint-gradle" target="_blank" rel="noreferrer noopener">here</a>.</p>



<p>Following, let&#8217;s sync the project. </p>



<p>When the synchronization process finishes, we should see plenty of new tasks added, among which, these we will use the most:</p>



<ul class="wp-block-list">
<li><strong>ktlintFormat</strong>&#8211; to format according to the code style all <code>SourceSets</code> Kotlin files and project Kotlin script files, </li>



<li><strong>ktlintCheck</strong>&#8211; to check all <code>SourceSets</code> and project Kotlin script files</li>
</ul>



<h2 class="wp-block-heading" id="h-add-script">Add Script</h2>



<p>With that done, let&#8217;s create the <code>scripts</code> directory and put the <code>pre-commit</code> file there: </p>



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

echo "Running git pre-commit hook"

./gradlew ktlintCheck

ktlintCheckStatus=$?

# return 1 if check fails
if [[ $ktlintCheckStatus -ne 0 ]]; then
     exit 1
else
     exit 0
fi</pre>



<p>But <strong>why don&#8217;t we put that directly to the <code>.git/hooks</code> directory?</strong> </p>



<p>Well, the problem is that by default, the <code>.git</code> directory <strong>is not tracked</strong>. It contains all the information about the repository, including the repository&#8217;s configuration, commit history, branches, and other metadata. And if the .git directory were tracked, it would create a kind of recursive loop, leading to potential issues and conflicts.</p>



<p>So, when working with Gradle and Kotlin, we will simply put our script in the <code>scripts</code> destination, and later configure Gradle to copy it to the desired folder. </p>



<p>And what is our script? </p>



<p>Well, it is a simple script, which will run the <em>ktlint check</em> command using the gradle wrapper. If it is successful, the command returns 0 and we return 0, too. In other case, we return 1 and the commit will simply fail. </p>



<p>Update build.gradle.kts</p>



<p>Following, let&#8217;s navigate to the <code>build.gradle.kts</code> file and add a new task- the <code>copyPreCommitHook</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="">tasks.register&lt;Copy>("copyPreCommitHook") {
    description = "Copy pre-commit git hook from the scripts to the .git/hooks folder."
    group = "git hooks"
    outputs.upToDateWhen { false }
    from("$rootDir/scripts/pre-commit")
    into("$rootDir/.git/hooks/")
}</pre>



<p>As we can see, this task is responsible for copying the <em>pre-commit</em> file from the <code>scripts/pre-commit</code> to the <code>.git/hooks/</code>. </p>



<p>Moreover, we make a small workaround- <code>outputs.upToDateWhen { false }</code> &#8211; so that our task will never be cached. </p>



<p>When we sync our Gradle project, we should see that our task is available from now on inside the <code>git hooks</code> group: </p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="632" height="641" src="http://blog.codersee.com/wp-content/uploads/2023/11/ktlint_pre-commit_gradle_view.png" alt="Image is a screenshot and presents the Gradle tool window in IntelliJ and our copyPreCommitHook task, which will copy the pre-commit script with ktlint check from the scripts tirectory to the desired directory- .git/hooks" class="wp-image-9008409" srcset="https://blog.codersee.com/wp-content/uploads/2023/11/ktlint_pre-commit_gradle_view.png 632w, https://blog.codersee.com/wp-content/uploads/2023/11/ktlint_pre-commit_gradle_view-296x300.png 296w" sizes="auto, (max-width: 632px) 100vw, 632px" /></figure>



<p>At this point, we can run the task and verify that the script was <strong>moved successfully</strong>. </p>



<p>But is that all? </p>



<p>Well, we could stop right here, but if we automate things, it would be good to avoid manual run of the <code>copyPreCommitHook</code> task, right? And we can easily achieve that this way: </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="">tasks.build {
    dependsOn("copyPreCommitHook")
}</pre>



<p>With this setting, we simply instruct Gradle to run <code>copyPreCommitHook</code> before the <code>build</code> task. </p>



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



<p>With all of that done, we can finally verify if our ktlint check will be run as a pre-commit hook. </p>



<p>To do so, let&#8217;s make some changes that will fail <code>ktlintCheck</code> and try to commit. In IntelliJ, we should see the following: </p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="558" height="177" src="http://blog.codersee.com/wp-content/uploads/2023/11/ktlint_pre-commit_failed_commit.png" alt="" class="wp-image-9008410" srcset="https://blog.codersee.com/wp-content/uploads/2023/11/ktlint_pre-commit_failed_commit.png 558w, https://blog.codersee.com/wp-content/uploads/2023/11/ktlint_pre-commit_failed_commit-300x95.png 300w" sizes="auto, (max-width: 558px) 100vw, 558px" /></figure>



<p>And when we check the details, we will see a meaningful message: </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="">Running git pre-commit hook
Starting a Gradle Daemon, 1 incompatible and 1 stopped Daemons could not be reused, use --status for details
> Task :loadKtlintReporters UP-TO-DATE
> Task :runKtlintCheckOverKotlinScripts UP-TO-DATE
> Task :runKtlintCheckOverTestSourceSet NO-SOURCE
> Task :ktlintTestSourceSetCheck SKIPPED
> Task :ktlintKotlinScriptCheck
> Task :runKtlintCheckOverMainSourceSet
> Task :ktlintMainSourceSetCheck FAILED
C:\Users\Piotr\src\main\kotlin\Main.kt:2:1 Unexpected indentation (10) (should be 4)
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':ktlintMainSourceSetCheck'.
> A failure occurred while executing org.jlleitschuh.gradle.ktlint.worker.ConsoleReportWorkAction
   > KtLint found code style violations. Please see the following reports:
     - C:\Users\Piotr\build\reports\ktlint\ktlintMainSourceSetCheck\ktlintMainSourceSetCheck.txt
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 7s
5 actionable tasks: 3 executed, 2 up-to-date
</pre>



<p>And this snippet proves that our <code>pre-commit</code> hook was triggered and works as expected.</p>



<p>Moreover, when we correct our code, we will see that we can commit without any message! </p>



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



<p>And that&#8217;s all for this article about implementing ktlint check as a pre-commit Git Hook. </p>



<p>I hope you enjoyed this tutorial and that this will be a great start for adding more automation to your project. If you would like to get a ready-to-go project, then please navigate to <a href="https://github.com/codersee-blog/kotlin-ktlint-git-pre-commit-hook" target="_blank" rel="noreferrer noopener">my GitHub repo here</a>.</p>



<p>Lastly, I would like to invite you to my <a href="https://codersee.com/newsletter/">free newsletter</a>, so that you will never miss any important updates from both my blog and Kotlin world!  </p>
<p>The post <a href="https://blog.codersee.com/git-hooks-kotlin-automate-ktlint-pre-commit-hook/">Ktlint with pre-commit Hook: Git Hooks in Kotlin Made Easy</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/git-hooks-kotlin-automate-ktlint-pre-commit-hook/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How To Pass Data Between Workflows in GitHub Actions?</title>
		<link>https://blog.codersee.com/how-to-pass-data-between-workflows-in-github-actions/</link>
					<comments>https://blog.codersee.com/how-to-pass-data-between-workflows-in-github-actions/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 17 Oct 2023 05:43:36 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[CICD]]></category>
		<category><![CDATA[GitHub]]></category>
		<category><![CDATA[GitHub Actions]]></category>
		<category><![CDATA[GitHub Workflows]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9007722</guid>

					<description><![CDATA[<p>In this article, I will show you how to pass data between workflows in GitHub actions- from workflow A to B (triggered by workflow_run).</p>
<p>The post <a href="https://blog.codersee.com/how-to-pass-data-between-workflows-in-github-actions/">How To Pass Data Between Workflows in GitHub Actions?</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this tutorial, we would like to see how to pass data between workflows in GitHub Actions. And to be even more specific- how can we pass the data from <code>workflow A</code> to <code>workflow B</code>, which is triggered using the <code>workflow_run</code> event. </p>



<p>But before we start, just a quick reminder that this is my second post in the series about GitHub Actions workflow. And if you would like to see the introduction, then check out my previous article and <a href="https://blog.codersee.com/how-to-create-github-actions-workflow/" target="_blank" rel="noreferrer noopener">learn how to create your first workflow</a>.</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/how-to-pass-data-between-workflows-in-github-actions/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FGewZriF7HV8%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-trigger-workflow-on-workflow-run">Trigger Workflow On <code>workflow_run</code> </h2>



<p>Let&#8217;s assume that we would like to add a new workflow to our GitHub Actions, <code>workflow B</code>, which will trigger whenever <code>workflow A</code> <strong>completes successfully</strong>. We go to the <em>events</em> documentation and figure out that the <a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_run">workflow_run</a> will be the right choice. </p>



<p>So, for the following <code>workflow A</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="">name: Workflow A

on:
  push:
    branches: [ "main" ]

jobs:
  some_job:  
    runs-on: ubuntu-latest
    steps:
      - name: Some Step 
        run: echo "Hello!"</pre>



<p>We pretty quickly came up with something, like this: </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="">name: Workflow B

on:
  workflow_run:
    workflows: ["Workflow A"]
    types:
      - completed

jobs:  
  run_on_workflow_a_success:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    runs-on: ubuntu-latest
    steps:
      - name: Run on workflow A success
        run: echo "Workflow A completes successfully"</pre>



<p>Whenever the <code>workflow A</code> completes successfully, we simply print out a message to the output. </p>



<h2 class="wp-block-heading" id="h-problem-with-passing-data">Problem With Passing Data</h2>



<p>But life is not usually that simple and we pretty quickly realize that we need to find a way to pass the data between our GitHub workflows.</p>



<p>If we learned from my previous article or spent some time with the official documentation, we finally land on the contexts page and try to find the necessary information. </p>



<p>If are lucky enough and the data we need are present, we can simply access them with one line of code. </p>



<p>Just like we did previously:</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="">github.event.workflow_run.conclusion</pre>



<p>As we can see, the above line accesses information about <em>conclusion </em>from the <code>github</code> context. </p>



<p>But what if we would like to <strong>access some custom data produced by the <code>workflow A</code> within the <code>workflow B</code>? </strong></p>



<p>Well- that&#8217;s the point where the story begins 🙂 </p>



<h2 class="wp-block-heading" id="h-pass-data-between-github-workflows-with-artifacts">Pass Data Between GitHub Workflows With Artifacts</h2>



<p>Unfortunately, at the moment when I am writing this article, there&#8217;s no such thing as a custom context or something similar where we could put the data and access them inside another (dependent) workflow. </p>



<p>Fortunately, we can bypass this limitation by producing artifacts. </p>



<p>Let&#8217;s update our <code>workflow A</code> first: </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="">name: Workflow A

on:
  push:
    branches: [ "main" ]

jobs:
  some_job:  
    runs-on: ubuntu-latest
    steps:
      - name: Some Step 
        run: echo "Hello!"

  job_producing_data:  
    runs-on: ubuntu-latest
    steps:
      - name: Some Step 
        run: |
            printf '{ 
              "prop_1": "Some value",  
              "prop_2": true
            }' >> context.json
      - uses: actions/upload-artifact@v3
        with:
          name: context.json
          path: ./</pre>



<p>With this solution, we produce a JSON file called <em>context.json</em>, which then we publish as an <a href="https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts" target="_blank" rel="noreferrer noopener">artifact</a>. </p>



<p>In simple words, GitHub <strong>artifacts</strong> are files (or collections of files), that we can produce during the workflow run and access later- from other workflows, through the API, or simply in the actions tab:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="806" height="216" src="http://blog.codersee.com/wp-content/uploads/2023/10/github_action_produced_artifact.png" alt="Image presents a screenshot from GitHub actions workflow run with produced artifact." class="wp-image-9007740" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/github_action_produced_artifact.png 806w, https://blog.codersee.com/wp-content/uploads/2023/10/github_action_produced_artifact-300x80.png 300w, https://blog.codersee.com/wp-content/uploads/2023/10/github_action_produced_artifact-768x206.png 768w" sizes="auto, (max-width: 806px) 100vw, 806px" /></figure>



<p>Following, let&#8217;s update the <code>workflow B</code> to consume this file and access its data:</p>



<p> </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="">name: Workflow B

on:
  workflow_run:
    workflows: ["Workflow A"]
    types:
      - completed

jobs:  
  run_on_workflow_a_success:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    runs-on: ubuntu-latest
    steps:
      - name: Run on workflow A success
        run: echo "Workflow A completes successfully"

  download_context_artifact:
    runs-on: ubuntu-latest
    steps:
      - name: 'Download artifact'
        uses: actions/github-script@v6
        with:
          script: |
            let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
               owner: context.repo.owner,
               repo: context.repo.repo,
               run_id: context.payload.workflow_run.id,
            });
            
            let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
              return artifact.name == "context.json"
            })[0];
            
            let download = await github.rest.actions.downloadArtifact({
               owner: context.repo.owner,
               repo: context.repo.repo,
               artifact_id: matchArtifact.id,
               archive_format: 'zip',
            });
            
            let fs = require('fs');
            fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/context.zip`, Buffer.from(download.data));
            
      - name: 'Unzip artifact'
        run: unzip context.zip

      - name: 'Return Parsed JSON'
        uses: actions/github-script@v6
        id: return-parsed-json
        with:
          script: |
            let fs = require('fs');
            let data = fs.readFileSync('./context.json');
            return JSON.parse(data);

    outputs:
      property_one: ${{fromJSON(steps.return-parsed-json.outputs.result).prop_1}}
      property_two: ${{fromJSON(steps.return-parsed-json.outputs.result).prop_2}}

  log_context_values:
    needs:
      - download_context_artifact
    runs-on: ubuntu-latest
    steps:
      - name: 'Log Context Values'
        run: |
          echo "${{ needs.download_context_artifact.outputs.property_one }}"
          echo "${{ needs.download_context_artifact.outputs.property_two }}"</pre>



<p>As we can see, we added two new jobs: <code>download_context_artifact</code> and <code>log_context_values</code>. </p>



<p>The first one is responsible for downloading the artifact (unfortunately <strong>GitHub API supports only zip files</strong> at the moment). We list all articles from the triggering flow (<code>workflow A</code>), match the one named <code>context.json</code> and download it. </p>



<p>Then, we unzip the file and parse its JSON content. </p>



<p>As the last thing in the first job, we produce two <strong>outputs</strong>.<strong> </strong>The output is nothing else than a map accessible in all <strong>downstream jobs dependent on this job</strong>. And to put it simply, in all jobs which point to this one using <strong><em>needs</em></strong>.</p>



<p>The interesting thing here is that in order to access the parsed json- <code>return JSON.parse(data)</code>&#8211; we need to use the <strong>&lt;step id&gt;.outputs.result</strong>.</p>



<p>The second job, <code>log_context_values</code>, simply prints out our values. </p>



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



<p>And that&#8217;s all for this tutorial, in which we learned how to pass data between workflows in GitHub actions. </p>



<p>I hope you enjoyed this one and if you would like to share your feedback with me or ask about anything, then let me know in the <strong>comments section</strong>.</p>
<p>The post <a href="https://blog.codersee.com/how-to-pass-data-between-workflows-in-github-actions/">How To Pass Data Between Workflows in GitHub Actions?</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-pass-data-between-workflows-in-github-actions/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			</item>
		<item>
		<title>How To Create a GitHub Actions Workflow</title>
		<link>https://blog.codersee.com/how-to-create-github-actions-workflow/</link>
					<comments>https://blog.codersee.com/how-to-create-github-actions-workflow/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 10 Oct 2023 06:00:00 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[CICD]]></category>
		<category><![CDATA[GitHub]]></category>
		<category><![CDATA[GitHub Actions]]></category>
		<category><![CDATA[GitHub Workflows]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9007701</guid>

					<description><![CDATA[<p>In this quick tutorial, I will teach you how to create a GitHub Actions workflow and customize some basic settings.</p>
<p>The post <a href="https://blog.codersee.com/how-to-create-github-actions-workflow/">How To Create a GitHub Actions Workflow</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this tutorial, I would like to show you <strong>how to set up a new GitHub Actions workflow</strong> and a few interesting things that will help you configure it in no time. </p>



<p>At the end of this tutorial, you will know precisely how to: </p>



<ul class="wp-block-list">
<li>navigate in GitHub actions and create new workflows,</li>



<li>trigger workflows manually,  </li>



<li>create a job that runs only when another job succeeds, </li>



<li>use GitHub context and access its data. </li>
</ul>



<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%;">
<p>
<a href="https://blog.codersee.com/how-to-create-github-actions-workflow/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FG4-vCV6sIkA%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-what-is-github-actions">What Is GitHub Actions? </h2>



<p><strong>GitHub Actions</strong> is nothing else than a CI/CD (continuous integration/continuous delivery) platform. </p>



<p>To put it simply, it is a platform, that allows us to automate builds, testing, deployments, and many other things related to the process in your company, or the project. </p>



<p>In practice, we define so-called <strong>workflows</strong>, which are triggered whenever some <strong>event</strong> happens in our repository. An event can be a merge to the main, a push to the remote branch, a deployment update, and plenty more. </p>



<p>Each workflow consists of <strong>jobs</strong> running inside their own machine runner and each job can have multiple <strong>steps</strong> responsible for performing an actual action, like code check, running a test, or creating a deployment.</p>



<p>Lastly, each workflow is nothing else than a YAML file placed within the <code>.github/workflows</code> directory.</p>



<h2 class="wp-block-heading" id="h-create-first-workflow">Create First Workflow</h2>



<p>With all of that said, let&#8217;s navigate to our GitHub repository and select the <em>actions</em> tab. Alternatively, you can do that with the following link: <a href="https://github.com/codersee-blog/{YOUR_REPO_NAME}/actions/new" target="_blank" rel="noreferrer noopener">https://github.com/codersee-blog/{YOUR_REPO_NAME}/actions/new</a>.</p>



<p>Nextly, let&#8217;s click the <em>configure<strong> </strong></em>button: </p>



<figure class="wp-block-image aligncenter size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="478" src="http://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_create_1-1024x478.png" alt="Image presents how to create a first GitHub Actions workflow." class="wp-image-9007702" style="width:840px;height:392px" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_create_1-1024x478.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_create_1-300x140.png 300w, https://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_create_1-768x359.png 768w, https://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_create_1.png 1182w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p> As a result, we should see the editor with the following 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=""># This is a basic workflow to help you get started with Actions

name: CI

# Controls when the workflow will run
on:
  # Triggers the workflow on push or pull request events but only for the "main" branch
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v3

      # Runs a single command using the runners shell
      - name: Run a one-line script
        run: echo Hello, world!

      # Runs a set of commands using the runners shell
      - name: Run a multi-line script
        run: |
          echo Add other actions to build,
          echo test, and deploy your project.</pre>



<p>So, let&#8217;s clean it up a bit to avoid distraction: </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="">name: CI

on:
  push:
    branches: [ "main" ]
  
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Run a one-line script
        run: echo Hello, world!

      - name: Run a multi-line script
        run: |
          echo Add other actions to build,
          echo test, and deploy your project.</pre>



<p>To sum up, we&#8217;ve defined a new GitHub Actions workflow, named <em>CI</em>, which will be triggered whenever we push a new commit to the <em>main </em>branch.</p>



<p>Our workflow consists of one job, <em>build</em>, which will be run on the <em>Ubuntu</em> runner. This job will invoke has two steps, which will be run one after another: </p>



<ul class="wp-block-list">
<li>firstly, it will print out a one-line script, </li>



<li>lastly, it will invoke the multiline script. </li>
</ul>



<p>Quite easy, isn&#8217;t it? </p>



<p>So with all of that done, let&#8217;s change the name of the file to <code>ci.yml</code> and let&#8217;s commit our changes to the <em>main</em> branch: </p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="597" height="618" src="http://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_commit_to_main.png" alt="Image presents a popup for merging to the main branch" class="wp-image-9007703" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_commit_to_main.png 597w, https://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_commit_to_main-290x300.png 290w" sizes="auto, (max-width: 597px) 100vw, 597px" /></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="h-how-to-view-workflow-run">How To View Workflow Run? </h2>



<p>With all of that done, let&#8217;s see how we can see the result of our triggered workflow. </p>



<p>To do so, let&#8217;s navigate to the following URL: <a href="https://github.com/codersee-blog/{your-repo-name}/actions/workflows/ci.yml" target="_blank" rel="noreferrer noopener">https://github.com/codersee-blog/{your-repo-name}/actions/workflows/ci.yml</a>.</p>



<p>On this page, we should see that our workflow run has completed successfully:</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="177" src="http://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_run_view-1024x177.png" alt="" class="wp-image-9007704" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_run_view-1024x177.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_run_view-300x52.png 300w, https://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_run_view-768x133.png 768w, https://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_run_view-1536x266.png 1536w, https://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_run_view.png 1572w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<p>As the next step, let&#8217;s click on the <code>build</code> job and let&#8217;s see the output:</p>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="487" height="685" src="http://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_run_build_output.png" alt="Image presents the output ot the workflow" class="wp-image-9007705" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_run_build_output.png 487w, https://blog.codersee.com/wp-content/uploads/2023/10/github_workflow_run_build_output-213x300.png 213w" sizes="auto, (max-width: 487px) 100vw, 487px" /></figure>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Excellent! </p>



<p>Both our scripts run successfully and we can see the expected output. </p>



<h2 class="wp-block-heading" id="h-how-to-trigger-flow-manually">How To Trigger Flow Manually? </h2>



<p>At this point, you might be wondering if (and if yes, then how) we can manually trigger the flow. </p>



<p>Well, unfortunately, to do so, we must edit our YAML file a little: </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="">name: CI

on:
  push:
    branches: [ "main" ]
  workflow_dispatch:  
  
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Run a one-line script
        run: echo Hello, world!

      - name: Run a multi-line script
        run: |
          echo Add other actions to build,
          echo test, and deploy your project.</pre>



<p>As we can see, the only thing we need to add is the <code>workflow_dispatch</code>. </p>



<p>This event, allows us to trigger our workflow using the <em>GitHub API</em>, <em>GitHub CLI</em>, or <em>GitHub browser interface</em>:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="162" src="http://blog.codersee.com/wp-content/uploads/2023/10/manually_trigger_workflow-1024x162.png" alt="Image shows how to manually trigger the GitHub Actions workflow thanks to the workflow_dispatch event trigger" class="wp-image-9007706" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/manually_trigger_workflow-1024x162.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/10/manually_trigger_workflow-300x47.png 300w, https://blog.codersee.com/wp-content/uploads/2023/10/manually_trigger_workflow-768x121.png 768w, https://blog.codersee.com/wp-content/uploads/2023/10/manually_trigger_workflow-1536x242.png 1536w, https://blog.codersee.com/wp-content/uploads/2023/10/manually_trigger_workflow.png 1540w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="h-how-to-disable-the-workflow">How To Disable The Workflow? </h2>



<p>But can we disable the workflow without deleting the file? </p>



<p>Yes, we can. </p>



<p>We can do that simply by using the <em>Disable workflow</em> button from the workflow runs page: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="160" src="http://blog.codersee.com/wp-content/uploads/2023/10/disable_workflow_button-1024x160.png" alt="Image shows how to disable GitHub Actions workflow " class="wp-image-9007707" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/disable_workflow_button-1024x160.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/10/disable_workflow_button-300x47.png 300w, https://blog.codersee.com/wp-content/uploads/2023/10/disable_workflow_button-768x120.png 768w, https://blog.codersee.com/wp-content/uploads/2023/10/disable_workflow_button-1536x240.png 1536w, https://blog.codersee.com/wp-content/uploads/2023/10/disable_workflow_button.png 1547w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="h-contexts-and-debugging">Contexts And Debugging </h2>



<p>Wonderful. At this point, we already know the basics of workflows and navigation in GitHub Actions. </p>



<p>As the next step, let&#8217;s learn a bit more about <strong>contexts</strong>. </p>



<p><strong>Contexts</strong> allow us to access information about the workflow runs, variables, runner environments, jobs, and steps. If you are looking for commit sha, PR author, branch name, or maybe GitHub access token, then that&#8217;s the place you should check out.</p>



<p>For the full list of contexts with descriptions please take a look at the <a href="https://docs.github.com/en/actions/learn-github-actions/contexts" target="_blank" rel="noreferrer noopener">official documentation</a>.</p>



<p>But from my end, I wanted to show you a useful tip if you would like to debug anything:</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="">name: CI

on:
  push:
    branches: [ "main" ]
  workflow_dispatch:  
  
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Run a one-line script
        run: echo Hello, world!

      - name: Run a multi-line script
        run: |
          echo Add other actions to build,
          echo test, and deploy your project.
  
      - name: Who initialized the flow? 
        run: echo "${{ github.actor }} initialized the flow."

      - name: Debug GitHub context
        run: |
            echo " ${{ toJSON(github) }}"</pre>



<p>As we can see, the third step &#8211; Who initialized the flow?- prints out the &#8220;actor&#8221; (or simply the account) that initialized the flow. </p>



<p>The fourth step- Debug GitHub context- prints out <code>github</code> context to the output. </p>



<p>The <code>toJson</code> function returns a pretty-print JSON representation of our value.</p>



<p>As a result, we should see the following:</p>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="707" height="630" src="http://blog.codersee.com/wp-content/uploads/2023/10/debug_workflow_result.png" alt="Image shows the result of the Debug GitHub context job." class="wp-image-9007708" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/debug_workflow_result.png 707w, https://blog.codersee.com/wp-content/uploads/2023/10/debug_workflow_result-300x267.png 300w" sizes="auto, (max-width: 707px) 100vw, 707px" /></figure>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Whatsoever, when debugging we don&#8217;t need to worry about exposing our secrets. </p>



<p>GitHub hides them by default. </p>



<h2 class="wp-block-heading" id="h-run-job-after-another-job-finishes-successfully">Run Job After Another Job Finishes Successfully</h2>



<p>Excellent! At this point, we learned quite a lot about the GitHub Actions workflows. </p>



<p>We know how to declare them, how to debug contexts, and how to navigate inside the GitHub world. </p>



<p>As the last thing for our first meeting, let&#8217;s learn how we can make one job dependent on the other.</p>



<p>To do so, let&#8217;s edit our YAML file once again:</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="">name: CI

on:
  push:
    branches: [ "main" ]
  workflow_dispatch:  
  
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Run a one-line script
        run: echo Hello, world!

      - name: Run a multi-line script
        run: |
          echo Add other actions to build,
          echo test, and deploy your project.
  
  second_job:        
    needs: build
    runs-on: ubuntu-latest
    
    steps:
      - name: I run only when build succeeds
        run: echo Hi!</pre>



<p>As we can see, the only thing we need here is <code>needs: build</code>. </p>



<p>To be more specific, the <code>needs</code> instruction allows us to specify jobs that must be completed <strong>successfully</strong> before our job starts. </p>



<p>Of course, we can define multiple jobs using the array syntax: <code>needs: [job1, job2]</code>. </p>



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



<p>And that&#8217;s all for this quick tutorial on how to create GitHub Actions workflows. </p>



<p>If you are interested in learning more about this topic, then I highly encourage you to check out <a href="https://docs.github.com/en/actions/quickstart" target="_blank" rel="noreferrer noopener nofollow">their documentation</a>. I&#8217;m pretty sure you will find there anything you need. </p>



<p>But if that&#8217;s not the case, then <strong>let me know in the comments section or by using the <a href="https://codersee.com/contact/" target="_blank" rel="noreferrer noopener">contact</a> form 🙂 </strong></p>
<p>The post <a href="https://blog.codersee.com/how-to-create-github-actions-workflow/">How To Create a GitHub Actions Workflow</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-create-github-actions-workflow/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How Can Codersee Help You?</title>
		<link>https://blog.codersee.com/how-can-codersee-help-you/</link>
					<comments>https://blog.codersee.com/how-can-codersee-help-you/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Fri, 02 Dec 2022 06:44:19 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[survey]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=5504017</guid>

					<description><![CDATA[<p>What can we do together to ship a better content and what you can do to get a free ebook with Spring Framework interview questions.</p>
<p>The post <a href="https://blog.codersee.com/how-can-codersee-help-you/">How Can Codersee Help You?</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Hello 😀</p>
<p>If you follow my blog a bit longer, then you probably realized that you can find here a bunch of various how-to articles and tutorials, mostly related to Kotlin and frameworks, or libraries that we can use with it. And although I am happy to write about any topic, at the end of the day, <strong>it is you, dear reader, who consumes this content</strong>.</p>
<p><img loading="lazy" decoding="async" class="alignright wp-image-5504018" src="http://blog.codersee.com/wp-content/uploads/2022/12/50-Spring-Framework-Interview-Questions-212x300.png" alt="Image is a cover of codersee ebook withh 50 Spring Framework Interview questions." width="300" height="424" srcset="https://blog.codersee.com/wp-content/uploads/2022/12/50-Spring-Framework-Interview-Questions-212x300.png 212w, https://blog.codersee.com/wp-content/uploads/2022/12/50-Spring-Framework-Interview-Questions-724x1024.png 724w, https://blog.codersee.com/wp-content/uploads/2022/12/50-Spring-Framework-Interview-Questions-768x1086.png 768w, https://blog.codersee.com/wp-content/uploads/2022/12/50-Spring-Framework-Interview-Questions-1086x1536.png 1086w, https://blog.codersee.com/wp-content/uploads/2022/12/50-Spring-Framework-Interview-Questions-1448x2048.png 1448w, https://blog.codersee.com/wp-content/uploads/2022/12/50-Spring-Framework-Interview-Questions.png 1587w" sizes="auto, (max-width: 300px) 100vw, 300px" /></p>
<h2></h2>
<h2></h2>
<h2>Ebook For A Survey</h2>
<p>So, without any further ado, I&#8217;d like to ask you to take part in the really short survey (2-3 minutes), which will help me to provide you with better-suited content so that <strong>you will become a better programmer</strong>, and<strong> my work will actually make sense</strong>.</p>
<p>It&#8217;s a classic win-win situation, and furthermore, in return for your precious time, you&#8217;ll receive an ebook containing <em>50 Spring Framework interview questions</em>.</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>Please find the survey below (you&#8217;ll receive the link to the ebook immediately after you submit the answers):</p>
<p style="text-align: center;"><iframe loading="lazy" src="https://docs.google.com/forms/d/e/1FAIpQLSd6dq4QRp9SfHguAmLijNM5YRI6nEf2nG5Fj79L_UwhCQuhpw/viewform?embedded=true" width="640" height="2387" frameborder="0" marginwidth="0" marginheight="0">Loading…</iframe></p>
<p>&nbsp;</p>
<p>Once again, thank you for your time, and see you in the next posts friend.</p>
<p>The post <a href="https://blog.codersee.com/how-can-codersee-help-you/">How Can Codersee Help You?</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-can-codersee-help-you/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-04-20 14:02:27 by W3 Total Cache
-->