As of v17.04, Dockerfiles now support multi-stage builds.

This is great as it means it’s no longer necessary to cram everything into a small number of layers to minimise image size.

Take this example that builds and runs a jar:

FROM java/8
COPY ./app /app
RUN apt-get update && \
    apt-get install -y -q gradle && \
    gradle -b /app/build.gradle assemble && \
    cp /app/build/app.jar /opt && \
    rm -rf /app && \
    apt-get purge -y gradle && \
    rm -rf /var/caches/apt
CMD ["java", "-jar", "/opt/app.jar"]

Four layers, and a big ugly RUN statement that might as well be a separate shell script.

This can now be rewritten as:

FROM java/8 as build
RUN apt-get update && apt-get install -y -q gradle
COPY ./app /app
RUN gradle -b /app/build.gradle assemble

FROM java/8 as run
COPY --from=build /app/build/app.jar /opt/app.jar
CMD ["java", "-jar", "/opt/app.jar"]

More layers in total, but only three in the run image.

Notice also that we now get cache layers for each build step, so if the gradle build fails because of a compilation error, the next build doesn’t need to go through the whole apt-get installation again.

We can also do away with housekeeping tasks like removing source files and build dependencies, because the entire build container can be discarded when finished with.