When I first tried to deploy an application, I had no clue how it works. I had to do it on my own and read lots of blog posts, tutorials and Stack Overflow answers to get it. I collected information all along the way to be able to create my own step-by-step tutorial.
Since then, I reused my own tutorial to deploy a Spring Boot + Angular and Ionic application and it worked like a charm.
What we will do in this tutorial
This tutorial aims at deploying a web application with a Java / Spring Boot REST API and an Angular frontend on an Ubuntu server version 16.04 (Xenial Xerus) or 18.04 (Bionic Beaver). I’m using a PostgreSQL database and also explain how I installed it. I did not use containerization.
I used Java 14, Postgres 11.
Deploy a Spring Boot – Angular application on Ubuntu server
I’m not going through the creation of Spring Boot, Java, Angular… projects or PostgreSQL database, if you’re here I guess you have it all ready to deploy.
I developed on a Windows machine but I’ll deploy on Ubuntu server. There was a little issue a had to solve to deploy on Ubuntu 16.04, which didn’t appear on version 18.04, I included that step in the tutorial.
If you’re working on a Windows machine, make sure you have a SSH client installed (PuTTy). To remote connect through SSH, you’ll use the Windows command line
putty.exe user@IP
where “user” is the user name and IP is the host IP address.
You might use PuTTy’s graphical interface, but using command line allows you to create a little script (.bat) so you just click on the script and it opens the connection window where you just have to input the password. I find it very handy.
When connected to the remote Ubuntu machine, you want to ensure the user that will deploy has the sudo privileges. To be honest, when I deploy a private project and I’m working alone, I do everything with the root account, but that’s not recommended. For production grade applications, you should of course have a technical user dedicated to this kind of actions.
From admin (root) account (type su):
visudo
It opens a file, just add following line at bottom:
username ALL=(ALL) NOPASSWD:ALL
where username is the name of the user that needs sudo access.
Now, when you connect as “username”, you can type sudo -i
or su
to act with full privilege (beware of security issues!) or prefix commands with sudo
.
Install Java (Oracle Java JDK Installer) on the Ubuntu server
Here started trouble, because I wasn’t able to install one of the licensed Java versions (8 or 11), neither from Oracle or OpenJDK sources. Here are the commands to install any unlicensed Java version from the web. I chose Java 14.
sudo apt update && sudo apt upgrade
sudo add-apt-repository ppa:linuxuprising/java
sudo apt update
sudo apt install oracle-java14-installer
Check that it worked with:
java --version
javac -version
Install PostgreSQL on Ubuntu server
Get dependency for PostgreSQL:
sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main" >> /etc/apt/sources.list.d/pgdg.list'
where “bionic” is the Ubuntu version. Check yours with lsb_release -c if needed and adapt above line.
Import the repository signing key:
wget --verbose -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add –
Install (here installing PostgreSQL version 11):
apt-get update
apt-get install postgresql-11
Alternately, this shell script will automate the repository setup. The script is included in the postgresql-common package in Debian and Ubuntu, so you can also run it straight from there:
sudo apt install postgresql-common
sudo sh /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
Now, try to connect to PostgresSQL. Default user’s name is “postgres”:
su - postgres
Exit Postgres and set the environment variables (change version if necessary):
export PATH=$PATH:/usr/lib/postgresql/11/bin
export PATH=$PATH:/usr/lib/postgresql/11/main
export PATH=$PATH:/var/lib/postgresql/11/main
export PATH=$PATH:/var/run/postgresql
Check your installation and variables:
postgres --version
You might want to make PostgreSQL start at server boot:
update-rc.d postgresql enable
systemctl enable postgresql
I’m not going into details on how to allow remote connections from other services here because I installed the database and the application on the same server, which was good enough for my use case.
You can always check the status of your database with the following commands. The first one gives the pg server status (connections, ports…), the second gives information on the service (if you created a service, as shown few lines above).
pg_isready
/etc/init.d/postgresql status
You probably want to make an automatic back-up of your database!
su – postgres
(postgres@server:~$)
mkdir -p ~/postgres/backups
find -name “backups”
crontab -e
The structure of the instructions is explained in the file that opens. Here’s an example of how to make a back-up of your database every Sunday at midnight. Beware, the server has to be up and running at that time, otherwise the back-up will be skipped!
0 0 * * 0 pg_dump -U postgres my_db_name > ~/postgres/backups/my_db_name.bak
Deploy Spring Boot back-end on Ubuntu
You can get your repository from a public GitHub repo like so:
wget --no-check-certificate https://github.com/User/Repository/archive/master(branchname).tar.gz
Or download the tar from your GitLab repo and copy it to the remote Ubuntu server. I tend to like GitLab a lot more because it gives you that choice to download a .tar or .tar.gz directly! Beware that destination file must have write permission from “other” group (chmod o+w remoteFolder).
scp full/path/myLocalFile remoteuser@remoteserver:/remoteFolder/
where “myLocalFile” is the tar.gz downloaded from GitLab and “remoteFolder” is the destination folder on the server.
Extract source code from the archive:
tar -xf myRepo.tar.gz
Installing and configuring maven for deployment
I worked with maven as a dependency manager.
Install maven:
apt-get install maven
You’ll need to make some changes in your pom.xml file, the one that you unzipped on your server (you might make the changes before copying to the Ubuntu server, but what’s more fun than using vim honestly?). This allows to make an executable jar.
<groupId> ... </groupId>
<artifactId> ... </artifactId>
<version>0.0.1</version>
<name> ... </name>
<packaging>jar</packaging>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<executable>true</executable>
<classifier>spring-boot</classifier>
<mainClass>
[groupId.artifactId].Application
</mainClass>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
Place yourself in the file where the pom.xml is and build the jar:
mvn clean package
Then try running the application:
mvn spring-boot:run
Make a service from the jar
Now that you have an executable jar, you want to make a service so it can run, start at boot, stopped and restarted…
Check that you file is executable, else change it with chmod 755 yourApp.jar
I created a script myapp.service in file /etc/systemd/system with following content:
[Unit]
Description=My Spring Boot application
After=syslog.target
[Service]
ExecStart=/path/to/your-app.jar
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
When saved, try:
service myapp start
service myapp status
If everything ok, you’d see the Spring Boot start logs, or an error log.
If you want the app to start at server boot, type:
systemctl enable myapp
You can now check your endpoints with the curl command, you’ll see an html/JSON representation or a JSON message saying that you have to be connected if you put some secured access on your application.
Deploy Angular front-end project on Ubuntu
You’ll have to get your repository just as you did for Spring Boot. Be sure you changed your URIs from http://localhost:8080/blabla to /blabla, because your Angular front-end won’t be communicating directly with your Spring Boot back-end, we will use a web server with a proxy.
When I first tried to deploy, I was convinced I just had to install everything just as it is on my dev machine and it would work, but spoiler alert, it didn’t.
You need a web server to render the frontend. I used Apache2 to serve my application.
Installations to be able to run Angular:
apt install nodejs
apt install npm
curl -sL https://deb.nodesource.com/setup_12.x | bash -
apt-get install -y nodejs
node --version
npm install -g @angular/cli
Move to the folder you previously transferred and unzipped, where [src] is, and build:
ng build --prod
If it works, a [dist] folder is created. If you’re working with Ionic, please note the folder will be called [www] instead of [dist].
You might encounter problem “@angular-devkit/build-angular”, which I solved with following lines:
npm install --save-dev @angular-devkit/build-angular
npm update
Install and use Apache2 as web server for your Angular application
Install Apache2 and allow access from anywhere (if your application is meant to be public). We then configure the Ubuntu firewall (ufw). If the app list
doesn’t show Apache as allowed application (from outside), type line 3. Type line 4 to check that firewall is up and running. Type line 5 if it wasn’t, and check again.
sudo apt install apache2
sudo ufw app list
(sudo ufw allow 'Apache')
(sudo ufw status)
(sudo ufw enable)
Check that Apache server is “active running”:
sudo systemctl status apache2
If everything seems ok, check from another computer that you can see the default Apache2 page by just typing the IP address in a browser.
Set up a virtual host:
sudo chmod -R 755 /var/www/domain_name
And configure the proxy from front to back:
sudo nano /etc/apache2/sites-available/domain_name.conf
Use 80 for http or 443 for https:
<VirtualHost *:80>
ServerAdmin webmaster@localhost
ServerName your_domain
ServerAlias your_domain
Alias /mypath /var/www/your_domain
DocumentRoot /var/www/your_domain
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
ProxyPass /api/ http://localhost:8080/api/
ProxyPassReverse /api/ http://localhost:8080/api/
ProxyRequests Off
</VirtualHost>
Enable your new domain and disable the default one:
a2ensite your_domain.conf
a2dissite 000-default.conf
Check config:
sudo apache2ctl configtest
You might encounter error “Invalid command ProxyPass”. If so, enable proxy with:
a2enmod proxy_http
systemctl restart apache2
Now, copy the Angular [dist] folder CONTENT to the newly created domain file. The index file must be in /domain_name.
cp -a ./angular/dist/[folder]/. /var/www/domain_name
When I build, I get a folder with my app name in the dist folder. That’s the content of that folder I need to copy into my new “domain_name” folder.
Finally, configure routing:
sudo nano /var/www/domain_name/.htaccess
Just copy/paste:
RewriteEngine on
#Don’t rewrite files or directories
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-d
RewriteRule ^- [L]
#Rewrite everything else to index.html to allow html5 state links
RewriteRule ^ /index.html [L]
Well, at this point, you should be able to access your application from any computer by typing its IP in a browser, and your frontend should be able to communicate with your bac-end, which is connected to your PostgreSQL database.
I tried this – it didn’t work, I also don’t understand how it is even supposed to work since Angular index.html is download on my browser on my local machine, following which it sends a request to backend with a url /blabla but without prepending the ip of EC2 instance such call can never work. I don’t undestand how setting up proxies can help if the call is made from my machine that has no idea about Apache2 installed on ec2
You access to the website served via Apache server from your machine, so it’s aware of the server address. You’re not making a direct call from your browser, your browser contacts the web server, then the web server calls to back-end service. This, of course, if both are on the same server machine. I’ve used this deployment template many times. Only thing I can think of if it doesn’t work is with the URIs on Angular or Java side.