Docker:在Dockerfile中暂时复制文件,但不包含在最终镜像中

25

我有一个Java服务需要打包,并且最终的Docker镜像只需要JAR文件和配置文件。但是,我需要先运行我的gradle命令来构建JAR文件,但我不希望所有gradle使用的内容都包含在最终的Docker镜像中。

这是我当前的DockerFile:

RUN apt-get update && apt-get install -y openjdk-7-jdk
COPY . /
RUN ./gradlew shadowJar
CMD ["java", "-jar", "service/build/libs/service.jar", "server", "service/service.yml"]

你可以看到我必须先COPY所有内容,这样才能运行./gradlew(否则会显示找不到命令)。但最终我只需要service.jarservice.yml文件。

我可能遗漏了一些东西,但我如何在./gradlew构建步骤期间使所有内容都可用,但只有结果镜像包含service.jarservice.yml呢?

6个回答

39

Docker引入了所谓的“多阶段构建”。使用它们,您可以避免在映像中拥有不必要的文件。您的Dockerfile将如下所示:

FROM ubuntu AS build
COPY . /src
# run your build

FROM ubuntu
RUN mkdir -p /dist/
COPY --from=build /src/my.object /dist/

原则很简单。在第一个 FROM 中,您为构建命名。在第二个 FROM 中,您可以使用 COPY --from= 参数将文件从第一个构建复制到第二个构建。第二个构建是稍后生成可用映像的构建。

如果您想测试构建结果而不是生成的映像,则可以使用 docker build --target build myimage:build .。生成的映像仅包括您的 Dockerfile 中第一个 FROM

尽量为您的“构建”和最终映像使用相同的基础映像。不同的基础映像可能会导致奇怪的错误甚至段错误。

了解更多信息: https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds


1
我的情况是,由于安全原因,我正在中间层下载软件包。然后将它们添加到最终镜像中。接着使用RUN命令安装这些软件包并删除它们。我的目标是在RUN命令安装这些软件包后删除已下载的软件包。有没有办法a)将ADD和RUN合并为一层;b)删除ADD层? - variable
这是一个对于评论来说比较复杂的问题。您需要编写一个小脚本来提取图像的层并将其导入到新的Dockerfile中,可以参考层提取为.tar的方法:https://dev59.com/wFkR5IYBdhLWcg3w9Rut#44030483 - Trendfischer
很遗憾,如果要从一个阶段复制文件到另一个阶段的文件分散在整个文件系统中,并且甚至可能在每次运行时都会更改,则无法使用多阶段构建。 (例如,dpkg -i 的结果。) - Torsten Bronger
@TorstenBronger 这是真的,但我不建议以这种方式复制文件。从一个阶段到另一个阶段的镜像可能以不兼容的方式不同。最好使用预期的安装脚本在一个包中,如.deb。特别是如果每次运行都有差异。 - Trendfischer
@variable 有两种方式。1. --mount=type=secret 2. 在构建时删除包并使用 --squash - Hunger

6
另一个解决方法是使用 Web 服务器来获取数据。
  1. Start a web server, serving your temporarily needed files. You may use

    python3 -m http.server 8000 --bind 127.0.0.1 &
    

    to serve the current directory, see https://docs.python.org/3/library/http.server.html#http-server-cli

  2. In your Dockerfile get the file, perform actions, delete the files in a single RUN

    RUN wget http://localhost:8000/big.tar && tar xf big.tar && ... && rm big.tar
    
  3. Build image with --network host to be able to access localhost


5
建立一个镜像的过程如下:
docker build命令将使用包含Dockerfile的目录作为构建上下文(包括所有子目录)。构建上下文会在构建镜像之前发送到Docker守护程序,这意味着如果你使用/作为源代码库,整个硬盘的内容都将被发送到守护程序...
请参阅https://docs.docker.com/reference/builder/ 我看不出实现你想要的功能的方法。有两个选项:
1. 在镜像中放置所有构建依赖项,并在容器内部构建JAR文件。这会膨胀你的镜像。
2. 我建议单独构建JAR文件,然后在构建可执行文件和配置文件时只使用ADD命令。这意味着所有构建依赖项必须在开发环境中可用,但是你的镜像尽可能小。

我明白了。我想我混淆了我的构建步骤。我应该先运行 ./gradlew shadowJar,然后再运行 docker build。我一直以为 docker build 是用来构建你的应用程序的,但它只是用来构建镜像的,你应该单独构建你的应用程序。这很有道理,谢谢。 - Sean Adkinson
2
+1 我个人喜欢有一个Makefile来方便地做这些事情。 - xh3b4sd
没有理由不将使用Gradle构建的Docker镜像与您的JAR和依赖项一起创建。这样做的好处是,Gradle已经具有一流的支持,可以向您公开完成构建所需的所有依赖项的路径。 - dty

2

0

我采用了各种评论,利用DOCKER_BUILDKIT得出此解决方案。我将somefile.tar.gz暂时复制到阶段0中,但该文件不会出现在最终的镜像中。整个容器大小缩小了。

# syntax=docker/dockerfile:experimental

# File: Dockerfile
# Build:
#     DOCKER_BUILDKIT=1 docker build -t sometag -f Dockerfile $(pwd)

# =============================================================================
# STAGE 0 ---------------------------------------------------------------------
# =============================================================================
FROM somebase as devel

COPY somefile.tar.gz /somemodules/
RUN cd /somemodules && \
    tar -xf somefile.tar.gz

# =============================================================================
# STAGE 1 ---------------------------------------------------------------------
# =============================================================================
FROM same_or_another_base

RUN apt-get update -y && \
    apt-get install -y --no-install-recommends \
        rsync && \
    rm -rf /var/lib/apt/lists/*

RUN  --mount=from=devel,src=/somemodules,dst=/somemodules \    
    rsync -I -K -a /somemodules/somefile/* /usr/local/somemodules/

RUN echo "/usr/local/somemodules/lib" >> /etc/ld.so.conf.d/somemodules.conf
ENV PATH=/usr/local/somemodules/bin:$PATH

# a bunch of other stuff . . .

RUN ldconfig

-3
你可以这样做:
FROM java:7
COPY . /sourcecode
WORKDIR /sourcecode
RUN ./gradlew shadowJar && rm -rf /sourcecode
WORKDIR /
CMD ["java", "-jar", "service/build/libs/service.jar", "server", "service/service.yml"]

这里使用了 Docker 官方的 java 镜像(请参见仓库)。

RUN 行应该创建你的 service.jar,然后在创建该层之前删除你已经 COPY 的所有源代码,这样源代码就不会成为最终镜像的一部分。我假设 gradlew 也会将其复制/安装到 /service/build/libs,否则你应该添加该步骤。


8
rm 命令无法减小镜像大小:COPY 在不同的层中。它只会删除 Gradle 生成的工作文件,但源代码仍然在下面的一层中(即使被 rm 隐藏)。 - Divide

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接