How to deploy a Racket web site to Ubuntu Linux with Apache

You’ve been working hard on your Racket web app. You’d like to deploy it, to make it live for everyone to see. But how do you do that?

To demonstrate, I’ll show you how I’ve deployed moon-and-stars.space, a single page web application (if that’s the right word) that displays a slowly rotating starscape with a moon.

The site is made in Racket, in the sense that the starscape is generated by a Racket program, which gets invoked from your browser via an AJAX call. The server is running on Ubuntu Linux 17.04 (Zesty Zapus). The main web server is Apache 2, which delivers a handful of resources and, crucially, serves as a (reverse) proxy between the user and an instance of the Racket web server, whose one and only job is to generate the starscape.

Although such a site is very simple, my hope is that this can kickstart you into doing web development with Racket.

What I’m leaving out

To keep this tutorial focused, I’m going to skip the following important problems.

Getting root access to a server.

There are many ways to do get access to a server connected to the internet, depending on your situation. You may want to rent server access using a cloud hosting provider such as Amazon Web Services (AWS) or Linode (which in fact drives the site that I’m describing here). I’ve used both and can recommend them. There are plenty of others.

These services are generally not free; you need to be able to pay for them. Alternatively, maybe you have access to a server through your school or workplace.

What kind of server to choose

When renting a server, you probably need to have a sense of how much memory your application needs. In my case, generating random starscapes and rendering them in SVG is pretty straightforward. I’m not worried of CPU or memory pressure. I don’t need a database or other services. But if you’re doing some heavy lifting, you may want to consider a server more appropriate to your application.

SSL

In my case, I see little motivation for serving a starscape over SSL. Obviously, not every site’s needs are the same.

Other web servers

I chose Apache, but you could equally well use nginx, lighttpd, etc.

Choosing, acquiring, and configuring a domain name

I use Hover, but there are lots of alternatives out there.

Structure of the site

The site offers three resources:

That’s it.

Apache takes care of the first two; Racket is sitting behind /moon.

To be precise, Racket deals with /moon/{width}/{height}, so I suppose it’s more like a family of resources. The idea is to take two integer arguments and generate a starscape having the specified width and height, which are computed based on a JavaScript snippet contained in index.html:

<script type="application/javascript">
  var w = window.innerWidth;
  var h = window.innerHeight;
  var url = '/moon/' + w + '/' + h;
  var moon = $('#moon');
  $.ajax({
    url: url,
    method: 'GET',
    success: function(xml, statusMessage, jqXHR) {
      console.log('xml:');
      console.log(xml);
      $(moon).replaceWith(xml.documentElement);
    },
    error: function() {
      alert('something went wrong!');
    }
  });

The site uses jQuery (that’s the $.ajax).

What needs to be on the server

I store my code on GitHub. On the server, I’ve installed git so that I can pull my code down. I store the code in my home directory, but I install it into another directory, /app. My installation process is, essentially:

cp *.rkt *.html *.js /app/

Apache configuration file

To give the site life, we need to tell Apache about it. Here’s the Apache2 config file (moon-and-stars.conf), which gets put into the /etc/apache2/sites-available directory (a directory that is created upon installing the apache2 package:

<VirtualHost *:80>
ServerName moon-and-stars.space
ProxyRequests On
ProxyPassMatch ^/moon/(\d+)/(\d+)$ http://localhost:5656/moon/$1/$2

<Location “^/moon/\d+/\d+$“>
Require all granted
</Location>

DocumentRoot /app/

<Directory /app/>
Options Indexes MultiViews
AllowOverride None
Require all granted
Order allow,deny
Allow from all
</Directory>

ErrorLog ${APACHE_LOG_DIR}/moon-and-stars.error.log
CustomLog ${APACHE_LOG_DIR}/moon-and-stars.access.log combined
</VirtualHost>

The file is fairly straightforward. The only somewhat interesting bit is the regular expression-based rewriting happening on line 4. Here, I fussily insist that any request for /moon be followed by two integer arguments.

The configuration file is, admittedly, not terribly clever. The main point is that Apache is acting as a proxy. It sits between the visitor and the Racket process.

To turn on the site, do

a2ensite moon-and-stars

and restart Apache with

service restart apache2

(The name of the site comes from stripping the trailing .conf from the configuration file name.)

Depending on what user is issuing these commands, you may have to run them using sudo. A typical gotcha is that the directory where Apache looks for the files (here, /app) should be owned by the www-user user, and everything should be readable by that user.

The Racket part

I use tmux to keep myself logged in to my server all the time. In a separate terminal window within tmux, I invoke Racket as follows:

racket site.rkt

This launches my site on port 5656 (as you see in the Apache configuration file). The relevant Racket code looks like this:

(module+ main
  (serve/servlet dispatcher
                 #:servlet-regexp #rx""
                 #:port 5656
                 #:launch-browser? #f
                 #:listen-ip #f))

I’m using the standard Racket web server to accomplish this. I make heavy use of Matthew Butterick’s txexpr package to generate the SVG.

There’s essentially only one function here, moon, that takes two integer arguments and is responsible for generating a valid SVG image. There’s a bit of math involved, but I’ll spare you that. Here’s what the main function looks like:

(define (moon req width height)
  (define svg ;; a complicated txexpr?)
  (parameterize ([empty-tag-shorthand 'always])
    ;; SVG built, let's deliver it
    (response/full
       200
       #"OK"
       (current-seconds)
       #"image/svg+xml"
       empty
       (list (string->bytes/utf-8
              (format "~a~%~a~%"
                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                      (xexpr->string svg)))))))

(The empty-tag-shorthand ‘always bit is to ensure that childless XML elements get rendered <so/>, rather than <so></so>. I’m fussy like that.)

Finally, the dispatcher for the site looks like this:

(define-values (dispatcher _)
  (dispatch-rules
   [("moon" (integer-arg) (integer-arg)) #:method "get" moon]
   [else                not-found]))

(Here, not-found is a function that delivers a plain HTTP 404 response.)

Improvements

There are a number of ways that the whole setup could be improved:

Daemonization

The Racket process should be wrapped in a process that restarts itself if it gets killed. As it stands, if the Racket process dies, then the site is down. Oops.

Resource limiting

At the moment, the Racket process is run as a normal process, which means that there’s no limits on memory consumption and CPU time. One can get away with such a practice for such a simple site, perhaps; but if you site is involves more resource consumption, and you imagine getting lots of traffic, it’s worthwhile to think about how to limit Racket’s resource consumption to suit your case.

Fancier deployment

Automated deployment services (e.g., AWS’s CodeDeploy) are pretty great. I’ve kept things simple here and do most things by hand. If I change the code, I log in to the server, pull the changes, and run my poor-man’s installation script. But even in my simple case, it’s possible for little mistakes to creep in. For more complex sites, or for a code base where you want to deploy frequently, it’s definitely a good idea to invest some time in automating deployments.

Another idea would be to deploy a Docker image using a service such as Amazon’s Elastic Container Service. Indeed, I develop the code with Docker on my laptop, but Docker isn’t being used on the live site.