Maven docker缓存依赖项

时间:2017-02-13 16:06:04

标签: java maven docker dockerfile

我试图使用docker自动化maven构建。我想要构建的项目花了近20分钟来下载所有依赖项,所以我尝试构建一个可以缓存这些依赖项的docker镜像,但它似乎并没有保存它。我的Dockerfile是

FROM maven:alpine
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
ADD pom.xml /usr/src/app
RUN mvn dependency:go-offline

图像构建,它确实下载了所有内容。但是,生成的图像与基本maven:alpine图像的大小相同,因此它似乎没有缓存图像中的依赖项。当我尝试将图像用于mvn compile时,它会经历整整20分钟的重新加载。

是否可以构建一个缓存我的依赖项的maven图像,以便每次使用图像执行构建时都不必下载?

我正在运行以下命令:

docker build -t my-maven .

docker run -it --rm --name my-maven-project -v "$PWD":/usr/src/mymaven -w /usr/src/mymaven my-maven mvn compile

我的理解是,在docker构建过程中RUN所做的任何事情都会成为结果图像的一部分。

13 个答案:

答案 0 :(得分:22)

通常,pom.xml文件没有变化,但是当您尝试启动docker图像构建时,只会更改其他一些源代码。在这种情况下,您可以这样做:

供参考:

FROM maven:3-jdk-8

ENV HOME=/usr/src/app

RUN mkdir -p $HOME

WORKDIR $HOME

# 1, add pom.xml only here, 2, and start downloading dependencies

ADD pom.xml $HOME

RUN ["/usr/local/bin/mvn-entrypoint.sh", "mvn", "verify", "clean", "--fail-never"]

# 3, now we can add all source code and start compiling

ADD . $HOME

RUN ["mvn", "package"]

EXPOSE 8888

CMD ["java", "-jar", "./target/dist.jar"]

所以关键是:

  1. 添加pom.xml文件。

  2. 然后mvn verify --fail-never它会下载maven依赖项。

  3. 然后添加所有源文件,并开始编译(mvn package)。

  4. 如果pom.xml发生了更改,或者您是第一次运行此脚本,则docker将执行1 - > 2 - > 3.当pom.xml文件没有变化时,docker将跳过步骤1,2并直接执行3.

    这个简单的技巧可用于许多其他包管理环境。

答案 1 :(得分:7)

事实证明,我用作基础的图像具有定义

的父图像
VOLUME "$USER_HOME_DIR/.m2"

请参阅:https://github.com/carlossg/docker-maven/blob/322d0dff5d0531ccaf47bf49338cb3e294fd66c8/jdk-8/Dockerfile

结果是在构建期间,所有文件都写入$USER_HOME_DIR/.m2,但由于它应该是卷,因此这些文件都不会与容器映像一起保留。

目前在Docker中没有任何方法可以取消注册该卷定义,因此有必要构建一个单独的maven图像,而不是使用官方的maven图像。

答案 2 :(得分:4)

@Kim是最接近的,但它还没有完全存在。我不认为添加--fail-never是正确的,即使通过它完成了工作。

verify命令导致很多插件执行,这是一个问题(对我来说) - 当我想要的只是安装依赖项时,我不认为它们应该执行!我还有一个多模块构建和一个javascript子构建,因此这进一步使设置变得复杂。

但仅运行verify是不够的,因为如果您在以下命令中运行install,则会使用更多插件 - 这意味着需要更多依赖项 - maven拒绝下载它们。相关阅读:Maven: Introduction to the Build Lifecycle

你基本上必须找到哪些属性禁用每个插件并逐个添加它们,这样它们就不会破坏你的构建。

WORKDIR /srv

# cache Maven dependencies
ADD cli/pom.xml /srv/cli/
ADD core/pom.xml /srv/core/
ADD parent/pom.xml /srv/parent/
ADD rest-api/pom.xml /srv/rest-api/
ADD web-admin/pom.xml /srv/web-admin/
ADD pom.xml /srv/
RUN mvn -B clean install -DskipTests -Dcheckstyle.skip -Dasciidoctor.skip -Djacoco.skip -Dmaven.gitcommitid.skip -Dspring-boot.repackage.skip -Dmaven.exec.skip=true -Dmaven.install.skip -Dmaven.resources.skip

