7. Docker multi-stage build

2021-09-19 linkshortener docker

Docker multi-state build allows you to use two (or more) FROM statements in single Dockerfile. Each FROM starts new build stage. The main feature is you can copy files between stages.

Let’s prepare multi-stage build Dockerfile for described in previous posts linkshortener service. Dockerfile will consist of two stages:

  • first stage (builder) will pull git repository and will build project,
  • second stage will prepare release image, to do this it will use build result of first stage.

Described file might look as following:

FROM adoptopenjdk/openjdk11:centos as builder
ARG APP_VERSION
ENV CURRENT_VERSION APP_VERSION
COPY ssh-key/docker-key /root/.ssh/docker-key
COPY ssh-key/docker-key.pub /root/.ssh/docker-key.pub
RUN set -eux; \
    printf "Host github.com \n  Hostname github.com \n  IdentityFile ~/.ssh/docker-key \n  IdentitiesOnly yes" > /root/.ssh/config; \
    yum -y install git; \
    ssh-keyscan github.com >> /root/.ssh/known_hosts; \
    mkdir /app; \
    git clone git@github.com:psstepniewski/linkshortener.git app; \
    yum -y install curl; \
    curl -L https://www.scala-sbt.org/sbt-rpm.repo > sbt-rpm.repo; \
    mv sbt-rpm.repo /etc/yum.repos.d/; \
    yum -y install sbt; \
    cd app; \
    git checkout "$APP_VERSION"; \
    sbt dist

FROM alpine:3.14
COPY --from=builder /app/target/universal/linkshortener.zip /srv/linkshortener.zip
WORKDIR /srv
RUN set -eux; \
    apk update && apk add --no-cache tcpdump nano tzdata && cp /usr/share/zoneinfo/Europe/Warsaw /etc/localtime && echo "Europe/Warsaw" > /etc/timezone && apk del tzdata && adduser --no-create-home --disabled-password --gecos "" --uid 2727 wheelfred wheelfred; \
    apk add --no-cache openjdk11-jre; \
    apk add --no-cache bash; \
    unzip linkshortener.zip; \
    mv linkshortener app; \
    rm -rf /srv/linkshortener.zip; \
    chown -R wheelfred:wheelfred /srv/*
VOLUME ["/srv/logs"]
CMD ./app/bin/linkshortener \
  -Dconfig.file=/srv/app/conf/linkshortener.conf \
  -Ddb.default.migration.auto=true \
  -Dhttp.port=13256

Result image consists from layers of second stage. First stage is used only to build application and is not part of final image. All multi-stage build magic is in line COPY --from=builder /app/target/universal/linkshortener.zip /srv/linkshortener.zip. It copies built application from first to second stage. Pay attention you can name Dockerfile stage, in above file name of first stage is builder FROM adoptopenjdk/openjdk11:centos as builder.

This simple feature resolves big problem. Before this you had two options:

  • Build and run application in one Dockerfile. Downside of this solution is bigger image size which contains tools needed to build application (like sbt). In our case it would contain also read key to GitHub repository and project source code. You can prepare Dockerfile which doesn’t pull git repository and uses volumes to access source code, but still you result with Dockerfile of image which need to be able to build project. Additional configuration of host machine is also required (pulling repository and setting volume).
  • Build project with one Dockerfile and prepare release image with second Dockerfile. It is analogous solution to multi-stage build, but it is more complicated. You need to build application in first Dockerfile in CMD (or ENTRYPOINT) statement and place result in volume. Then second Dockerfile can access it using COPY or ADD statement.

Read more about multi-stage build in Docker documentation.