Deploying Ktor Web Service on AWS EC2

Hussain Mukadam
6 min readJun 19, 2021

--

This article basically talks about how do we go about deploying a Ktor web service on an AWS EC2 instance running Ubuntu / any linux distro.

At the time of writing this article, I am using Ktor version 1.5 to build the project and the EC2 instance we are going to be deploying on will be a T2 micro instance.

In order to learn more about creating a new Ktor web service you can follow this series it’s extremely well written and at the time of writing this, it’s still a WIP or there are tons of good articles along with the official docs that help you get started with Ktor on the Backend.

I’ll assume that your Ktor web app is up and running locally, and we will start with launching our instance and getting started with the deployment process.

Let’s start by adding a security group to the instance if you don’t already have kept it open for all IP address, this is done so that we are able to test our web service from the browser / mobile app.

Login to your AWS Dashboard -> EC2 -> Security Groups section and edit the inbound rules.

We will be adding two new rules here -

First Rule -

Type — Custom TCP
Protocol — TCP
Port Range — 8080 — we are using 8080 port number in this example this has to be the port number you are running our web service on.
Source — 0.0.0.0/0

Second Rule -

Type — Custom TCP
Protocol — TCP
Port Range — 8080
Source — ::/0

Now let’s create a Jar file for our web service, we will be using the Gradle Shadow Plugin to create this, the reason for that is this plugin allows us to create a Fat Jar which is shadowed (obfuscated), a very good explanation for the same is here.

If we don’t create a fat jar, and start our deployment process when running it on the server it will throw an error — no main manifest attribute, in projectname.jar

While you will see the Manifest file in the jar file if you extract it and check under the Manifest -> META-INF directory, you will notice that it doesn’t have the mainClass attribute, that points to the Class in our project that has the main function, in our case it will be the ApplicationKt.class, we need to make some changes in our build.gradle file in order for it to be added in our Manifest and that we don’t get the above error.

First, we need to make sure that in our build.gradle.kts file, we have the correct Class mentioned for mainClassName -

Next, we want Gradle to add the mainClass attribute in the Manifest file when building the jar file, for this we will create a task -

Now let’s add the Gradle Shadow Plugin in our build.gradle file

After this just run the Gradle build, this will create a Jar file in our project directory — builds -> libs -> projectname-version-all.jar

We are going to need to copy this file onto our web server, for this we will use the following command, make sure you to keep your `pem` file handy for this next step, since it’s needed to access your instance via the terminal.

scp -i “instance.pem” IdeaProjects/starter-project/build/libs/projectname-0.0.1.jar ubuntu@ec2–0.0.0.0.us-east-2.compute.amazonaws.com:/home/ubuntu

The above command is Secure Copy to securely transfer files from your machine to the server, further more you are giving it the directory of the jar file you want to transfer to your server along with the path on the server where you’d want to store it, you can also create a workspace directory on your server for storing this file, and give it that path instead.

Now, let’s SSH into our server for the next step -

ssh -i “instance.pem” ubuntu@ec2–0.0.0.0.us-east-2.compute.amazonaws.com

We need to check what’s the version of Java installed on our server and make sure it aligns with the version we are running our web service on, we will install it if needed.

For this we need to run the following command —
java -version

This command will either return the version of Java installed on your server or an error that says command not recognised and a suggestion to install it, it will also include the commands that you can run to install the same, make sure you have the correct version installed, in our case it is `Java 1.8`

If all goes well, after installation you will see the below response on hitting the java -version command -

openjdk version “1.8.0_292”
OpenJDK Runtime Environment (build 1.8.0_292–8u292-b10–0ubuntu1~20.04-b10)
OpenJDK 64-Bit Server VM (build 25.292-b10, mixed mode)

Next, let’s run our web app on the server, make sure you’re in the same directory where you had copied the jar file in the previous steps, run the below command to see if it’s running on the port 8080.

Also make sure there are no other processes running on the port number we want our web service to run on -

kill -9 $(lsof -t -i:8080)This will kill all the processes that are running on the port number 8080.

Now, let’s run the jar file with the following command -

java -jar projectname-0.0.1-all.jar

If all goes well, the above command should return the following response —

2021–06–16 19:31:42.194 [main] INFO Application — Autoreload is disabled because the development mode is off.
2021–06–16 19:31:42.711 [main] INFO Application — Responding at http://0.0.0.0:8080

We can now hit our Public v4 DNS with the port number our web service is running on and see the response in the browser -

http://ec2-0-0-0-0.us-east-2.compute.amazonaws.com:8080/

But you will notice that as soon as we exit the shell, our web service stops working, in order for this to work even after existing the shell we need to create a service on our server instance that runs the web service even after exiting the shell or in case our server reboots.

Let’s create a web service, by running the below command -

sudo vim /etc/systemd/system/webapp.service

Press i -> to start inserting
Paste the following the instructions to our service -

[Unit]
Description=Ktor REST Service
[Service]
User=ubuntu
# Change this to your workspace
WorkingDirectory=/home/ubuntu/
# Path to executable
ExecStart=usr/bin/java -jar /home/ubuntu/projectname-0.0.1-all.jar
SuccessExitStatus=143
TimeoutStopSec=10
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target

Press :wq to save the above script.

ExecStart is the instruction that tells our system to run the command mentioned in it, here we can also mention the path to our bash script which runs the web service instead, which is much easier to maintain.

After this we need to run the following commands to tell our system to run the service -

sudo systemctl daemon-reload 
sudo systemctl enable webapp.service

After enabling the service it will return a symlink to our webapp.service file

sudo systemctl start webapp
sudo systemctl status webapp

This should return the response that we had received earlier, when running the service -

Autoreload is disabled because the development mode is off.
Application — Responding at http://0.0.0.0:8080

If you face any issues in this, make sure the paths mentioned in the webapp.service file under ExecStart instruction are valid, and accessible.

If all goes well you will be able to hit the Public V4 DNS URL / IP address / Domain with 8080 port number on the browser to see the response you were expecting.

You can stop / restart the service by using the following commands -

sudo systemctl stop webapp
sudo systemctl restart webapp

To get the service logs you can use the following commands, in case you want to further debug if there’s an error -

sudo journalctl -f -n 1000 -u webapp

I’m still very new to this, hence if I have failed to mention a way of doing this that was quite obvious, please ping me on Twitter so that I can improve. Thanks!

Thanks to @trianton, who’s article helped me completely understand the deployment process.

Know more about me from the links below —
Github
Stack Overflow

--

--