Baeldung Pro – Kotlin – NPI EA (cat = Baeldung on Kotlin)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

1. Overview

We might want to create a Spring Boot application with Kotlin and deploy it as a container in a Kubernetes cluster using Docker.

In this tutorial, we’ll look at deploying a simple Kotlin application to Kubernetes (K8s) using Docker and Docker Hub.

2. Requirements

Before we proceed with deploying to Kubernetes, let’s ensure that we have a few prerequisites installed and basic knowledge of the technologies involved:

  1. Docker:
    • Ensure that Docker is installed and running on our machine. Docker will allow us to package the Kotlin application into a container for easy deployment.
  2. Kubernetes:
    • K8s needs to be installed locally or with access to a cloud-based Kubernetes cluster. We’ll use Minikube to install a single-node K8s cluster for this tutorial.
    • We need to know Kubernetes concepts like services, pods, and deployments.
  3. kubectl:
    • We’ll need to install the kubectl command-line tool to interact with Kubernetes clusters.
  4. Docker Hub or Private Container Registry:
    • We need a Docker Hub or private container registry account where we can push the Docker image of our Kotlin application.

3. Setting up a Kotlin Application

Let’s create a simple Spring Boot application we’ll containerize later:

@SpringBootApplication
class Application

fun main(args: Array<String>) {
    runApplication<Application>(*args)
}

Let’s also create a simple endpoint:

@RestController
@RequestMapping("/api")
class HelloController {
    @GetMapping("/hello")
    fun hello(): ResponseEntity<String> {
        return ResponseEntity.ok("Hello From Docker and Kubernetes!")
    }
}

We’ll use the endpoint to check that our container is running in the K8s cluster.

4. Containerizing the Kotlin Application With Docker

Once we have our main Kotlin application, we need to create an entry point to start it. We’ll build an executable with a docker-k8s Maven profile with the shade plugin or other plugins available to create an executable JAR. Therefore, we need to update the pom.xml file:

<profiles>
    <profile>
        <id>docker-k8s</id>
        <build>
            <finalName>kotlin-app</finalName>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-shade-plugin</artifactId>
                    <version>3.2.4</version>
                    <executions>
                        <execution>
                            <phase>package</phase>
                            <goals>
                                <goal>shade</goal>
                            </goals>
                            <configuration>
                                <transformers>
                                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                        <mainClass>com.baeldung.dockerkubernetes.ApplicationKt</mainClass>
                                    </transformer>
                                </transformers>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Now, let’s create our JAR file:

$ mvn clean package -Pdocker-k8s

The Maven command will create the kotlin-app.jar file in the /target directory.

Next, let’s create a Docker image using the Dockerfile:

# Use an official OpenJDK runtime as a parent image
FROM openjdk:17-jdk-slim

# Set the working directory in the container
WORKDIR /app

# Copy the JAR file into the container
COPY target/kotlin-app.jar /app/kotlin-app.jar

# Run the application
CMD ["java", "-jar", "/app/kotlin-app.jar"]

Finally, let’s build our image:

$ docker build -t kotlin-app .

If we run the docker images command, we should see the new image listed:

REPOSITORY                TAG             IMAGE ID       CREATED         SIZE
kotlin-app                latest          99630fc39439   3 days ago      447MB

5. Deploying the Docker Container to Kubernetes

Let’s push the Docker image to Docker Hub and deploy it to K8s.

5.1. Create a Docker Image

To push our image to the Docker Hub, we need to log in:

$ docker login

We authenticate by providing our Docker Hub credential or with a browser authentication through a provided code.

Then, we need to provide a tag for the image. We’ll set a USERNAME environment variable to reuse throughout the process. For example, let’s say we have authenticated with the baeldung user:

$ export USERNAME=baeldung

Finally, let’s tag the image and push it to the repository:

$ docker tag kotlin-app:latest ${USERNAME}/kotlin-app:latest \
&& docker push ${USERNAME}/kotlin-app:latest

