Deployment

How to deploy and monitor a Common Lisp web app?

Info

We are re-using content we contributed to the Cookbook.

Deploying manually

We can start our executable in a shell and send it to the background (C-z bg), or run it inside a tmux session. These are not the best but hey, it works©.

Here’s a tmux crashcourse:

  • start a tmux session with tmux
  • inside a session, C-b is tmux’s modifier key.
    • use C-b c to create a new tab, C-b n and C-b p for “next” and “previous” tab/window.
    • use C-b d to detach tmux and come back to your original console. Everything you started in tmux still runs in the background.
    • use C-g to cancel a current prompt (as in Emacs).
  • tmux ls lists the running tmux sessions.
  • tmux attach goes back to a running session.
    • tmux attach -t <name> attaches to the session named “name”.
    • inside a session, use C-b $ to name the current session, so you can see it with tmux ls.

Here’s a cheatsheet that was handy.

Unfortunately, if your app crashes or if your server is rebooted, your apps will be stopped. We can do better.

SystemD: daemonizing, restarting in case of crashes, handling logs

This is actually a system-specific task. See how to do that on your system.

Most GNU/Linux distros now come with Systemd, so here’s a little example.

Deploying an app with Systemd is as simple as writing a configuration file:

$ emacs -nw /etc/systemd/system/my-app.service
[Unit]
Description=stupid simple example

[Service]
WorkingDirectory=/path/to/your/app
ExecStart=/usr/local/bin/sthg sthg
Type=simple
Restart=always
RestartSec=10

Then we have a command to start it:

sudo systemctl start my-app.service

a command to check its status:

systemctl status my-app.service

and Systemd can handle logging (we write to stdout or stderr, it writes logs):

journalctl -f -u my-app.service

and it handles crashes and restarts the app:

Restart=always

and it can start the app after a reboot:

[Install]
WantedBy=basic.target

to enable it:

sudo systemctl enable my-app.service

With Docker

There are several Docker images for Common Lisp. For example:

Running behind Nginx

There is nothing CL-specific to run your Lisp web app behind Nginx. Here’s an example to get you started.

We suppose you are running your Lisp app on a web server, with the IP address 1.2.3.4, on the port 8001. Nothing special here. We want to access our app with a real domain name (and eventuall benefit of other Nginx’s advantages, such as rate limiting etc). We bought our domain name and we created a DNS record of type A that links the domain name to the server’s IP address.

We must configure our server with Nginx to tell it that all connections coming from “your-domain-name.org”, on port 80, are to be sent to the Lisp app running locally.

Create a new file: /etc/nginx/sites-enabled/my-lisp-app.conf and add this proxy directive:

server {
    listen www.your-domain-name.org:80;
    server_name your-domain-name.org www.your-domain-name.org;  # with and without www
    location / {
        proxy_pass http://1.2.3.4:8001/;
    }

    # Optional: serve static files with nginx, not the Lisp app.
    location /files/ {
        proxy_pass http://1.2.3.4:8001/files/;
    }
}

Note that on the proxy_pass directive: proxy_pass http://1.2.3.4:8001/; we are using our server’s public IP address. Often, your Lisp webserver such as Hunchentoot directly listens on it. You might want, for security reasons, to run the Lisp app on localhost.

Reload nginx (send the “reload” signal):

$ nginx -s reload

and that’s it: you can access your Lisp app from the outside through http://www.your-domain-name.org.

Deploying on Heroku, Digital Ocean, OVH, Deploy.sh and other services

See:

Cloud Init

You can take inspiration from this Cloud Init file for SBCL, an init file for providers supporting the cloudinit format (DigitalOcean etc).

Monitoring

See Prometheus.cl for a Grafana dashboard for SBCL and Hunchentoot metrics (memory, threads, requests per second,…).

See cl-sentry-client for error reporting.

References