Full guide to deploy your Play Framework app on Ubuntu 20.04

Full guide to deploy your Play Framework app on Ubuntu 20.04

Introduction

So, you've developed your Play Framework application, and you now want to host it. In this blog post we will guide you through the general steps to safely host your application on an Ubuntu 20.02 server.

If you want to follow along with this guide, but don't have a web app, don't worry, we have a sample project on GitHub that you can use.

Steps

The web app we work with in this guide is a simple Play Framework 2.8 application in Java. It uses a PostgreSQL database and serves a sample page. In order to deploy such application, we will go through the following steps:

  1. Optional: running Ubuntu 20.02 on your local machine
  2. Setting up SSH authentication
  3. Installing and setting up Git
  4. Installing Java
  5. Installing sbt
  6. Setting up PostgreSQL
  7. Running your application
  8. Setting up a web server
  9. Installing Let's Encrypt for SSL connections (HTTPS)
  10. Notes and further improvements

1. Optional: running Ubuntu 20.02 on your local machine

If you don't already have a server on which you want to host your application, or if you just want to follow along without having a remote server, you can set up a local Ubuntu server. One of the easiest ways to do so is by downloading a VirtualBox or VMware image and spin it up. OSBoxes.org provides images that you can download (the "Info" contains the user credentials).

For this guide, we'll be using an Ubuntu 20.02 VirtualBox image and pretend as if it's our server. If you set up your local server correctly, you can sign in with the root account:

VirtualBox running Ubuntu 20.02 server

However, in a real-life situation, you would be SSH'ing to your server. So, let's also do that. In order to SSH to a machine, you need its IP-address. But since we are using VirtualBox, we need to forward a port. First, sign in as your root user via VirtualBox itself and enter the following commands:

1[osboxes@osboxes] $ sudo apt-get update
2[osboxes@osboxes] $ sudo apt-get upgrade
3[osboxes@osboxes] $ sudo apt-get install openssh-server

If that has succeeded, you can shut down your machine and go to your VirtualBox machine settings. Go to Network > Adapter 1 > Advanced > Port forwarding and add a new record with Host IP: 127.0.0.1, Host Port: 2222 and Guest Port: 22.

VirtualBox port forwarding

Save it and start your machine. You can now minimize your VirtualBox windows and pretend as if it's running in some data center. Open up a new terminal window or some other SSH client program that you use. In this guide we use a bash terminal. You can now SSH to your machine:

1$ ssh osboxes@127.0.0.1 -p 2222
1The authenticity of host '[127.0.0.1]:2222 ([127.0.0.1]:2222)' can't be established.
2ECDSA key fingerprint is SHA256:gTCDYqbsUNGHwrFRV1XuiFa+PpQ+nE2uoD0QqMGm5WE.
3Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
4Warning: Permanently added '[127.0.0.1]:2222' (ECDSA) to the list of known hosts.
5osboxes@127.0.0.1's password:

In real life, if you have an VPS for example, you would be using the IP-address of the VPS instead of 127.0.0.1, user root and default port 22.

2. Setting up SSH authentication

Usually, when you get a VPS for example, your hosting provider gives you the IP-address and root user credentials. This allows you to SSH to your server, like so (with default port 22):

1$ ssh root@203.0.113.0
2root@203.0.113.0's password:

Or, when you already pointed your domain name to the IP-address:

1$ ssh root@mywebsite.com
2root@mywebsite.com's password:

However, it is not recommended to SSH to your server like this. For three reasons: you don't want to sign in as the root user, you don't want to sign in with a username and password and you don't want to use the default port 22. Why? In short: for security purposes. You don't want bots to keep trying to sign in with (root) user credentials (brute force attacks). And if a bot or hacker succeeds, you definitely don't want them to have root access on your server. That's why we have to do the following:

  1. Set up SSH certificate authentication
  2. Disable password authentication
  3. Change the authentication port
  4. Create a new non-root user and do the same

