Building a simple HTTP server in Clojure: Part III — Dockerizing Clojure Application
What is Docker ?
Docker is a tool for containerizing applications, such that they can be run on any host. Docker images are lightweight, disposable and immutable. An image is built up of immutable layers, allowing consistent operations, small size, and reusability.
Creating a Dockerfile
Dockerfile forms the basis of any Docker image. It is a set of simple commands a user would call to assemble an image on command-line.
You can read more about it here.
Below is a basic Dockerfile for the app we have built:
LABEL maintainer="Divyum Rastogi"
COPY . /usr/src/app
CMD ["lein", "run"]
Docker runs instructions in a
Dockerfile in order. A
Dockerfile must start with a `FROM` instruction. Below is the explanation of dockerfile written above:
- FROM instruction specifies the base image from which we shall be building our app image. If a FROM image is not found on the host, docker will try to find it (and download) from registry.
- LABEL it enables setting any metadata to be viewed about the image like from
docker inspect <image_id>.
- COPY instruction copies files or directories from <src> and adds them to the filesystem of the container at the path
It has two forms:
COPY <src>... <dest>
COPY ["<src>",... "<dest>"](this form is required for paths containing whitespace)
- WORKDIR instruction sets the working directory for any
ADDinstructions that follow it in the
Dockerfile. If the
WORKDIRdoesn’t exist, it will be created even if it’s not used in any subsequent
- EXPOSE instruction informs Docker that the container listens on the specified network ports at runtime. You can specify whether the port listens on TCP or UDP, and the default is TCP if the protocol is not specified.
- CMD provide defaults for an executing container. There can only be one
CMDinstruction in a
Building and Running Image
Run these commands to build and run the image:
docker build -t clojure-server .
docker run -it --rm -p 8088:8080 clojure-server
- -t : tagname for the image
- -it : interactive shell
- - -rm: Remove intermediate containers after a successful build
- -p: exposes the port inside container to external (host) port (here 8080 is exposed to 8088)
Phew! We have build and run the server from our docker image.
While the above is the most straightforward example of a
Dockerfile, it does have some drawbacks. The
lein run command will download your dependencies, compile the project, and then run it. That's a lot of work, all of which you may not want done every time you run the image. To get around this, we can download the dependencies and compile the project ahead of time (aot). This will significantly reduce startup time of the image.
COPY . /usr/src/app
RUN mv "$(lein uberjar | sed -n 's/^Created \(.*standalone\.jar\)/\1/p')" app-standalone.jar
CMD ["java", "-jar", "app-standalone.jar"]
We see some new commands in the file:
- RUN instruction will execute any commands in a new layer on top of the current image and commit the results. The resulting committed image will be used for the next step in the
lein uberjaris a leiningen command to build runnale jar from clojure app.
The above dockerfile builds a jar and then build the image with that jar. Thus, dependencies will not be downloaded everytime we run the container.
Now we can use the above command to build image and run it.
This was a very simple guide of how to dockerize a clojure app. To package an application, you need a simple Dockerfile and a runnable JAR.
The complete code can be found on Github.
If you have any questions/suggestions, feel free to drop it in comments and if you found this article useful, clap below.