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.