This command will pull all the Docker image layers and push the tagged image to the container repository. Notably, every container registry has its domain — in this case, it’s docker.io/, and we should use it as a prefix to identify our image.

5.2. Deploy to K8s

We’ll use Minikube to deploy our Dockerized Spring Boot app to K8s locally. After installing Minikube, let’s start a local K8s cluster simply by running:

$ minikube start

The Minikube single-node K8s cluster will run as a Docker container, as we can see by running the docker ps -a command:

CONTAINER ID   NAMES      STATUS        IMAGE                                  PORTS
a7d43b6ac800   minikube   Up 3 minutes  gcr.io/k8s-minikube/kicbase:v0.0.45    127.0.0.1:32768->22/tcp ...

We need to create a K8s deployment to run our containerized Kotlin app inside pods. Let’s define a deployment.yaml file:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kotlin-app-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kotlin-app
  template:
    metadata:
      labels:
        app: kotlin-app
    spec:
      containers:
        - name: kotlin-app-container
          image: DOCKER_IMAGE
          ports:
            - containerPort: 8080

Kubernetes doesn’t allow dynamic variable substitution. Therefore, we can manually replace DOCKER_IMAGE with the image we created earlier and apply the deployment:

$ kubectl apply -f deployment.yaml

Otherwise, we can export an environment variable:

$ export DOCKER_IMAGE="docker.io/${USERNAME}/kotlin-app:latest"

Then, we pipe cat with the sed command (to replace the variable name) and kubectl apply -f to create the deployment:

$ cat deployment.yaml | sed "s|DOCKER_IMAGE|$DOCKER_IMAGE|g" | kubectl apply -f -

Either way, let’s now check that we’ve deployed our pod:

$ kubectl get pods -l app=kotlin-app

We should also see our pod up and running:

NAME                                     READY   STATUS    RESTARTS   AGE
kotlin-app-deployment-664d69c57c-gszj6   1/1     Running   0          3m20s

Finally, we need to expose our deployment through a service. Let’s define a service.yaml file:

apiVersion: v1
kind: Service
metadata:
  name: kotlin-app
spec:
  type: NodePort
  ports:
    - port: 8080
      targetPort: 8080
      nodePort: 30000
  selector:
    app: kotlin-app

Let’s create the service:

$ kubectl apply -f service.yaml

We also want to check the service creation:

$ kubectl get service kotlin-app

We should now see the service up and running:

NAME         TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kotlin-app   NodePort   10.96.229.29   <none>        8080:30000/TCP   3m40s

As a Minikube alternative, we might consider another lightweight K8s distribution or deploy directly to the Cloud, for instance, with EKS or AKS.

6. Access the Kotlin Application in the Kubernetes Cluster

Minikube runs Kubernetes clusters locally, but services are not automatically accessible through a public IP as they would be in a cloud environment. To access a service, Minikube will perform a port-forwarding:

$ minikube service kotlin-app

The output will show the exposed URL:

|-----------|------------|-------------|---------------------------|
| NAMESPACE |    NAME    | TARGET PORT |            URL            |
|-----------|------------|-------------|---------------------------|
| default   | kotlin-app |        8080 | http://192.168.49.2:30000 |
|-----------|------------|-------------|---------------------------|
  Opening service default/kotlin-app in default browser...

As we can see, a web browser opens the URL for the service by forwarding the service’s port from the Minikube VM to our local machine. This allows us to access the service as if it were running on a public IP but locally.

If we now curl or go to http://192.168.49.2:30000/api/hello URL in the browser, we’ll access the Kotlin app running inside the pod in the K8s cluster and get the response:

Hello From Docker and Kubernetes!

7. Conclusion

In this article, we saw how to deploy a Spring Boot Kotlin app in a K8s cluster. To achieve this, we first containerized a Spring Book app and pushed a Docker image to the Docker Hub.

We then used Minikube to start a local K8s cluster, created a deployment, and exposed a service through port-forwarding. Finally, we successfully got a response from the endpoint we exposed in the Kotlin app.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.