# cache YARN dependencies
ADD ./web-admin/package.json ./web-admin/yarn.lock /srv/web-admin/
RUN yarn --non-interactive --frozen-lockfile --no-progress --cwd /srv/web-admin install

# build the project
ADD . /srv
RUN mvn -B clean install

但有些插件并不容易被忽略 - 我不是专家(所以我不知道为什么它会忽略cli选项 - 这可能是一个错误),但以下工作正如预期org.codehaus.mojo:exec-maven-plugin

<project>
    <properties>
        <maven.exec.skip>false</maven.exec.skip>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.3.2</version>
                <executions>
                    <execution>
                        <id>yarn install</id>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                        <phase>initialize</phase>
                        <configuration>
                            <executable>yarn</executable>
                            <arguments>
                                <argument>install</argument>
                            </arguments>
                            <skip>${maven.exec.skip}</skip>
                        </configuration>
                    </execution>
                    <execution>
                        <id>yarn run build</id>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                        <phase>compile</phase>
                        <configuration>
                            <executable>yarn</executable>
                            <arguments>
                                <argument>run</argument>
                                <argument>build</argument>
                            </arguments>
                            <skip>${maven.exec.skip}</skip>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

请注意明确的<skip>${maven.exec.skip}</skip> - 其他插件从cli params中选择这个但不是这个(-Dmaven.exec.skip=true-Dexec.skip=true都不能自己工作)

希望这有帮助

答案 3 :(得分:2)

与@Kim答案类似,但是我使用dependency:resolve mvn命令。这是我完整的Dockerfile:

FROM maven:3.5.0-jdk-8-alpine

WORKDIR /usr/src/app

# First copy only the pom file. This is the file with less change
COPY ./pom.xml .

# Download the package and make it cached in docker image
RUN mvn -B -f ./pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency:resolve

# Copy the actual code
COPY ./ .

# Then build the code
RUN mvn -B -f ./pom.xml -s /usr/share/maven/ref/settings-docker.xml package

# The rest is same as usual
EXPOSE 8888

CMD ["java", "-jar", "./target/YOUR-APP.jar"]

答案 4 :(得分:0)

我刚才有这个问题。 Web上有很多解决方案,但对我有用的只是为maven模块目录安装一个卷:

mkdir /opt/myvolumes/m2

然后在Dockerfile中:

...
VOLUME /opt/myvolumes/m2:/root/.m2
...

有更好的解决方案,但不是那么简单。

此博客文章在帮助您缓存所有内容方面付出了额外的努力:

https://keyholesoftware.com/2015/01/05/caching-for-maven-docker-builds/

答案 5 :(得分:0)

我认为这里的其他答案不是最佳的。例如,mvn verify答案执行以下阶段,并且不仅解决依赖关系而做很多工作:

  

验证-验证项目是否正确并且所有必要的信息均可用

     

compile-编译项目的源代码

     

test-使用合适的单元测试框架测试已编译的源代码。这些测试不应要求将代码打包或部署

     

package-将编译后的代码打包并以可分发的格式打包,例如JAR。

     

验证-对集成测试的结果进行任何检查,以确保符合质量标准

如果您只想解决依赖关系,则无需运行所有这些阶段及其相关目标。

如果您只想解决依赖关系,则可以使用dependency:go-offline目标:

FROM maven:3-jdk-12
WORKDIR /tmp/example/

COPY pom.xml .
RUN mvn dependency:go-offline

COPY src/ src/
RUN mvn package

答案 6 :(得分:0)

