Türchen #17: Docker Multistage Builds
Von Michael Krieg am 17. Dezember 2019
Wegweisende Architektur
Docker Architekturen erleichtern das Bereitstellen hochkomplexer Anwendungen. Für uns Entwickler bedeutet das, effizient und wiederverwendbar Systeme aufbauen zu können, die auch lokal während der Entwicklungsphase identisch verwendet werden.
Mit ein paar Kniffen können wir zudem auch sehr schlanke Docker Images bereitstellen, die tatsächlich nur das enthalten, was die Applikation verlangt.
Einsatz-Szenario 1 - Building Dependencies
Stellt Euch eine Java Application vor, welche nach dem Compile lediglich das fertige .jar File benötigt. Dieses sogenannte “Artefakt” ist das Einzige, was später in Zielumgebungen wie “Staging” und “Production” relevant ist. Somit können wir zwar beim Build-Prozess nicht auf Tools wie Maven oder Gradle verzichten, aber im Ziel-Container-Image ist neben der Java Runtime nur noch das fertiggebaute .jar File nötig:
Erstellen wir uns also im ersten Schritt ein Build-Image auf Basis von openJDK 8 und kopieren uns am Ende nur das resultierende .jar File daraus:
## unser Basis Image, welches die Java App baut:
FROM openjdk:8 AS BUILD_IMAGE
ENV APP_HOME=/root/dev/myapp/
RUN mkdir -p $APP_HOME/src/main/java
WORKDIR $APP_HOME
COPY build.gradle gradlew gradlew.bat $APP_HOME
COPY gradle $APP_HOME/gradle
# Dependencies
RUN ./gradlew build -x :bootRepackage -x test --continue
COPY . .
RUN ./gradlew build
## ab hier interessiert uns nur noch das .jar File:
FROM openjdk:8-jre
WORKDIR /root/
COPY --from=BUILD_IMAGE /root/dev/myapp/build/libs/myapp.jar .
EXPOSE 8080
CMD ["java","-jar","myapp.jar"]
Einsatz-Szenario 2 - Wiederverwendbarkeit und Image-Flavors
Angenommen, wir wollen PHP Images in verschiedenen Versionen und Varianten bauen. Da sich der Code im Dockerfile doch recht deutlich wiederholt und überflüssig aufbläht, kürzen wir es wie folgt: Wir bauen uns ein schlankes Alpine Image (“base”) und leiten davon dann Varianten ab, etwa “php-dev” oder “php-fpm”.
ARG ALPINE_VERSION
###########################################################
FROM alpine:${ALPINE_VERSION} as base
LABEL maintainer="Jérôme Gamez <jerome@kreait.com>"
COPY docker /docker/
# See https://github.com/gliderlabs/docker-alpine/issues/184
RUN \
sed -i 's/http\:\/\/dl-cdn.alpinelinux.org/https\:\/\/alpine.global.ssl.fastly.net/g' /etc/apk/repositories && \
apk update && apk upgrade
RUN /docker/scripts/install-packages.sh \
&& /docker/scripts/ensure-www-data.sh \
&& mv /docker/php-entrypoint /usr/local/bin/
ENTRYPOINT ["php-entrypoint"]
CMD ["php", "-a"]
###########################################################
FROM base AS php
RUN rm -rf /docker
###########################################################
FROM base as php-dev
RUN /docker/scripts/install-dev-packages.sh \
&& rm -rf /docker
###########################################################
FROM base AS base-fpm
RUN apk --no-cache add php7-fpm \
&& mv /docker/www.conf /etc/php7/php-fpm.d/www.conf
EXPOSE 9000
CMD ["php-fpm7"]
###########################################################
FROM base-fpm as php-fpm
RUN rm -rf /docker
###########################################################
FROM base-fpm as php-fpm-dev
RUN /docker/scripts/install-dev-packages.sh \
&& rm -rf /docker
Mit diesem einen Dockerfile
können wir nun z. B. zwei PHP-Versionen (7.2 und 7.3) bauen (wichtig ist hier die --target
Option sowie die Angabe der ALPINE_VERSION
):
docker build --build-arg ALPINE_VERSION=3.9 --target php -t php:7.2 -f Dockerfile
docker build --build-arg ALPINE_VERSION=3.10 --target php -t php:7.3 -f Dockerfile
Den gesamten Sourcecode gibt es übrigens auf Github, inspiriert vom geschätzten Kollegen Jérôme Gamez.