2.1 Set up SSH certificate authentication

If you aren't familiar with SSH certificate authentication, it works as follows: On your computer you generate a public- and private key. You then transfer the public key over to your server. Then, after adjusting some settings, when you SSH to your server, you send your private key along, instead of a password. The server validates if your private key matches with its public key. If it's correct, you get signed in. In this guide, we'll also be changing the default port that gets used. We will change it from 22 to 900 (you can also pick another available port). Quick side note: if you followed along with chapter 1, your current port is 2222 instead of 22.

Let's start by signing in to your server, if you haven't already:

1$ ssh root@203.0.113.0
2root@203.0.113.0's password:

Make sure your software is up to date by executing the following commands (you can skip this if you already followed along with the first chapter on setting up your virtual machine). The first command, sudo apt-get update checks what packages need to be updated, and sudo apt-get upgrade actually updates them.

1[root@203.0.113.0] $ sudo apt-get update
2[root@203.0.113.0] $ sudo apt-get upgrade

We also need directory to place our public key in. We create a .ssh folder in our home directory:

1[root@203.0.113.0] $ mkdir ~/.ssh/

Now switch back to your local computer. We need to generate the public and private key. Make sure you have a folder in which you want to place your private key. On MacOS/Unix machines this can be your ~/.ssh folder. cd over to that folder and execute the ssh-keygen command. Choose a name for your certificates, in this example we entered " root_myserver". If you want, you can also enter a password.

1$ cd ~/.ssh/
2$ ssh-keygen -t rsa -b 4096
1Generating public/private rsa key pair.
2Enter file in which to save the key (/Users/johnny/.ssh/id_rsa): root_myserver
3Enter passphrase (empty for no passphrase):
4Enter same passphrase again:
1$ chmod 600 root_myserver
2$ chmod 600 root_myserver.pub

If done correctly, it generates two files: root_myserver and root_myserver.pub. The first one is the private key, the latter the public key. What we need to do now, is to transfer the public key (root_myserver.pub) over to the server in the already created .ssh folder. To accomplish this, we use the bash scp command (if you use a virtual machine on port 2222, add -P 2222):

1$ scp root_myserver.pub root@203.0.113.0:~/.ssh/

Sign back into your server and verify that the public key has been transferred:

1[root@203.0.113.0] $ cd .ssh/
2[root@203.0.113.0] $ ls
3[root@203.0.113.0] $ root_myserver.pub

We now need to make sure that it can actually be used for SSH certificate authentication. Let's add the public key to the authorized keys (make sure your working directory is still the ~/.ssh folder):

1[root@203.0.113.0] $ cat root_myserver.pub >> authorized_keys

Edit the ssh configuration file (we use Vim) and uncomment the line with AuthorizedKeysFile. Restart the ssh service after saving:

1[root@203.0.113.0] $ sudo vim /etc/ssh/sshd_config
1...
2AuthorizedKeysFile      .ssh/authorized_keys .ssh/authorized_keys2
3...
1[root@203.0.113.0] $ sudo service ssh restart

You can now sign out from the server, and sign back in with your private key (add -p 2222 if you use port 2222 instead of 22):

1$ ssh -i ~/.ssh/root_myserver root@203.0.113.0

Only problem is, you can still also sign in with just a username and password. Let's change that.

2.2 Disable password authentication

To disable password authentication, ssh to your server and edit the ssh configuration file. Here you want to uncomment the line with PasswordAuthentication and change the value to no. Restart the ssh service after saving changes"

1[root@203.0.113.0] $ sudo vim /etc/ssh/sshd_config
1...
2PasswordAuthentication yes
3...
1[root@203.0.113.0] $ sudo service ssh restart

Whenever you now try to ssh to your server with username and password authentication you'll get a Permission denied error.

2.3 Change the authentication port

