KMPizza
Kotlin Multiplatform + Pizza = ❤️

Step 30: Store data with ElephantSQL and containerize Ktor backend application with Docker

As Heroku cancelled its free plans and sharing knowledge has suddenly become expensive, I’ve decided to move KMPizza Ktor backend somewhere else. To do so I also had to learn how to containerize my backend app with Docker.

Another change was database related. Even though many PaS services offer a Postgres plugin, it usually comes with extra expenses, so I decided to host the PostgresSQL database separately. I went with ElephantSQL, as they have very attractive offers for small projects. And they have a free plan for such tiny ones as KMPizza. But the best thing about ElephantSQl is that you don’t have to enter your credit card data if you only need a free plan.

elephantsql plans

Connecting to ElephantSQL

To start with, go to ElephantSQL.
Sign up, create a new team and then click on Create a New Instance.

create a new instance button

Then go on and create a Tiny Turtle Database instance.

select tiny turtle plan

It takes less than a minute.
Voilà, you have the connection data:

connection data

Here you see the most important pieces: user DB_USER, default database name DB_NAME, password DB_PASS and the URL which you’ll partly use as your connection string.

And you can see the new instance in the list of all your projects:

database list

Now let’s get back to our app and see what we need to change in the code.

First, in application.conf extend the database configuration with the following properties:

 
user = "postgres"
user = ${?DB_USER}
password = "password"
password = ${?DB_PASS}
db_name = "recipecollection"
db_name = ${?DB_NAME}

These properties will be set from the environment variables later. In the past we used to set the whole JDBC_DATABASE_URL string from the environment, but I’ve dicided it’d be nicer to split it into several parts and concatenate into a connection string after.

Hence, adjust the init {} in your LocalSourceImpl.kt to use the new connection data:

 
init {
   val config = application.environment.config.config("database")
   val dbUser = config.property("user").getString() [1]
   val dbPassword = config.property("password").getString() [2]
   val dbName = config.property("db_name").getString() [3]
   val instanceConnection = config.property("instance_connection").getString()
   val url = "jdbc:postgresql://snuffleupagus.db.elephantsql.com/$dbName" [4]



   dispatcher = newFixedThreadPoolContext(poolSize, "database-pool")
   val hikariConfig = HikariConfig().apply {
       jdbcUrl = url
       maximumPoolSize = poolSize
       driverClassName = driver
       username = dbUser [5]
       password = dbPassword [6]
       validate()
   }



}

[1], [2], [3] Extract the database user, password and database name from the environment variables
[4] Use the new database url to connect
[5], [6] Use the username and password to authenticate

Now proceed to the next step before deploying the backend: packaging it into a docker container.

Using Docker

Follow the instructions to install desktop Docker application on your computer. It’s rather uncomplicated if you carefully follow all the steps and check that your computer meets all the system requirements.

Now return to KMPizza. Create a new file in the KMPizza project root called Dockerfile:

dockerfile

Copy the following configuration to the Dockerfile:

 
FROM openjdk:11
EXPOSE 8080:8080
RUN mkdir /app
COPY ./backend/build/libs/Backend.jar /app/Backend.jar
ENTRYPOINT ["java","-jar","/app/Backend.jar"]

This file basically tells Docker to build an image from our shadow jar file in ./backend/build/libs/Backend.jar and expose port 8080 for the access.

Now we need to create a new shadow jar package for the backend, so that we can containerize it. Just like in Step 4 we simply need to rebuild the project and look for a new jar file in /backend/build/libs/.

However, currently I’m running into an IR lowering error trying to rebuild the project. But here’s a temporary workaround for it.

As the error comes from Koin usage in CommonModule.kt we can replace

 
fun initKoin(appDeclaration: KoinAppDeclaration) = startKoin {
    appDeclaration()
    modules(
        apiModule,
        repositoryModule,
        viewModelModule,
        platformModule,
        coreModule
    )

with

 
fun initKoin(appDeclaration: KoinAppDeclaration) =  {}

Now make the project again and the new Backend.jar will appear in the build/libs folder.
May be there’s a better solution to this problem - reach out to me if you have an idea.
Don’t forget to reverse your changes to CommonModule.kt afterwards!

Now it’s time to containerize this Backend.jar according to the Dockerfile.
Open your command line in the KMPizza project root.
Run the following command:

docker build -t kmpizza .

This tell the Docker to create and image using the Dockerfile from the current directory. The -t parameter tells it to tag the image “kmpizza”. You should be able to see your new image in the Docker Desktop:

docker desktop image

Finally, we can set the local environment variables from the commands line just like before:

export bucketName=kmpizza
export secretKey=your_AWS_secret_key
export accessKey=your_AWS_access_key
export region=eu-central-1
export DB_NAME=your_ElephantSQL_db_name
export DB_USER=your_ElephantSQL_user
export DB_PASS=your_ElephantSQL_password

Now tell Docker to use the variables from local environment when you test run the container locally:

docker container run --env accessKey --env secretKey --env region --env bucketName --env DB_NAME --env DB_USER --env DB_PASS -p8080:8080 kmpizza

[1] Here with –env parameter you pass every secret to Docker from the environment variables.
[2] With -p8080:8080 you specify the ports you’ve exposed in the Dockerfile.
[3] And kmpizza stands for the application tag you used when building an image.

As usually, go to localhost:8080/pizza and verify it. Alternatively, with Postman you can try sending a recipe to your new database with the /recipes endpoint and see that it’s still working.

Our Ktor server runs locally now and is connected to ElephantSQL cloud database.
In the next step we’ll see how to deploy it somewhere else 😉