Deploy a racket web app as a daemon

Introduction

After spending a bit of time twidling around creating the simpliest of REST API using some racket lang, it was time to find a way to deploy it, and make it accessible from my domain. I spoke a bit about subdomains in apache , and I'll apply it in this case because I want to have an api subdomain for all backend services I'm offering on this domain. This racket specific one should be accessible from api.platyshell.me/facts.

TL;DR: In order to manage a racket process from systemctl, we just need to have a unit.service file that contains a path to the racket binary, and eventually some arguments that it should use. We then use Apache as a reverse proxy in order to expose its boundaries.

Create a systemd unit file

I want the unit file to be controlled by the racket user I have created. After browsing through the best way to go about this, I found a working solution in the wonderful arch wiki to be the one that matches the most what I am trying to do1 . The unit file is written in the /home/racket/.config/systemd/user/<unit>.service and a minimal content might look like this:

[Unit]
Description= Racket facts %i

[Service]
# racket project is located at /srv/racket/facts
WorkingDirectory=/srv/racket/facts/
ExecStart=/usr/bin/racket /srv/racket/facts/app.rkt %i

[Install]
WantedBy=multi-user.target

Depending on weither or not you want the racket process to access ports 80 and 443, you might want to add an entry AmbientCapabilities=CAP_NET_BIND_SERVICE to the [Service] header that should enable it to2. In our case we don't need to as we'll use Apache to serve the racket internal port to the outside world through a reverse proxy.

You can then give it a spin by doing a systemctl --user start facts.service. I'm running this on a debian box, and I run in a couple of issues regarding the a failed connection the the bus, but found a working solution here.

Edit: Ultimately, the issue came from the fact that the user's runtime switch isn't handled when changing user through the su command on an SSH connection. That means that there are no such issue when ssh-ing directly as the user handling the service.

Setup the subdomain and proxy requests through Apache

Setup the subdomain

To register the api subdomain, all I had to do was create the following apache configuration file in /etc/apache2/sites-available/api.subdoma.in.conf.

<VirtualHost api.doma.in:80>
        ServerAdmin mymail@doma.in
        ServerName api.domain.in

        Redirect / https://api.doma.in

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

<VirtualHost api.doma.in:443>
        ServerName api.doma.in

        SSLEngine on
        SSLCertificateFile      /etc/ssl/certs/doma_in.pem
        SSLCertificateKeyFile /etc/ssl/private/doma_in.key

        # Racket runs on the 8000 local port by default when using their web-server
        # module. This can obviously be changed accordingly
        ProxyPass / http://localhost:8000
        ProxyPassReverse / http://localhost:8000

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

In this example, I'm also configuring a redirection from HTTP to HTTPS, but it isn't necessary. The actual important lines are the ProxyPass and ProxyPassReverse ones.

A simple a2ensite api.subdoma.in.conf && systemctl reload apache should bring the whole thing up.

Conclusion

Once everything is brought up, you should be able to reach the subdomain, and get the expected output from the racket process. One obvious thought I had writing and setting this up was how could this be automated. I'll show how I ended up bundling my microscopic racket app to be deployed and used automatically in the next post. I wrote this small article as I couldn't find something wrote up for this specific purpose, in hope that it helps someone.

Footnotes

1 : You can also create a unit file in /etc/systemd/system/ and add a couple of entries to the [Service] header like so:

[Unit]
Description= Racket facts %i

[Service]
User=racket
Group=racket
# racket project is located at /srv/racket/facts
WorkingDirectory=/srv/racket/facts/
ExecStart=/usr/bin/racket /srv/racket/facts/app.rkt %i

[Install]
WantedBy=multi-user.target

Don't use those extra lines if you're planning on having the unit file in your local configuration file (~/.config/systemd/user/unit.service=) as it will throw out a obscure error about how it "fails to determine supplementary groups". Check here if you stumble upon this issue.

2 : Details found in a "racket users" google groups conversation here.

links

social