Running Spring Boot Applications with systemd

This article explores all the steps we have to take in order to run our Spring Boot application as a systemd service on a Linux host. Making our application as a whole a lot more reliable, due to the additional resilience to failures of our setup.

Project Setup

Traditionally web applications would be packaged as war archives (Web Application Archives) and deployed onto a Web Application Server like Tomcat or any other.

Spring Boot applications can be packaged either as war or jar files. Packaging our application as a jar file embeds the web server already inside the jar, allowing us to run the jar as a standalone application.

Let's start by configuring our application to be packaged as a standalone jar file, making it easily executable.

To create a ‘fully executable’ jar with Maven, we use the following plugin configuration in our pom.xml:


<packaging>jar</packaging>

<build>
    <finalName>${project.artifactId}</finalName>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <executable>true</executable>
            </configuration>
        </plugin>
    </plugins>
</build>

Worth noting here is the following:

  • The packaging directive used here must be set to jar for the obvious reason that we would like to package the application as a jar.
  • The finalName directive switches the naming scheme of our jar file from the default "my-application-0.0.1-SNAPSHOT.jar" to be the artifact id only, "my-application.jar". This makes it a lot easier for our CI / CD to work with our packaged application.
  • The build plugin spring-boot-maven-plugin with the configuration executable set to true instructs the plugin to first and foremost build a jar, but also to include stuff like the web application server into that package.

To build the executable package, we can execute the following command:

mvn clean package

To actually execute it, we ought to call:

java -jar my-application.jar

Seeing our application startup verifies that we packaged it correctly, and are now able to deploy it to various systems via our CI / CD workflow.

systemd Setup

After we have configured our project to package our application as a jar file, it's time to set up the systemd configuration on our Linux server. To add another layer of security to our deployed application, it would always be advisable to run our application as a specific application user.

Creating a user can easily be done like so:

sudo useradd myapplication
sudo chown myapplication:myapplication my-application.jar
sudo chmod 500 my-application.jar
  • As a first step the code above is creating a new user called myapplication.
  • Secondly we are changing the owner of the deployed file (via CI / CD or direct upload) to be the just created user myapplication.
  • As a last part, we are allowing that user to read and execute the deployed file, but totally withdraw access to the deployed jar from the group or anyone else on the system.

This way access to the system can be restricted, even if due to a code issue someone is able to access the file system through our deployed application.

Now it's time to set up our systemd configuration file, telling the daemon how to handle our application.

[Unit]
Description=A Spring Boot application
After=syslog.target

[Service]
User=myapplication
ExecStart=/opt/my-application.jar
SuccessExitStatus=143
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

This code snippet tells systemd how to run our application and with which user. But not only that. We also added the 2 directives Restart and RestartSec which instruct systemd to always restart our service if it's not running, but wait 5 seconds before each restart. This makes sure that even if our application might crash, it will automatically be restarted by systemd.

To enable our configuration, we can execute

sudo systemctl enable my-application.service

This tells systemd to always start the service during boot up of the host, and keep it running even if it crashes.

To verify that we have now successfully deployed our Spring Boot application via systemd, we should be able to check the status of our application with the following command:

sudo systemctl status my-application.service

To start/stop/restart our application we might execute one of the following commands:

sudo systemctl start my-application.service
sudo systemctl stop my-application.service
sudo systemctl restart my-application.service

Accessing the logs with journalctl

If we haven't configured anything special, our Spring Boot application should write its logs to stdout. Since our application is now running as a daemon, we aren't able to view the stdout of the application anymore. That's where journalctl comes into play.

systemd writes all the logs of its services into a so called journal managed by the journald service. That journal can easily be accessed via journalctl:

sudo journalctl -u my-application.service

The command from above will drop us into a view with which we'll be able to view all the logs of the application since it has been deployed, with various options for filtering and more.

If we'd like to view just the last log lines and keep watching for new log output that is about to come, we can follow our logs by appending the -f option:

sudo journalctl -u my-application.service -f

The statement will tell us about the last log lines written by our application, but also keeps watching for new logs lines to come.

CI / CD

Having our setup readily configured to work as a systemd daemon process, the last thing we'll need to clarify is how to proceed when deploying new versions of our application.

Which is actually pretty straight forward, we just have to replace the deployed version of our application jar via direct upload, rsync or any other deployment tool.

rsync -avIhz --progress ./my-application/target/my-application.jar myaplication@123.45.67.231:/opt/my-application.jar
ssh gitlab@123.45.67.231 "sudo systemctl restart my-application.service"

Having our freshly built application synced to the server, the only thing left is restarting the service in order for the server to pick up the new changes we made in our application.

Congratulations!

You made it! Now you should be able to package your Spring Boot application as a self-containing jar file, and have it securely deployed as a systemd service on your Linux hosts.