How to Create a Customized Clojure Docker Image

Creating a Docker image isn’t particularly difficult, but I never do it often enough to remember the incantations required. Yesterday I needed a Docker image for running CI jobs on Circle CI. There are some great pre-built images out there, but I couldn’t find the combination of Java 17, Leiningen, and Node that I was looking for. This is a quick guide to how to build your own image with the tools you need.

Great Artists Steal

First off we want to find a suitable base image to start with. Docker images are built in layers, so we can start with a solid foundation pre-written by an expert and simply layer on the additional bits we need. Circle CI has a great set of pre-built Docker images that take care of the heavy lifting.

Here we’ll use their cimg/openjdk:17.0.1-node image. This is a variant of their cimg/openjdk:17.0.1 image which already includes Node. Two pieces already done for us!

Create a new Dockerfile and add the following:

FROM cimg/openjdk:17.0.1-node
MAINTAINER Your Name <you@your.domain>

To layer on Leiningen, I cribbed the relevant incantations from the official Clojure Docker images. From that page you can click on any of the Dockerfile links to see how they’re defined and borrow what you need.

In my case, I simply grabbed these lines and appended them to my Dockerfile:

### INSTALL LEIN ###
ENV LEIN_VERSION=2.9.8
ENV LEIN_INSTALL=/usr/local/bin/

WORKDIR /tmp

# Download the whole repo as an archive
RUN set -eux; \
    sudo apt-get update && \
    sudo apt-get install -y gnupg wget && \
    sudo rm -rf /var/lib/apt/lists/* && \
    mkdir -p $LEIN_INSTALL && \
    wget -q https://raw.githubusercontent.com/technomancy/leiningen/$LEIN_VERSION/bin/lein-pkg && \
    echo "Comparing lein-pkg checksum ..." && \
    sha256sum lein-pkg && \
    echo "9952cba539cc6454c3b7385ebce57577087bf2b9001c3ab5c55d668d0aeff6e9 *lein-pkg" | sha256sum -c - && \
    sudo mv lein-pkg $LEIN_INSTALL/lein && \
    sudo chmod 0755 $LEIN_INSTALL/lein && \
    export GNUPGHOME="$(mktemp -d)" && \
    export FILENAME_EXT=jar && \
    if printf '%s\n%s\n' "2.9.7" "$LEIN_VERSION" | sort -cV; then \
      gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys 6A2D483DB59437EBB97D09B1040193357D0606ED; \
    else \
      gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys 20242BACBBE95ADA22D0AFD7808A33D379C806C3; \
      FILENAME_EXT=zip; \
    fi && \
    wget -q https://github.com/technomancy/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT && \
    wget -q https://github.com/technomancy/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT.asc && \
    echo "Verifying file PGP signature..." && \
    gpg --batch --verify leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT.asc leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT && \
    gpgconf --kill all && \
    rm -rf "$GNUPGHOME" leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT.asc && \
    sudo mkdir -p /usr/share/java && \
    sudo mv leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT /usr/share/java/leiningen-$LEIN_VERSION-standalone.jar && \
    sudo apt-get purge -y --auto-remove gnupg wget

ENV PATH=$PATH:$LEIN_INSTALL
ENV LEIN_ROOT 1

I’m very glad I didn’t have to write all of that myself because I would have been tempted to cut corners on the validation of the Leiningen package contents and that wouldn’t have been good.

Building the Image

Now we have everything we need to build the image. Open a shell to the location where you put the Dockerfile and run the following command (making appropriate name substitutions for your Docker organization and desired image name):

docker build --tag collbox/clojure-ci .

Docker will pull the base image, layer on your changes, and create a new local image.

If you’re ready to share that image with your coworkers or build server, push it to Docker Hub with:

docker push collbox/clojure-ci

By default the image will be private. If you haven’t added any private information, you can make the image public and make it easy to access without adding authentication. Do this by logging into Docker Hub, clicking the image, “Settings”, and “Make public”.

Next Steps

Now you’re ready to use your new Docker image from a project by simply using the name we tagged the image with above (collbox/clojure-ci, in this example).

Finally, why not version control your Dockerfile and add a Makefile for next time you forget this process and need to update the image?

image=collbox/clojure-ci

build:
	docker build --tag $(image) .

push:
	docker push $(image)

.PHONY: build push

Now you can rebuild the image with a simple make build or re-push it with make push.

That’s all there is to it.


If you want to see the code above fully assembled, check out collbox/clojure-ci-docker on GitHub.