adam bien's blog

Multi-Stage Docker Builds Are An Antipattern In Most Java Projects 📎

Docker's building best practices recommend multi-stage builds. The first stage compiles the source, the final stage copies the result into a minimal runtime image. That is useful when Docker is the build environment. In a Maven or Gradle centric organization with an existing CI/CD pipeline, Docker is not the build environment.

The pipeline already compiles, tests, and versions the artifact in a controlled, isolated environment. A multi-stage build compiles it a second time, with a different JDK, a different local repository, and a different cache. The image no longer contains the artifact that passed the tests. It contains a rebuild that happens to share the same commit.

The single-stage alternative packages what the pipeline produced:

FROM eclipse-temurin:25-jre-alpine
COPY target/application.jar /opt/application.jar
CMD ["java", "-jar", "/opt/application.jar"]

Ranked by importance:

  1. Build once and promote the exact artifact.
  2. Use a controlled, isolated, reproducible CI build environment.
  3. Keep the runtime image minimal.
  4. Use multi-stage Docker builds when Docker is the chosen build environment.

Multi-stage is a technique, not an architectural best practice. Making multi-stage Dockerfiles the rule obscures the more important one: the build tool and the pipeline own the software build, Docker only packages its output.