Einige meiner Dockerfiles beinhalten viele RUN und COPY Schritte. Einfaches Beispiel, dass zur Veranschaulichung dient:
FROM docker.io/rockylinux:8
ARG VERSION=1.0.0
COPY FILE1 /etc/FILE1
COPY FILE2 /etc/FILE2
RUN /usr/local/sbin/install_script.sh
RUN rm -f /usr/local/sbin/install_script.sh
RUN microdnf -y install git-core jq sudo && \
microdnf clean all && \
rm -rf /var/cache/{dnf,yum} && \
rm -rf /var/lib/dnf/history.* && \
rm -rf /var/log/*
Jeder Schritt erzeugt einen neuen Container-Layer und vergrößert das Container-Image. Um das Container-Image zu verkleinern und alle Layer zusammenzufassen, gab es die experimentelle Option docker build --squash=true. Seit einigen Docker-Versionen wird bei Nutzung der Option allerdings folgende Meldung ausgegeben:
WARNING: experimental flag squash is removed with BuildKit. You should squash inside build using a multi-stage Dockerfile for efficiency.
Die squash Option steht uns also nicht mehr zur Verfügung. Wir können aber mit einem Multi-Stage Build alle Container Layer zu einem Layer zusammenfassen. Ein Multi-Stage Build ermöglicht es, mehrere Build-Stages in einem einzigen Dockerfile zu definieren. Dies ist besonders nützlich, um Abhängigkeiten zu installieren und Build-Schritte durchzuführen, ohne den endgültigen Container mit unnötigem Ballast zu belasten. z.B. lässt sich ein in GO geschriebenes Projekt im ersten Stage kompilieren und dann das fertig kompilierte Binary aus der ersten Stage in den zweiten Stage kopieren, ohne dabei alle Build Abhängigkeiten mitzunehmen. Diesen Mechanismus, Dateien zwischen Stages zu kopieren, können wir nutzen um alle Layer zu einem Layer zusammenzufassen. Beispiel:
FROM docker.io/rockylinux:8 AS build
ARG VERSION=1.0.0
COPY FILE1 /etc/FILE1
COPY FILE2 /etc/FILE2
RUN /usr/local/sbin/install_script.sh
RUN rm -f /usr/local/sbin/install_script.sh
RUN microdnf -y install git-core jq sudo&& \
microdnf clean all && \
rm -rf /var/cache/{dnf,yum} && \
rm -rf /var/lib/dnf/history.* && \
rm -rf /var/log/*
FROM scratch AS final
COPY --from=build / /
In der ersten Build-Stage werden alle Schritte ausgeführt. Anschließend wird das Root-Dateisystem / in die zweite Build-Stage kopiert. Dadurch wird das endgültige Container Image auf einen einzigen Layer reduziert, anstatt mehrere Layer für die einzelnen Schritte des Build-Prozesses zu verwenden.
Das Beispiel beinhaltet es nicht aber falls euer Container z.B. Entrypoints oder Labels haben, müssen diese im letzten Build-Stage hinzugefügt werden. Klingt logisch, aber es soll Menschen geben, die beim ersten mal darüber gestolpert sind 🙈