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.