Hussain Mukadam
Posted on June 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 using8080
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
-
application {
mainClassName = "com.starter-project.ApplicationKt"
}
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 -
tasks.withType<Jar> {
manifest {
attributes["Main-Class"] = application.mainClassName
}
}
Now let's add the Gradle Shadow Plugin in our build.gradle
file
plugins {
id("com.github.johnrengelman.shadow") version "6.1.0"
}
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
Posted on June 19, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.