You have worked hard on creating your application, its coding is perfection and you have tested it locally but now you want to containerise it and get it into the hands of users!
Like an 80’s and 90’s text based adventure game (yes, I know they existed before they became popular on computers) you have a few options, each with their own stories and different obstacles to deal with en route…..can you tell that I have recently watched the Netflix “High Score” series 😁
Each path has its own benefits and equally downsides! I decided to sketch out the common ones that I come across.
Dockerfile
Often peoples first experience of building containers and one of the most common methods is to use Dockerfile
. A Dockerfile is basically a file that acts like a script to create the container image. You specify how to build and what you want to see in the container images, then keep with the application code and commit to your git repo.
The Dockerfile can then be used to create locally (wherever you have a Docker Daemon running) or via your pipeline (should be the main way).
They are super flexible, which is both good and bad! You can have really simple Dockerfiles with just a few lines of code;
FROM openjdk:8-jdk-alpine
ARG JAR_FILE=JAR_FILE_MUST_BE_SPECIFIED_AS_BUILD_ARG
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-Djava.security.edg=file:/dev/./urandom","-jar","/app.jar"]
The community has collection of best practice Dockerfiles for the various languages has been compiled so you can just reuse those if you prefer. Some of them get close to 200 lines (although, to be fair they do have lots of comments). A few examples;
When I have a new project I end up needing to copy and paste from a previous project or consult the likes of Stackoverflow, forums, friends or random Internet searches (I don’t create enough torememberr off the top of my head). As a person who has spent a large portion of their career in the operations space I am more comfortable with the building blocks, however, some of the developers I have come across that would rather focus on writing an application and not have to think about the “boring” bit of containerisation (containers do solve many challenges we have had in days gone by, but they equally have moved concerns around).
Super, I now have a Dockerfile for each of my projects/applications (collection of snowflakes), I also now have 500 applications across my organisation and I need to make a change to the best practice template, say due to security vulnerability! How do I do this easier, or even know which images need to be updated?
I personally find Dockerfiles can be painful at times, although I do use them for some bits of work. We could do with a higher level of abstraction to make life easier for developers and operators.
At Compilation Time (JIB
)
Can we not create a container image as part of the compilation process? Once you have written code in certain languages (Java, Go, .Net etc.) you need to compile into a binary, so it does make some sense to expand the compilation process to also include creating the image. JIB is a Google project to do just this, I seem to remember Spotify having a Spring dependency to do this way before I discovered JIB.
Jib builds optimized Docker and OCI images for your Java applications without a Docker daemon - and without deep mastery of Docker best-practices. It is available as plugins for Maven and Gradle and as a Java library.
A little demo using JIB can be found here. You can just compile your application with your favourite tool, in my case maven;
mvn compile jib:build
In the JIB FAQ I saw a really interesting example of what an equivalent Dockerfile would look like;
# Jib uses distroless java as the default base image
FROM gcr.io/distroless/java:latest
# Multiple copy statements are used to break the app into layers, allowing for faster rebuilds after small changes
COPY dependencyJars /app/libs
COPY snapshotDependencyJars /app/libs
COPY projectDependencyJars /app/libs
COPY resources /app/resources
COPY classFiles /app/classes
# Jib's extra directory ("src/main/jib" by default) is used to add extra, non-classpath files
COPY src/main/jib /
# Jib's default entrypoint when container.entrypoint is not set
ENTRYPOINT ["java", jib.container.jvmFlags, "-cp", "/app/resources:/app/classes:/app/libs/*", jib.container.mainClass]
CMD [jib.container.args]
Great, this works a treat for my Java apps, what about the others? It turns out that KO exists for Go but yes it is not great for an organisation that is polyglot.
So what’s the third path I can take?
Buildpacks
In steps Buildpacks!
Created originally by Heroku in 2011 and then adopted as v2 in Cloud Foundry (where I first came across them whilst working at Pivotal) around 2013 and then v3 was created in January 2018. Both Heroku and Pivotal shared to the CNCF and a Sandbox project was created called Cloud Native Buildpacks.
They help raise the value line, allowing developers to focus on coding applications and operations teams to focus on running the application. The security teams are happier that the containers are created to the known standards and meet their compliance concerns. Day 2 (upgrades, maintenance) is much easier, especially when you have a large number of applications.
In summary the benefits of containers without needing to write or understand Dockerfiles. An example/demo I have used can be found here.
Google has release an Open source version designed to run on Google services and if you use Google App Engine or Cloud Functions buildpacks are used behind the scenes, although a few different options exist or you could create your own. I was super excited when I saw the option to hook in buildpacks to my Cloud Run services (including easy pipeline creation), it makes it so nice and easy to commit my code and applications get updated just the way I want.
Which path do you want to take?