Up(sun) and running with Lisp

Up(sun) and running with Lisp

March 17, 2025· Kemi Elizabeth Ojogbede
Kemi Elizabeth Ojogbede
·Reading time: 5 minutes
Lisp support was discontinued in March 2025. If you really want to power a Lisp project, feel free to contact us on our Discord.

Lisp, created by John McCarthy in 1958, is one of the oldest programming languages, renowned for its unique syntax where code and data share the same structure. It’s commonly used in fields like artificial intelligence and functional programming, with its main strengths being its flexibility and suitability for recursive data structures.

In this step-by-step guide, you will learn how to quickly deploy a Lisp web application on both Platform.sh and Upsun. So, without further ado, let’s jump right in:

Write your Lisp program

For this guide we will be using a basic example of a Hunchentoot-based web app (you can find the corresponding .asd and .yaml files further down):

(defpackage #:example
  (:use :hunchentoot :cl-who :cl)
  (:export main))

(in-package #:example)

(define-easy-handler (greet :uri "/hello") (name)
  (with-html-output-to-string (s) (htm (:body (:h1 "hello, " (str name))))))

(defun main ()
  (let ((acceptor (make-instance
                   'easy-acceptor
                   :port (parse-integer (uiop:getenv "PORT")))))
    (start acceptor)
    (sleep most-positive-fixnum)))

Notice how the app gets the PORT from the environment and sleeps at the end as (start acceptor) yields and the apps run in the foreground as required.

This code is surprisingly legible. For example, use means “import all the symbols so there’s no need to prefix them with the package name” and cl-who allows the actual HTML to be created.

Specify the language in your app

Next, make sure that you specify lisp as your app’s type in your config.yaml:

.platform.app.yaml
type: 'lisp:2.1'
.upsun/config.yaml
applications:
  # The app's name, which must be unique within the project.
  <APP_NAME>:
    type: 'lisp:2.1'

Handle dependencies

The recommended way to handle Lisp dependencies with Upsun and Platform.sh is through using ASDF.

ASDF is a description of dependencies between files of source code in such a way that they can be compiled and loaded in the right order.

Create an .asd file. When an .asd file is found in your repository, the system will automatically download the dependencies using QuickLisp.

Below is an example.asd file:

example.asd
(defsystem example
 :name "example"
 :description "Example of a simple web application on Platform.sh"
 :author "Lisp Coder <user@example.com>"
 :components ((:file "example"))
 :build-operation "asdf:program-op"
 :build-pathname "example"
 :entry-point "example:main"
 :depends-on (:hunchentoot :cl-who))

Assumptions

Before we continue, the following assumptions have been made about your application to provide a more streamlined experience. These assumptions are the following:

  • Your .asd file is named like your system name. For example, our application above is named example.asd and has (defsystem example ...) in the .asd file.

Therefore (asdf:make :example) will run on our system to build a binary.

If you don’t want these assumptions, you can disable this behavior by specifying in your config.yaml:

.platform.app.yaml
build:
  flavor: none
.upsun/config.yaml
applications:
  myapp:
    type: 'lisp:2.1'
    build:
      flavor: none

Okay, let’s proceed…

Specify QuickLisp distrubutions

If you wish to change the distributions that QuickLisp is using, you can specify those as follows, specifying a distribution name, its URL and, an optional version:

.platform.app.yaml
runtime:
  quicklisp:
    DISTRIBUTION_NAME:
      url: "..."
      version: "..."
.upsun/config.yaml
applications:
  # The app's name, which must be unique within the project.
  <APP_NAME>:
    type: 'lisp:2.1'
    runtime:
      quicklisp:
        DISTRIBUTION_NAME:
          url: "..."
          version: "..."

For example:

.platform.app.yaml
runtime:
  quicklisp:
    quicklisp:
      url: 'http://beta.quicklisp.org/dist/quicklisp.txt'
      version: '2025-03-17'
.upsun/config.yaml
applications:
  # The app's name, which must be unique within the project.
  myapp:
    type: 'lisp:2.1'
    runtime:
      quicklisp:
        quicklisp:
          url: 'http://beta.quicklisp.org/dist/quicklisp.txt'
          version: '2025-03-17'

Get built-in variables

Upsun and Platform.sh expose relationships and other configurations as environment variables. To get the PORT environment variable (the port on which your web application is supposed to listen):

(parse-integer (uiop:getenv "PORT"))

Build and run your application

Assuming example.lisp and example.asd are present in your repository, the app is automatically built on push.

You can then start it from the web.commands.start directive.

Note that the start command must run in the foreground. Should the program terminate for any reason it’s automatically restarted. In the example below the app sleeps for a very long time. You could also choose to join the thread of your web server, or use other methods to make sure the program doesn’t terminate.

Create a .yaml containing something like what is shown below:

.platform.app.yaml
name: myapp
type: lisp:2.1
web:
  commands:
    start: ./example
  locations:
    /:
      allow: false
      passthru: true
disk: 512
.upsun/config.yaml
applications:
  myapp:
    type: 'lisp:2.1'
    web:
      commands:
        start: ./example
      locations:
        /:
          allow: false
          passthru: true

Note that a proxy server is still in front of your app. If desired, certain paths may be served directly by the router without hitting your app (for static files, primarily) or you may route all requests to the Lisp application unconditionally, as in the example above.

Access services

The services configuration is available in the environment variable PLATFORM_RELATIONSHIPS.

To parse them, add the dependencies to your .asd file:

:depends-on (:jsown :babel :s-base64)

The following is an example of accessing a PostgreSQL instance:

(defun relationships ()
  (jsown:parse
   (babel:octets-to-string
    (with-input-from-string (in (uiop:getenv "PLATFORM_RELATIONSHIPS"))
      (s-base64:decode-base64-bytes in)))))

Given a relationship defined in your .yaml config file:

.platform.app.yaml
relationships:
  postgresql:
💡
Depending on your needs, instead of the default endpoint configuration above, you can use an explicit endpoint configuration.
.upsun/config.yaml
applications:
  myapp:
    type: 'lisp:2.1'
    relationships:
      postgresql:
💡
Depending on your needs, instead of the default endpoint configuration above, you can use an explicit endpoint configuration.```

The following would access that relationship, and provide your Lisp program the credentials to connect to a PostgreSQL instance.

Add this to your .asd file:

:depends-on (:postmodern)

Now, in your program you can access the PostgreSQL instance as follows:

(defvar *pg-spec* nil)

(defun setup-postgresql ()
  (let* ((pg-relationship (first (jsown:val (relationships) "postgresql")))
         (database (jsown:val pg-relationship "path"))
         (username (jsown:val pg-relationship "username"))
         (password (jsown:val pg-relationship "password"))
         (host (jsown:val pg-relationship "host")))
    (setf *pg-spec*
      (list database username password host)))
  (postmodern:with-connection *pg-spec*
    (unless (member "example_table" (postmodern:list-tables t) :test #'string=)
      (postmodern:execute "create table example_table (
    a_field TEXT NOT NULL UNIQUE,
    another_field TEXT NOT NULL UNIQUE
")))))

Conclusion

In conclusion, deploying a Lisp-based web application on Platform.sh and Upsun is a straightforward process once you understand the key configurations and dependencies involved. From setting up your Lisp program to managing environment variables and service relationships, you can easily get your application up(sun) and running.

Once again, while Lisp’s support is discontinued as of March 2025, the language remains a valuable tool for certain projects. If you’re looking to power a Lisp application with composable image in the future, don’t hesitate to reach out via our Discord for assistance.
Last updated on