Building Docker images for Common Lisp applications

Hello, all Common Lispers.

Today, I will introduce how to build a Docker container for Common Lisp applications.

Docker is designed to provide the same execution environment throughout the development, CI, and production environment.

It is losing momentum as an infrastructure to support increasingly large web applications, but it is still useful when viewed as a development tool.

Recently, Eric Timmons has stably maintained Docker images provided by the Common Lisp Foundation, and I have also published SBCL and CCL images containing Roswell.

This article will show how to write a Dockerfile based on these images and create your image to run a Quicklisp or Common Lisp application.

Using clfoundation/sbcl

First, let's discuss using Common Lisp Foundation's image.

This image provides only a Lisp implementation, which means you must install Quicklisp if necessary.

Here's an example of a Dockerfile based on the CL Foundation image with Quicklisp:

FROM clfoundation/sbcl:2.2.2-slim
ARG QUICKLISP_DIST_VERSION=2022-02-20

WORKDIR /app
COPY . /app

ADD https://beta.quicklisp.org/quicklisp.lisp /root/quicklisp.lisp

RUN set -x; \
  sbcl --load /root/quicklisp.lisp \
    --eval '(quicklisp-quickstart:install)' \
    --eval '(ql:uninstall-dist "quicklisp")' \
    --eval "(ql-dist:install-dist \"http://beta.quicklisp.org/dist/quicklisp/${QUICKLISP_DIST_VERSION}/distinfo.txt\" :prompt nil)" \
    --quit && \
  echo '#-quicklisp (load #P"/root/quicklisp/setup.lisp")' > /root/.sbclrc && \
  rm /root/quicklisp.lisp

It allows specifying explicitly to fix the Quicklisp dist version.

Dockerfile is like a step-by-step guide for creating an execution environment, and docker build allows you actually to create a Docker image.

$ docker build -t clf-sbcl .
$ docker run --rm -it clf-sbcl

# Use the dist version '2022-04-01'
$ docker build -t clf-sbcl --build-arg QUICKLISP_DIST_VERSION=2022-04-01 .

This image provides only a minimal setup, for better or worse. Notably that they provide images for Windows, if you need to run it on Windows, this image is a strong candidate.

If you are not concerned about the size of the final Docker image, the Dockerfile can be simplified a bit by using the non-slim base image (clfoundation/sbcl:2.2.2-slim -> clfoundation/sbcl:2.2.2).

FROM clfoundation/sbcl:2.2.2
ARG QUICKLISP_DIST_VERSION
ARG QUICKLISP_ADD_TO_INIT_FILE=true

WORKDIR /app
COPY . /app

RUN set -x; \
  /usr/local/bin/install-quicklisp

Using fukamachi/sbcl

Next, I will introduce the Docker images I provide.

The difference that this image has is that it contains Roswell and Quicklisp.

FROM fukamachi/sbcl:2.2.3

WORKDIR /app
COPY . /app

RUN set -x; \
  ros install qlot && qlot install

ENV QUICKLISP_HOME /app/.qlot/

Since it is not possible to specify the dist version of Quicklisp, it is recommended to use Qlot; see the previous article for how to use Qlot.

# Create qlfile.lock beforehand
$ touch qlfile
$ echo .qlot/ | tee -a .gitignore | tee -a .dockerignore
$ docker run --rm -it -v $PWD:/app fukamachi/qlot install

$ docker build -t fukamachi-sbcl .
$ docker run --rm -it fukamachi-sbcl

The good thing about this image is the Dockerfile is short, and Qlot is easy to be installed. On the other hand, the only architectures offered are AMD64 and ARM64 for Linux.

multi-stage build

If you do not want to include Qlot in the final image, you can use Docker's multi-stage build.

The "multi-stage build" is a mechanism that separates the build environment from the execution environment so that tools needed only at build time are not left in the final image.

It can be used by writing multiple FROM clauses in the Dockerfile, like this:

FROM fukamachi/qlot:1.0.1 AS build-env

WORKDIR /app
COPY . /app

RUN set -x; \
  qlot install

FROM fukamachi/sbcl:2.2.3

WORKDIR /app
COPY --from=build-env /app /app

ENV QUICKLISP_HOME /app/.qlot/

Only the .qlot directory after qlot install is copied to the final image.

The final image can also be clfoundation/sbcl based, and in this way, it is possible to create a final image that does not even include Roswell.

FROM fukamachi/qlot:1.0.1 AS build-env

WORKDIR /app
COPY . /app

RUN set -x; \
  qlot install

FROM clfoundation/sbcl:2.2.2-slim

WORKDIR /app
COPY --from=build-env /app /app

ENTRYPOINT ["sbcl", "--load", ".qlot/setup.lisp"]

Don't forget to load .qlot/setup.lisp to enable the project-local Quicklisp.

Publish the Docker image to Docker Registry

Built images can be pushed to the Docker registry to allow people to use them or deploy them to remote environments.

Several Docker registry services are available: Docker Hub, officially provided by Docker, and GitHub Container Registry, provided by GitHub, is well known for OSS use.

You can also use GitHub Actions to publish an image to the registry each time you push to GitHub. It is easily accomplished using docker/build-and-push.