I had a hard time writing my startup script for a Flask service I wanted to launch through GUnicorn. The official documentation is a little old, as systemd evolved a lot since then, and there were some mechanisms I did not understand at that time. Here is how, after all my findings and tries, I now start the service.
Description=Password Changer Webservice (GUnicorn)
ExecStart=/usr/bin/gunicorn -p %t/passchanger/passchanger.pid -D -c /srv/http/passchanger/gunicorn.py app:app
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
The main difference to the official example, is the use of the new
RuntimeDirectory option. This option will setup a private directory inside the system’s temporary runtime directory (usually
/run) with the right mode, user and group for our service. There, the service can create its own directory structure and/or temporary files, in our case the PID tracking file. The private folder will be cleaned and deleted automatically if the service stops. The
%t specifier, later in the script, is replaced by the system’s runtime directory (the parent folder of our private runtime directory). This allows for a configuration where the system’s runtime directory is not
PrivateTmp option is probably not needed. This creates a special
/tmp just for the service, so that files created there won’t interfere with files from other programs on the system. I left that option in case I later modify the service and need to write temporary files, and as a (thin) protection in case the service is attacked and compromised.
The last change I made is on the GUnicorn side. As the service forks twice while starting, the
stderr tracked by
systemd for its logs are lost, and so are the application logs. After adding the
enable_stdio_inheritance=True option to the
gunicorn.py configuration file, systemd was able to catch the logs, allowing for an easier debugging in case of failing start.