dockerdockerfilemulti-moduledocker-multi-stage-build

How to organize multi-stage Dockerfiles for multi-module microservices with common modules


I have a gRPC microservices project with following structure:

- common (common protobuf definitions)
- microservices
  - ms1
    ..
  - msN

Now I want to add multi stage Dockerfiles for each microservice. The problem is that I have this common module which I need to build the rest of the projects. I can't reference the sources outside the microservice project in Dockerfile.

So the only posibility I see is to have one Dockerfile in the root folder that builds all the images:

FROM maven:3.8.6-eclipse-temurin-17 AS builder
COPY ./ /usr/app
RUN mvn -f /usr/app/pom.xml clean package


FROM eclipse-temurin:17-jre-alpine
COPY --from=builder /usr/app/microservices/ms1/target/ms1-1.0-SNAPSHOT.jar /usr/app/ms1-1.0-SNAPSHOT.jar
ENTRYPOINT ["java", "-jar", "/usr/app/ms1-1.0-SNAPSHOT.jar"]

But still I have to build all the project in builder image. One other option I see is to create separate Docker images for builder and then referencing it inside of the microservice Dockerfile by tag. But how can I trigger rebuild for builder image when building microservice image.

Are there any other options? Which one should I use?


Solution

  • We can use a multistage dockerfile with arguments (docs.docker.com) and maven's --also-make command line option (maven.apache.org):

    FROM maven:3.8.6-eclipse-temurin-17 AS builder
    ARG RELATIVE_PATH_TO_ADAPTER_MODULE # e.g. service/ms1
    COPY ./ /usr/app
    
    # specify the specific project to build via "-pl", we could also use "-f /usr/app/${PATH_TO_ADAPTER_MODULE}/pom.xml"
    # we specify --also-make to also build the dependencies
    RUN mvn -pl /usr/app/${PATH_TO_ADAPTER_MODULE} --also-make clean package
    
    FROM eclipse-temurin:17-jre-alpine
    ARG RELATIVE_PATH_TO_ADAPTER_MODULE # e.g. service/ms1
    COPY --from=builder /usr/app/${RELATIVE_PATH_TO_ADAPTER_MODULE}/target/*.jar /usr/app/service.jar
    ENTRYPOINT ["java", "-jar", "/usr/app/service.jar"]
    

    Should the target-directory contains more than one .jar-file, we can exclude all unwanted .jar-files through a .dockerignore-file (docs.docker.com), e.g. in the module's root directory.

    We can then build a particular microservice by calling docker build with --build-arg (docs.docker.com)

    docker build --build-arg RELATIVE_PATH_TO_ADAPTER_MODULE=services/ms1 --tag services/ms1 .
    docker build --build-arg RELATIVE_PATH_TO_ADAPTER_MODULE=services/ms2 --tag services/m22 .
    ...
    

    Notice, however, that each build has still to re-build the dependency modules; they are not shared across builds. Also, each build has to re-download all maven dependencies. This article at baeldung.com shows how to use a host directory as maven cache.