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:
Unit files are placed in
/etc/systemd/system/, and there are three main sections
[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
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
Group options ensures that the application runs as the user
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.
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:
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: Started My awesome webapp. Mar 20 12:47:06 cs5101 node: Mon, 20 Mar 2017 04:47:06 GMT express-session deprecated undefined res Mar 20 12:47:06 cs5101 node: Mon, 20 Mar 2017 04:47:06 GMT express-session deprecated undefined sav Mar 20 12:47:06 cs5101 node: 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 -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: Started My awesome webapp. Mar 20 12:47:06 cs5101 node: Mon, 20 Mar 2017 04:47:06 GMT express-session deprecated undefined res Mar 20 12:47:06 cs5101 node: Mon, 20 Mar 2017 04:47:06 GMT express-session deprecated undefined sav Mar 20 12:47:06 cs5101 node: Mon, 20 Mar 2017 04:47:06 GMT body-parser deprecated undefined extende Mar 20 12:54:52 cs5101 systemd: Stopping My awesome webapp... Mar 20 12:54:52 cs5101 systemd: 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.