How to Publish a Ktor Docker Image to Container Registry Using the Ktor Plugin

In this post, we’ll walk through the configuration of the Ktor plugin for deploying a Ktor Docker image to Container Registry via a generated Gradle task.  We’ll also explore several other Gradle tasks that make working with a containerized version of our Ktor service easier.

This post is part of a series on managing Ktor service deployments using Gradle:

Ktor is a framework from JetBrains enabling developers to build client and/or server applications using Kotlin.  For organizations wanting to use Kotlin for server-side development, Ktor is an appealing choice of framework as it’s built from the ground up to leverage Kotlin language features such as coroutines. 

In many organizations today, especially those undergoing “cloud transformation” through adoption of serverless technologies, containers are a popular choice for packaging, sharing, and deploying applications.  Those containers can be used with tools like Docker to simplify the development, testing, and coordination of services.

Containers are a key aspect of many serverless deployment workflows within Google Cloud Platform.  Servless solutions like Cloud Run rely on containers to respond quickly to increases in server load by deploying new containers, when needed, to handle the incoming traffic, and then spin down those containers when traffic subsides.

If using Ktor for modern serverless development, it’s likely you’ll need to create a container image for your Ktor application and be able to deploy that image to some external repository where it may then be deployed and start receiving traffic.

It’s possible to do this in an entirely manual fashion; defining our own Dockerfile, building the container locally, and then pushing it to some external registry such as Google’s Container Registry.

However, the Ktor plugin provides mechanisms for simplifying this process in a way that makes it easy for those without extensive Docker, container, or GCP knowledge to leverage.

If you need to publish your Ktor Docker image to Artifact Registry, check out this related post.

Prerequisites

Since we’ll be deploying our Ktor service container to Container Registry, that comes with a new Google Cloud Platform (GCP) prerequisites.

  1. We need to have access to a GCP organization
    1. How to setup a GCP organization?
    2. You’ll eventually need to set up a billing account for your organization.  To try out GCP for learning purposes, there are often ways to find free, or discounted, billing credits
      1. Trial period with $300 free credits for new users
      2. GCP credits for faculty and students
      3. Google Cloud Innovators Plugs program
  2. We need to have a project set up within that organization
    1. How to create and manage GCP projects?
  3. Container Registry must be enabled within that project
    1. How to enable Container Registry for a GCP project?

How to configure the Ktor plugin to build a Docker image?

To configure a Docker image, and publishing tasks, for our Ktor project, we’ll use the Ktor plugin dsl in our project’s Gradle buildscript.  This will leverage the jib Gradle plugin under the hood to configure and build images for your project.

// build.gradle.kts
ktor {
   docker {
      // container configuration goes here
   }
}

To start, we’ll specify a Java Runtime Environment (JRE) version to use when constructing the container image.

ktor {
   docker {
       // set a JRE version that will be used to build the container
       jreVersion.set(io.ktor.plugin.features.JreVersion.JRE_17)
   }
}

Next, we’ll configure port mappings for the image.

ktor {
   docker {
       // set a JRE version that will be used to build the container
       jreVersion.set(io.ktor.plugin.features.JreVersion.JRE_17)

       // setup port mapping for the container/service
       portMappings.set(listOf(
           io.ktor.plugin.features.DockerPortMapping(
               outsideDocker = 8080,
               insideDocker = 8080,
               io.ktor.plugin.features.DockerPortMappingProtocol.TCP
           )
       ))
   }
}

And finally, we’ll configure the external registry used when publishing the container.  In our example, we’ll configure it to use Container Registry.

ktor {
   docker {
       // set a JRE version that will be used to build the container
       jreVersion.set(io.ktor.plugin.features.JreVersion.JRE_17)

       // setup port mapping for the container/service
       portMappings.set(listOf(
           io.ktor.plugin.features.DockerPortMapping(
               outsideDocker = 8080,
               insideDocker = 8080,
               io.ktor.plugin.features.DockerPortMappingProtocol.TCP
           )
       ))

       // configure how the container should be deployed to Container Registry
       externalRegistry.set(
           DockerImageRegistry.googleContainerRegistry(
               projectName = provider { "<your gcp project name>" },
               appName = provider { "<desired repository name>" },
               username = provider { "" },
               password = provider { "" }
           )
       )
   }
}

There are several key pieces of information needed for this Container Registry configuration:

  1. projectName should be the name of your existing GCP project in which Container Registry has been enabled and where you want to upload the built container
  2. appName will be used to name the repository within Container Registry.  If the repository doesn’t exist, it will be created when the container image is published.
    1. A reasonable default here could be the name of your Ktor project. You could pull this from the Gradle project using project.name
  3. username and password we leave blank here.  In the next section we’ll discuss how authentication happens when deploying the container image

Once this configuration is applied to the project, we can build our Docker image using the buildImage Gradle task.  Check out this FAQ from the jib Gradle plugin repo for more details on what an equivalent Dockerfile would look like for the generated image.

How to deploy a Ktor Docker image to Container Registry?

With the Ktor Docker configuration applied to the project, we can publish a Docker image for our project using the publishImage Gradle task.

This task requires some form of authentication against the GCP project we are attempting to access.  Specifically, we need access to the following resource url based on our project configuration: gcr.io/<project name>/<app name>

As the Ktor Docker configuration uses the jib plugin under the hood, we can review documentation from jib on how to authenticate with different container registries.

For Container Registry, there straightforward options for authenticating the requests to publish our image:

  1. Setup Google Cloud Application Default credentials on your machine using an account that has access to necessary GCP project
  2. Setup gcloud as a credential helper for any Docker-supported registries

To setup auth via application default credentials, run the following terminal command before running the publishImage task: gcloud auth application-default login

To setup auth via gcloud Docker credential helper, run the following terminal commands before running the publishImage task:

  • gcloud auth configure-docker gcr.io
  • gcloud auth login

Once one of these (or another) authentication methods are applied, running the publishImage task should successfully publish the built image to Container Registry.

How to deploy a Ktor Docker image to local Docker service?

Using the Ktor Docker configuration, we can also streamline publishing of our project’s Docker image to a local Docker.

  1. Start Docker on your development machine
  2. Run the publishImageToLocalRegistry Gradle task

How to run the Ktor service container using generated Gradle tasks?

Using the Ktor Docker configuration, we can build an image and start running it with the local Docker daemon by running the runDocker Gradle task.

Conclusion

The Ktor plugin’s Docker configuration provides a streamlined workflow for developers working with Ktor services via Docker.

By configuring the plugin to publish images to Google Cloud’s Container Registry, developers can publish.  This task can be used by all developers, and by CI/CD workflows to provide a consistent deployment workflow.