Next thing we want to do is change our port. By default, it is set to 22 (using your virtual machine it's set to 2222 in chapter 1). You can use any available port that you want (make sure is actually is available and not reserved for something else), in this guide we'll change it to 900. Ssh to your server and once again, edit the ssh configuration file. Uncomment the line with Port, change the value to 900 and restart the ssh service.

1[root@203.0.113.0] $ sudo vim /etc/ssh/sshd_config
1...
2Port 900
3...
1[root@203.0.113.0] $ sudo service ssh restart

So now, when you ssh to your server, you need to provide the port and private key. Notice how to don't need the root user its password anymore. But do be careful with your private key and don't just share it!

1$ ssh -p 900 -i ~/.ssh/root_myserver root@203.0.113.0

Note: If you followed along with chapter 1, and set up VirtualBox, you'll notice that it doesn't work. That's because you need to add another port forwarding, for example from host 9999 to guest 900. If you do this, you need to ssh to your server with -p 9999.

2.4 Create a new non-root user and do the same

Because we don't want to run our application as the root user, we are going to create a new user: my_user. Creating a new user goes as follows in Ubuntu:

1[root@203.0.113.0] $ sudo adduser myuser
1Adding user `myuser' ...
2Adding new group `myuser' (1001) ...
3Adding new user `myuser' (1001) with group `myuser' ...
4Creating home directory `/home/myuser' ...
5Copying files from `/etc/skel' ...
6New password:
7Retype new password:
8passwd: password updated successfully
9Changing the user information for myuser
10Enter the new value, or press ENTER for the default
11    Full Name []: My User
12    Room Number []:
13    Work Phone []:
14    Home Phone []:
15    Other []:
16Is the information correct? [Y/n] Y
1[root@203.0.113.0] $ sudo usermod -aG sudo myuser

When to try to ssh to your server with myuser, you'll see that you get a "Permission denied" error. That's because myuser doesn't have the ssh certificate authentication set up. So, we need to generate another public and private key: myuser_myserver and myuser_myserver.pub and scp it to the ~/.ssh/ folder of the root user. Then ssh to you server as the root user and move the public key to the home directory of myuser and add it to its authorized keys:

1[root@203.0.113.0] $ cd ~/.ssh/
2[root@203.0.113.0] $ sudo mkdir /home/myuser/.ssh
3[root@203.0.113.0] $ sudo mv myuser_myserver.pub /home/myuser/.ssh/
4[root@203.0.113.0] $ su myuser
1[myuser@203.0.113.0] $ cd ~/.ssh/
2[myuser@203.0.113.0] $ sudo cat myuser_myserver.pub >> authorized_keys

You can now directly ssh to your server from your newly created user myuser:

1$ ssh -p 900 -i ~/.ssh/myuser_myserver myuser@203.0.113.0

Thus, concludes the server authentication part of this guide. We can finally start with the setup of our actual application!

3. Installing and setting up Git

In a professional environment you use a VCS (version control) to host your code. For example, using Git and Github to host your code. You probably also do this for the application that you want to host. In this guide we will use this Play Framework application: sample-play-framework-project. Feel free to clone this repository if you want to follow along.

Sign back into your server and install git:

1[myuser@203.0.113.0] $ sudo apt-get install git

You should now set up your authentication method. We recommend you use SSH authentication to pull your code. If you use Bitbucket, check out this link: Set up and SSH key. If you use GitHub, you can use this link: Connecting to GitHub with SSH . In this guide we'll use GitHub:

1[myuser@203.0.113.0] $  cd ~/.ssh/
2[myuser@203.0.113.0] $ ssh-keygen -t rsa -b 4096 -C "your_github_mail@mail.com"
1Generating public/private rsa key pair.
2Enter file in which to save the key (/home/myuser/.ssh/id_rsa): key_github
3Enter passphrase (empty for no passphrase):
4Enter same passphrase again:
5Your identification has been saved in key_github
6Your public key has been saved in key_github.pub
7The key fingerprint is:
8...
1[myuser@203.0.113.0] $ eval "$(ssh-agent -s)"
2Agent pid 1150
3[myuser@203.0.113.0] $ ssh-add ~/.ssh/key_github
4Identity added: /home/myuser/.ssh/key_github (your_github_mail@mail.com)

Copy the contents of key_github.pub. Go you the Github website and visit your settings page. There you'll see a tab named "SSH and GPG keys", there you add your copied key. Now you can clone your repository. We clone our sample application in the home directory:

1[myuser@203.0.113.0] $ cd ~
2[myuser@203.0.113.0] $ git clone git@github.com:Peggir/sample-play-framework-app.git

Now we have our code from the server, and we can pull it whenever there are some changes (git pull).

4. Installing Java

In this chapter, and the next two, we need to install a couple of things that we need in order to actually run the application on our server. Since our sample web app is a java application, we need to install java. We need to install the jre and the jkd. After installing, we can verify it by checking the version:

1[myuser@203.0.113.0] $ sudo apt install default-jre
2[myuser@203.0.113.0] $ sudo apt install default-jdk
3[myuser@203.0.113.0] $ javac -version
4javac 11.0.8

5. Installing sbt

Play Framework applications use sbt. They use sbt to build projects and thus we need to install it. When you install it, always make sure you install the correct version. In this guide we install it as follows (version 1.3.5):

1[myuser@203.0.113.0] $ curl -L -o sbt.deb http://dl.bintray.com/sbt/debian/sbt-1.3.5.deb
2[myuser@203.0.113.0] $ sudo dpkg -i sbt.deb
3[myuser@203.0.113.0] $ sudo apt-get update
4[myuser@203.0.113.0] $ sudo apt-get install sbt

6. Setting up PostgreSQL

Our sample application uses a PostgreSQL database, so we need to install it, add the database and a user (with password) . If your application uses a different database, then install that one. In this guide we won't dive in securing your remote database connection, but it is important to know that you can disable remote access or set up certificate authentication. Installing and setting up our database (remember the database name, user and password for the next step):

1[myuser@203.0.113.0] $ sudo apt-get install postgresql postgresql-contrib
2[myuser@203.0.113.0] $ sudo -u postgres psql
1postgres=$ CREATE DATABASE "sample-app-db";
2postgres=$ CREATE USER "sample-app-db-user" WITH ENCRYPTED PASSWORD 'my_server_password';
3postgres=$ GRANT ALL PRIVILEGES ON DATABASE "sample-app-db" TO "sample-app-db-user";
4postgres=$ \q
1[myuser@203.0.113.0] $ /etc/init.d/postgresql restart

7. Running your application

In order to run the application on our server we need do three things:

  1. Make a log directory
  2. Add a production configuration file
  3. Create a deploy script

7.1 Make a log directory

We need to create the directory that we are going to output our logs to. From our logback.xml configuration we can see that it logs to /var/log/sample-play-framework-app/. Let's create that directory:

1[myuser@203.0.113.0] $ sudo mkdir /var/log/sample-play-framework-app

7.2 Add a production configuration file

In our sample application we have an application.conf file. Here we specify our application variables. The current one contains values for our development environment, but these don't all work on our production environment. So, we are going to create a new configuration file in which we are going to specify different values for some properties:

1[myuser@203.0.113.0] $ cd ~
2[myuser@203.0.113.0] $ sudo vim prod.conf
1include "application"
2    
3application.env = "PROD"
4play.http.secret.key = "c9147165-e782-46f8-8dfa-a7156b4c8082"
5db.default.url = "jdbc:postgresql://localhost:5432/sample-app-db"
6db.default.username = "sample-app-db-user"
7db.default.password = "my_server_password"
1[myuser@203.0.113.0] $ sudo chmod 600 prod.conf

On the first line, include "application", we make sure that we keep our default values. On the other lines we specify our production environment specific properties and values. Our secret is a random GUID that we generated.

7.3 Create a deploy script

We are now going to create a script that will pull all changes and start the application for us. You can repeatedly execute this script whenever you have new changes that you want to deploy. Okay, lets create it:

1[myuser@203.0.113.0] $ cd ~
2[myuser@203.0.113.0] $ sudo vim deploy-script.sh

Add the following contents to the script (contents will be explained below):

1#!/bin/bash
2
3cd ~/sample-play-framework-app/
4git pull
5sudo rm -rf target/universal
6sudo sbt dist
7cd target/universal
8sudo unzip $(ls -t sample-play-framework-app-*.zip | head -1)
9sudo rm $(ls -t sample-play-framework-app-*.zip | head -1)
10cd $(ls -td sample-play-framework-app-* | head -1)
11
12sudo kill $(cat ~/application-instance-1.pid)
13sleep 20
14sudo bin/sample-play-framework-app -Dconfig.file=/home/myuser/prod.conf -Dhttp.port=9998 -Dpidfile.path=/home/myuser/application-instance-1.pid > /dev/null 2>&1 &
15
16sleep 60
17
18sudo kill $(cat ~/application-instance-2.pid)
19sleep 20
20sudo bin/sample-play-framework-app -Dconfig.file=/home/myuser/prod.conf -Dhttp.port=9999 -Dpidfile.path=/home/myuser/application-instance-2.pid > /dev/null 2>&1 &

Explanation of the script: In short, it pulls the latest version of the application from git. It then uses sbt dist to build a binary version of the application. This creates a .zip file, that we then unzip and delete. In the unzipped folder there is a script in the bin directory. We run that script with our arguments. In our arguments list we include our prod.conf file, a http port, and a pid file. The pid file will be generated when the application starts, it contains the process ID, that can be used to stop the application. As you can see, we actually run two instances of the application. One on port 9998 and one on port 9999. In chapter 8 you'll see why we do this (spoiler alert: for redeploying the application without down-time).

After saving the file, we need to make it executable. But we also need to install unzip:

1[myuser@203.0.113.0] $ sudo chmod +x deploy-script.sh
2[myuser@203.0.113.0] $ sudo apt-get install zip unzip

If we are going to run our script the first time, we will get an error. That's because the script kills the running instances of the application, but there are none the first time. So, you can ignore the error messages about the kill command. You can run your script as follows (may take some minutes):

1[myuser@203.0.113.0] $ ./deploy-script.sh

The applications are running in the background after the script is done, but how do we verify that? We can't access it in our browser yet, since we don't expose it (we'll do that in the next chapter). So, for now you can use curl to check if they are running:

1[myuser@203.0.113.0] $ curl localhost:9998
2[myuser@203.0.113.0] $ curl localhost:9999

Curl outputs the html of the index page if they are running. If you don't get the html output, something might have gone wrong in one of the previous steps. Try to solve that first before continuing.

8. Setting up a web server

At this point we have two running application instances on our server. One on port 9999 and the other on port 9998. What we need to do now is exposing our application on port 80 and let it point to one of our running instances. Port 80 is the default http port: http://example.com/ automatically goes through port 80. That way our web application becomes accessible to the outer world, in our case, by going to http://203.0.113.0/. Don't worry about https, we will handle that in the next chapter.

In this guide we'll use an apache server, but you can change this as you wish. If you want, you can use nginx. Let's install and set up our apache server to expose our application:

1[myuser@203.0.113.0] $ sudo apt-get install apache2

If the installation succeeded, an apache server is running. Verify this by accessing your IP-address or domain in a browser (side note: if you use VirtualBox, make sure to add another port forwarding from 8080 to guest port 80, then you can access your server in your browser via: http://127.0.0.1:8080/):

Apache2 Ubuntu default page

We now need to change the apache configuration to show our web application. We are going to use a load balancer so apache can pick which app instance to serve to the user (remember: we have two running instances, on port 9999 and 9998) . Let's change our configuration:

1[myuser@203.0.113.0] $ cd /etc/apache2/sites-available/
2[myuser@203.0.113.0] $ sudo vim 000-default.conf

Change the apache configuration to:

1<VirtualHost *:80>
2    ServerName www.example.com
3    
4    <Location /balancer-manager>
5        SetHandler balancer-manager
6        Order Deny,Allow
7        Deny from all
8    </Location>
9    
10    <Proxy balancer://mycluster>
11        BalancerMember http://localhost:9999
12        BalancerMember http://localhost:9998 status=+H
13        </Proxy>
14        
15    <Proxy *>
16        Order Allow,Deny
17        Allow From All
18    </Proxy>
19    
20    ProxyPreserveHost On
21    ProxyPass /balancer-manager !
22    ProxyPass / balancer://mycluster/
23    ProxyPassReverse / balancer://mycluster/
24</VirtualHost>

After saving, install the following dependencies and restart apache:

1[myuser@203.0.113.0] $ sudo a2enmod proxy
2[myuser@203.0.113.0] $ sudo a2enmod proxy_balancer
3[myuser@203.0.113.0] $ sudo a2enmod proxy_http
4[myuser@203.0.113.0] $ sudo a2enmod lbmethod_byrequests
5[myuser@203.0.113.0] $ sudo service apache2 restart

If you now refresh your browser, you should see your application. If you used our sample application, test it out and check if everything works! When you submit a form, it should be inserted into the database, if you go to the "Overview" page, you see all forms from the database.

Running sample application

9. Installing Let's Encrypt for SSL connections (HTTPS)

Note: In this chapter we will be setting up a HTTPS connection with Let's Encrypt. If you follow along this chapter using VirtualBox or some other virtual machine, you won't be able to actually generate the certificate and install the HTTPS connection, because you won't be authorized.

We finally got our application up and running. Only problem is, it's using HTTP, while we really want to use HTTPS. We need a certificate and have all HTTP pages automatically redirect to HTTPS. For this, we are going to use Let's Encrypt. Depending on your web server, the installation might differ. Check out their documentation. For this guide we'll install it as follows (taken from the documentation of Let's Encrypt/Certbot):

1[myuser@203.0.113.0] $ sudo apt install snapd
2[myuser@203.0.113.0] $ sudo snap install core; sudo snap refresh core
3[myuser@203.0.113.0] $ sudo snap install --classic certbot
4[myuser@203.0.113.0] $ sudo ln -s /snap/bin/certbot /usr/bin/certbot
5[myuser@203.0.113.0] $ sudo certbot --apache

If you followed all steps along successfully, you'll now have a Let's Encrypt certificate and your apache configuration will automatically be updated to actually use it. You can now visit your web application using HTTPS instead of HTTP.

10. Notes and further improvements

You've reached the finish! At least, for now that is. We've successfully installed a server and ran a Play Framework application on it, with a database, logging and (re)deploy script (that redeploys without down-time!). What else can you do now? There are multiple things you can do to further improve your server and hosting. These are:

  • Visualizing and analyzing your application logs: It's important to always analyze your logs. Thanks to your logs you are able to detect errors that users encounter. One way to do that, is using the Elastic Stack.
  • Securing your database: You should always work actively on securing your database. For this guide is was out of its scope, but you should really look into it.
  • Create automated back-ups of logs and the database: In addition to previous two points, you should make regular back-ups of your logs and database. Again, be careful how/where you store your backed-up data. You don't want to lose all data when something happens to your server.
  • Set up automated continuous deployment: Whenever you add changes to your application and push it to your version control, you don't really want to manually sign into your server and execute the deployment script. That's where CI-tools (continuous integration) chime in. With a CI-tool you can automate the process of building, testing and deploying your application. Two widely used CI tools are Travis-CI and Jenkins.