Linux & Bash: Running a webapp

In this blog post we will be setting up a systemd unit for our webapp. systemd helps manage background system services and mount points. A systemd unit is a definition for that service. systemd also can do dependency management.

There are many types of units in systemd, but I will go through the most relevant unit, the service unit.

Service units define how to start, stop and reload the process, when to restart the process in the case of an error, dependencies the service requires to have started, and many other options.

Take a look at the manual page for systemd service unit files:

man systemd.service  

Unit files are placed in /etc/systemd/system/, and there are three main sections [Unit], [Install] and [Service].

[Unit] defines information about the unit that is independent of the type of unit. This includes things like description, dependencies. This is an example from MariaDB:

# /lib/systemd/system/mariadb.service
# ...

[Unit]
Description=MariaDB database server  
After=network.target  
After=syslog.target  

Here, the After options point to the units that MariaDB depends on. You can see that it uses syslog for logging, and thus needs the syslog target to be started before MariaDB.

[Install] defines options used while doing systemctl enable <unit> and systemctl disable <unit>.

# /lib/systemd/system/mariadb.service
# ...

[Install]
WantedBy=multi-user.target  
Alias=mysql.service  
Alias=mysqld.service  

Most user-defined unit files you create will have the option WantedBy=multi-user.target. This option would cause the unit file to be started when the system enters the multi-user target.

[Service] is the section where you define how to start and restart a service. There are multiple types of mechanisms that services can be monitored and restarted, but we will stick with the simplest type of service.

# /etc/systemd/system/app.service
# ...

[Service]
Type=simple  
User=myuser  
Group=myuser  
WorkingDirectory=/home/myuser/app  
Environment=NODE_ENV=production  
Environment=PORT=8011  
ExecStartPre=/usr/bin/npm install  
ExecStart=/usr/bin/npm start --production  
Restart=always  
RestartSec=3  

This is a modified extract from the unit file that commongoods uses to run it's webapp. Running services as root can open up the system to security issues, so the User and Group options ensures that the application runs as the user myuser.

WorkingDirectory specifies the starting directory to run the service in. It's similar to running cd in bash before running a command.

Environment sets environmental variables. They are another way, besides by arguments, you can pass data to an application. In this example, I set the variable PORT to be 8011 and within my application I would look up this environmental variable to determine the port to listen on. You can read more about environmental variables at the Arch Linux wiki.

ExecStartPre are some commands to run before the actual service. These can be initialisation commands. I run npm install in ExecStartPre to ensure that all the Node.js dependencies are installed.

ExecStart, when Type=simple, would be the command that is run in the background. Here, I am running npm start to run the Node.js application in the background.

Restart=always restarts the application when it exits. RestartSec defines the number of seconds to wait before retrying.

Now you can put together your first unit file! We will be using a Node.js webapp as an example. First, let's install Node.js and the webapp.

curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash -  
sudo apt install -y nodejs  
# Get the webapp
wget https://github.com/ambrosechua/file-manager/archive/master.zip  
unzip master.zip  
mv file-manager-master file-manager  
cd file-manager  
npm install # Install project dependencies  

You can run the webapp to check that it starts:

node index.js  

Press Control-C to stop the webapp. (Control-C sends a SIGTERM to the process, asking it nicely to exit. Signals are another way to communicate with running processes, and there are many other types of signals)

Time to write the unit file!

sudo nano /etc/systemd/system/app.service  
[Unit]
Description=My awesome webapp  
After=network.target

[Install]
WantedBy=multi-user.target

[Service]
Type=simple  
User=myuser  
Group=myuser  
WorkingDirectory=/home/myuser  
Environment=PORT=8081  
ExecStart=/usr/bin/node /home/myuser/file-manager/index.js  

Remember to replace myuser with your username. Now you can start the service:

sudo systemctl start app  
sudo systemctl status app # Prints out service status:  
● app.service - My awesome webapp
   Loaded: loaded (/etc/systemd/system/app.service; disabled; vendor preset: enabled)
   Active: active (running) since Mon 2017-03-20 12:47:06 SGT; 2min 9s ago
 Main PID: 23651 (node)
    Tasks: 6
   Memory: 30.0M
      CPU: 239ms
   CGroup: /system.slice/app.service
           └─23651 /usr/bin/node /home/ambrosechua/file-manager/index.js

Mar 20 12:47:06 cs5101 systemd[1]: Started My awesome webapp.  
Mar 20 12:47:06 cs5101 node[23651]: Mon, 20 Mar 2017 04:47:06 GMT express-session deprecated undefined res  
Mar 20 12:47:06 cs5101 node[23651]: Mon, 20 Mar 2017 04:47:06 GMT express-session deprecated undefined sav  
Mar 20 12:47:06 cs5101 node[23651]: Mon, 20 Mar 2017 04:47:06 GMT body-parser deprecated undefined extende  

If you set it up right, systemctl logs would show you the application logs as shown above. You can view the complete log with the journalctl command:

journalctl -u app  

You can stop the service with the below command. This sends a SIGTERM to the Node.js process:

sudo systemctl stop app  
sudo systemctl status app  
● app.service - My awesome webapp
   Loaded: loaded (/etc/systemd/system/app.service; disabled; vendor preset: enabled)
   Active: inactive (dead)

Mar 20 12:47:06 cs5101 systemd[1]: Started My awesome webapp.  
Mar 20 12:47:06 cs5101 node[23651]: Mon, 20 Mar 2017 04:47:06 GMT express-session deprecated undefined res  
Mar 20 12:47:06 cs5101 node[23651]: Mon, 20 Mar 2017 04:47:06 GMT express-session deprecated undefined sav  
Mar 20 12:47:06 cs5101 node[23651]: Mon, 20 Mar 2017 04:47:06 GMT body-parser deprecated undefined extende  
Mar 20 12:54:52 cs5101 systemd[1]: Stopping My awesome webapp...  
Mar 20 12:54:52 cs5101 systemd[1]: Stopped My awesome webapp.  

Now you should enable it to start on next boot:

sudo systemctl enable app  
sudo reboot  

You're done! Now, you can reverse proxy the app to make it accessible alongside other webapps.