<?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>Exposed Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/tag/exposed/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>Kotlin &#38; Backend Tutorials - Learn Through Practice.</description>
	<lastBuildDate>Wed, 16 Apr 2025 04:49:52 +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>Exposed Archives - Codersee blog- Kotlin on the backend</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Easily Deploy Your Ktor Server with Docker</title>
		<link>https://blog.codersee.com/easily-deploy-ktor-server-with-docker/</link>
					<comments>https://blog.codersee.com/easily-deploy-ktor-server-with-docker/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 17 Jan 2023 07:40:26 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[Exposed]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=5504250</guid>

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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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

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

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



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



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



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



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



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



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



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



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



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



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



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

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

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

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

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

  var email by AppUsers.email
}
</pre>



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



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



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



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



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



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



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



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



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



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



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



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

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

# Response:
Hello World!
</pre>



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



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



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



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



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



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



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



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



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



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



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



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



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



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

# Verification: 
docker images

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



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



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



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



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

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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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

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



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



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



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



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



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

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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



<p>Lastly, if you would like to learn a bit more about Ktor, then check out my other articles about <a href="https://blog.codersee.com/a-guide-to-ktor-with-mongodb/">Ktor with MongoDB</a>, or <a href="https://blog.codersee.com/rest-api-ktor-ktorm-postgresql/">Ktor with PostgreSQL</a>.</p>
<p>The post <a href="https://blog.codersee.com/easily-deploy-ktor-server-with-docker/">Easily Deploy Your Ktor Server with Docker</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/easily-deploy-ktor-server-with-docker/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</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-02-21 20:41:48 by W3 Total Cache
-->