Skip to content

Instantly share code, notes, and snippets.

@yoeluk
Last active March 21, 2024 14:41
Show Gist options
  • Save yoeluk/e40d7fef2cc5c98da444d4373cb6290c to your computer and use it in GitHub Desktop.
Save yoeluk/e40d7fef2cc5c98da444d4373cb6290c to your computer and use it in GitHub Desktop.
build multi stages docker images

Build And Deploy Docker Containers

Linux Alpine docker image is based on super tiny Linux kernel BusyBox and features and package manager, apk, making easy to install build dependencies. This is particularly important for static compile languages like haskell and go as example but also for java and scala.

Scala is also a statically compiled language for the jvm and although we think of it as requiring just the jdk the typical way to build a scala application is with sbt which requires the jvm but the build also downloads a considerable number of sbt core dependencies. Hence, the safe and reliable way for reproducing build a project anywhere is to start from equal sbt installation. The best way to ensure that is to start from a pre-loaded sbt docker image.

Ideally we shouldn't deploy the build system but just the artifact in a reliable and reproducible way. Here, I'll describe two ways to do just that.

Sbt Base Image

FROM takari-oss-alpine

WORKDIR /root

ARG SBT_VERSION

ENV SBT_HOME=/usr/local/sbt

ENV PATH=${PATH}:${SBT_HOME}/bin

ADD https://dl.bintray.com/sbt/native-packages/sbt/$SBT_VERSION/sbt-$SBT_VERSION.tgz .

RUN tar -xf sbt-$SBT_VERSION.tgz && mv ./sbt-launcher-packaging-$SBT_VERSION /usr/local/sbt

RUN sbt version

A typical docker build command would be docker build --no-cache --squash --build-arg SBT_VERSION=0.13.13 takari-oss-alpine-sbt:8_0.13.13

Note that the command --squash may not be available in your docker version but if it is we should use it as it would compressed the different committed layers into just one and would reduce uploading time and size of the resulting image.

The Builder Pattern

The resulting sbt base image above from the previous step is quite large already at 376MB. Let's use it to build a known app and see what's the result. Let's build autodimension as example.

Dockerfile

FROM takari-oss-alpine-sbt:8_0.13.13

ENV CASSANDRA_USERNAME=cassandra CASSANDRA_PASSWORD=cassandra

ENV rest_interface=8080

RUN mkdir -p /root/usr/src/wm_auto_dimension/

COPY . /root/usr/src/wm_auto_dimension

WORKDIR /root/usr/src/wm_auto_dimension

RUN mkdir -p /log/autodimension && \
    mkdir -p /etc/autodimension && \
    cp -r src/main/resources /etc/autodimension

RUN sbt -Dsbt.override.build.repos=true -Dsbt.repository.config=repositories assembly

VOLUME ["/etc/autodimension","/log/autodimension"]

EXPOSE ${rest_interface}

ENTRYPOINT ["java","-jar","target/scala-2.11/autodimension.jar"]

CMD ["/etc/autodimension/resources/qa-config.conf"]

You'll build autodimension with docker build --no-cache --squash -t autodimension:build typically.

This is a fine image we could use to run autodimension but it is huge clocking 734MB. This is where the build pattern comes in. We'll prepare a different dockerfile in the same root directory. We'll call it Dockerfile.deploy.

Dockerfile.deploy

FROM openjdk:8-jre-alpine

ENV CASSANDRA_USERNAME=cassandra CASSANDRA_PASSWORD=cassandra

ENV rest_interface=8080

WORKDIR /root/

RUN mkdir -p /log/autodimension && \
    mkdir -p /opt/autodimension && \
    mkdir app

COPY app app

VOLUME ["/opt/autodimension","/log/autodimension"]

EXPOSE ${rest_interface}

ENTRYPOINT ["java","-jar","./app/autodimension.jar"]

CMD ["/app/resources/qa-config.conf"]

And a build script to copy the artifact over from the build image leaving behind the build system and deploying just what's needed to run the application.

build.sh

#!/bin/sh
echo Building autodimension:build

docker build -t autodimension:build .

mkdir app

docker create --name extract autodimension:build
docker cp extract:/root/usr/src/wm_auto_dimension/target/scala-2.11/autodimension.jar ./app
docker cp extract:/root/usr/src/wm_auto_dimension/src/main/resources ./app/resources
docker rm -f extract

echo Building autodimension:$1

docker build --no-cache -t autodimension:$1 . -f Dockerfile.deploy

rm -r ./app

Now you can build the deployable docker image with build.sh 0.13.13 resulting in an image of size 161MB. This is now a very reasonable image size for a java/scala application.

Multi-Stage Docker Builds

The builder pattern is such a success and widespread that docker has decided to address the problem. What's the problem? Well, it relies in an intermediate shell script to do the building and while this will work extremely well with build servers it isn't friendly for kubernetes and other container driven cloud deployments since often is not possible to provide these instructions.

So, how would our Dockerfile look with multi-stage building? Like this

Dockerfile.multi

FROM takari-oss-alpine-sbt:8_0.13.13 as builder

RUN mkdir -p /root/usr/src/wm_auto_dimension/

COPY . /root/usr/src/wm_auto_dimension

WORKDIR /root/usr/src/wm_auto_dimension

RUN sbt -Dsbt.override.build.repos=true -Dsbt.repository.config=repositories assembly

FROM openjdk:8-jre-alpine

ARG VERSION

ENV CASSANDRA_USERNAME=cassandra CASSANDRA_PASSWORD=cassandra

ENV rest_interface=8080

WORKDIR /root/

RUN mkdir -p /log/autodimension && \
    mkdir -p /opt/autodimension && \
    mkdir app

COPY --from=builder /root/usr/src/wm_auto_dimension/target/scala-2.11/autodimension.jar ./app

COPY --from=builder /root/usr/src/wm_auto_dimension/src/main/resources ./app/resources

VOLUME ["/opt/autodimension","/log/autodimension"]

EXPOSE ${rest_interface}

ENTRYPOINT ["java","-jar","app/autodimension.jar"]

CMD ["/app/resources/qa-config.conf"]

You can build this image with docker build --no-cache --squash --build-arg VERSION=2.1.1 -t autodimension:multi . -f Dockerfile.multi and you'll find that the resulting image is also 161MB but now we did it all with one Dockerfile and there was no need for an intermediate shell script.

Multi-stage building are available for docker version >= 17.05.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment