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 <email@example.com>
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
### 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”.
Now you’re ready to use your new Docker image from a project by simply
using the name we tagged the image with above (
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
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
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.