经过几天的努力,后来我设法使用中间约束器进行了此缓存,由于这个主题非常有用并且经常出现在Google搜索首页上,我想在这里总结我的发现:

  1. Kim的答案仅在特定条件下有效:pom.xml无法更改,并且Maven默认每天进行定期更新
  2. mvn依赖项:go-offline -B --fail-never也有类似的缺点,因此,如果您需要从回购中提取新鲜代码,则Maven每次触发一次完整结帐的可能性很大
  3. 安装卷无法正常工作,因为我们需要在构建映像期间解决相关问题
  4. 最后,我合并了一个可行的解决方案(可能对其他人无效):
    • 构建图像以首先解决所有依赖性(不是中间图像)
    • 使用中间映像创建另一个Dockerfile,像这样的dockerfile示例:
#docker build -t dependencies .
From ubuntu
COPY pom.xml pom.xml
RUN mvn dependency:go-offline -B --fail-never
From dependencies as intermediate

From tomcat
RUN git pull repo.git (whatsoever)
RUN mvn package

这个想法是将所有依赖项保留在Maven可以立即使用的不同映像中

这可能是我还没有遇到过的其他情况,但是此解决方案使我每次下载3GB垃圾时都感到有些轻松 我无法想象为什么Java在当今的精益世界中变得如此胖鲸

答案 7 :(得分:0)

有两种方法可以缓存Maven依赖项:

  1. 在容器执行过程中执行“ mvn verify”,而不是进行构建,并确保从卷中装入.m2。

    这是有效的方法,但是它不适用于云构建和多个构建从属

  2. 使用“依赖项缓存容器”,并定期对其进行更新。方法如下:

    a。创建一个可复制pom并构建脱机依赖关系的Dockerfile:

    FROM maven:3.5.3-jdk-8-alpine
    WORKDIR /build
    COPY pom.xml .
    RUN mvn dependency:go-offline
    

    b。定期(例如每晚)将其构建为“ Deps:latest”

    c。创建另一个Dockerfile来根据每次提交实际构建系统(最好使用多阶段)-并确保它是FROM Deps。

使用该系统,您将获得快速且可重构的构建,并且缓存足够好。

答案 8 :(得分:0)

使用BuildKit

Docker v18.03开始,您可以使用BuildKit代替其他答案中提到的卷。它允许装入缓存,这些缓存可以在构建之间保留,并且可以避免每次都下载相应的.m2/repository的内容。

假设Dockerfile位于项目的根目录中:

# syntax = docker/dockerfile:1.0-experimental

FROM maven:3.6.0-jdk-11-slim AS build
COPY . /home/build
RUN mkdir /home/.m2
WORKDIR /home/.m2
USER root
RUN --mount=type=cache,target=/root/.m2 mvn -f /home/build/pom.xml clean compile

target=/root/.m2将缓存安装到Maven映像Dockerfile docs中的指定位置。

要构建,您可以运行以下命令:

DOCKER_BUILDKIT=1 docker build --rm --no-cache  .   

可以在here上找到有关BuildKit的更多信息。

答案 9 :(得分:0)

我认为其他答案中提出的总体游戏计划是正确的主意:

  1. 复制pom.xml
  2. 获取依赖项
  3. 复制源
  4. 构建

但是,真正的关键是您执行步骤2的方式。对我来说,使用与构建依赖项时使用的相同命令是正确的解决方案:

O(m + n)

任何其他用于获取依赖项的命令都会导致在构建步骤中需要下载许多内容。合理地运行您计划在运行的确切命令,将使您最接近实际运行该命令所需的一切。

答案 10 :(得分:0)

在Docker中运行并充当本地代理的本地Nexus 3映像是可以接受的解决方案:

这个想法类似于Dockerize apt-cacher-ng服务apt-cacher-ng

在这里您可以找到详细的逐步说明。 github repo

它真的很快。

答案 11 :(得分:0)

另一种解决方案是使用存储库管理器,例如 Sonar Nexus 或 Artifactory。您可以在注册表中设置一个 Maven 代理,然后将注册表用作您的 Maven 存储库来源。

答案 12 :(得分:-1)

如果在容器已启动后下载依赖项,则需要在此容器上提交更改并使用下载的工件创建新映像。