我想在github上为Linkurious项目构建一个docker映像,这需要Neo4j数据库和Node.js才能运行。

我的第一种方法是为映像声明一个包含Neo4j的基本映像。参考文档没有以任何有用的方式定义“base image”:

基地图片: 没有父映像的映像是基映像

从中我了解到,如果一个图像本身没有基像,那么我可能只有一个基像。

但是什么是基像呢?这是否意味着,如果我在FROM指令中声明neo4j/neo4j,当我的映像运行时,neo数据库将自动运行,并在端口7474的容器中可用?

阅读Docker参考,我看到:

为了创建多个映像,FROM可以在一个Dockerfile中出现多次。只需在每个新FROM命令之前记录提交输出的最后一个图像ID。

是否要创建多个图像?似乎我想要的是有一个单独的图像,其中包含其他图像的内容,如neo4j和node.js。

我在参考手册中没有发现声明依赖项的指令。是否没有像在RPM中那样的依赖关系,为了运行我的映像,调用上下文必须首先安装它需要的映像?

我困惑……


从2017年5月开始,在一个Dockerfile中可以使用多个from。 参见“Docker中的构建器模式与多阶段构建”(作者Alex Ellis)和Tõnis Tiigi的PR 31257。

一般的语法包括在Dockerfile中添加FROM,最后一个FROM语句就是最终的基映像。从中间图像复制工件和输出使用copy——from=<base_image_number>。

FROM golang:1.7.3 as builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html  
COPY app.go    .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app    .
CMD ["./app"]  

结果是两个图像,一个用于构建,一个仅用于生成应用程序(小得多)

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

multi               latest              bcbbf69a9b59        6 minutes ago       10.3MB  
golang              1.7.3               ef15416724f6        4 months ago        672MB  

什么是基像?

一组文件,加上exposed端口,ENTRYPOINT和CMD。 你可以添加文件,并基于该基本映像构建一个新的映像,使用一个以FROM指令开头的新Dockerfile: FROM之后提到的映像是新映像的“基本映像”。

这是否意味着如果我在FROM指令中声明neo4j/neo4j,当我的映像运行时,neo数据库将自动运行,并在端口7474的容器中可用?

除非你不覆盖CMD和ENTRYPOINT。 但是图像本身就足够了:如果必须为特定的neo4j使用添加与neo4j相关的文件,则可以使用FROM neo4j/neo4j。


第一个答案太复杂,太有历史意义,对我来说信息量太大。


其实很简单。Docker提供了一个叫做多阶段构建的功能,这里的基本思想是,

通过强迫你列出你想要的东西,让你不必手动删除你不想要的东西, 释放资源,否则会因为Docker的实现而被占用。

让我们从第一个开始。你会经常看到Debian这样的软件。

RUN apt-get update \ 
  && apt-get dist-upgrade \
  && apt-get install <whatever> \
  && apt-get clean

我们可以根据上面的解释来解释这一切。上面的命令是链接在一起的,因此它表示不需要中间图像的单个更改。如果是这样写的,

RUN apt-get update ;
RUN apt-get dist-upgrade;
RUN apt-get install <whatever>;
RUN apt-get clean;

这将导致3个临时的中间图像。将其缩减为一个映像后,还有一个问题:apt-get clean不能清除安装中使用的工件。如果Debian维护者在他的安装中包含了修改系统的脚本,该修改也会出现在最终的解决方案中(参见pepperflashplugin-nonfree之类的例子)。

通过使用多阶段构建,您可以获得单个更改操作的所有好处,但这将需要您手动allowlist并复制使用copy在临时映像中引入的文件—从这里记录的语法。此外,这是一个很好的解决方案,没有其他选择(如apt-get clean),否则你会有很多不需要的文件在你的最终映像。

另请参阅

多级构建 复制语法


下面我总结一下我对问题和答案的理解,希望对大家有所帮助。

假设我有三张图片,苹果,香蕉和橘子。我可以有一个Dockerfile,从苹果,从香蕉和从橙色,将告诉docker神奇地合并所有三个应用程序到一个单一的图像(包含三个独立的应用程序),我可以称之为smoothie?

答:不,你不能。如果你这样做,你最终会得到四张图片,你拉出来的三张水果图片,加上基于最后一张FROM图片的新图片。例如,如果FROM orange是Dockerfile中没有添加任何内容的最后一条语句,那么smoothie图像将只是橙色图像的克隆。

为什么它们没有合并?我真的想要

典型的docker映像包含应用程序运行所需的几乎所有内容(不包括内核),这通常意味着它们是根据所选操作系统和特定版本或发行版的基本映像构建的。

在不考虑所有可能的发行版、文件系统、库和应用程序的情况下成功合并映像,这不是Docker想要做的事情,这是可以理解的。相反,开发人员应该接受微服务范式,运行多个容器,根据需要相互通信。

还有什么选择?

映像合并的一个可能的用例是将Linux发行版与我们想要的应用程序混合和匹配,例如Ubuntu和Node.js。这不是解决方案:

FROM ubuntu
FROM node

如果我们不想坚持使用我们的应用程序映像所选择的Linux发行版,我们可以从我们所选择的发行版开始,使用包管理器来安装应用程序。

FROM ubuntu
RUN apt-get update &&\
    apt-get install package1 &&\
    apt-get install package2

但你可能已经知道了。通常情况下,所选择的发行版中没有可用的快照或包,或者它不是所需的版本,或者它在开箱即用的docker容器中不能很好地工作,这是希望使用映像的动机。我只是在确认,据我所知,唯一的选择是走长远的路,如果你真的想要遵循一个整体的方法。

以Node.js为例,你可能想手动安装最新的版本,因为apt提供了一个古老的版本,而snap没有附带Ubuntu镜像。对于neo4j,我们可能必须下载包,并根据文档和许可证手动将其添加到映像中。

如果大小无关紧要,一种策略是从最难手动安装的基本映像开始,然后将其余的映像添加到顶部。

何时使用多个FROM指令

还可以选择使用多个FROM语句,并在构建阶段之间或最终阶段中手动复制内容。换句话说,你可以手动合并图像,如果你知道你在做什么。根据文件:

可以通过添加AS名称为新的构建阶段指定一个名称 到FROM指令。该名称可用于后续的FROM和 COPY——from=<name>指令引用在此构建的映像 阶段。

就我个人而言,我只喜欢将这种合并方法用于我自己的图像或遵循应用程序供应商的文档,但如果您需要它,或者您只是觉得幸运,它就在那里。

不过,这种方法的一个更好的应用是,当我们确实想使用来自不同映像的临时容器来构建或执行某些操作,并在复制所需的输出后丢弃它时。

例子

我想要一个只有gpgv的精简映像,根据这个Unix和Linux的答案,我用yum安装了整个gpg,然后只复制所需的二进制文件,到最终的映像:

FROM docker.io/photon:latest AS builder
RUN yum install gnupg -y

FROM docker.io/photon:latest
COPY --from=builder /usr/bin/gpgv /usr/bin/
COPY --from=builder /usr/lib/libgcrypt.so.20 /usr/lib/libgpg-error.so.0 /usr/lib/

Dockerfile的其余部分照常继续。


这里可能是使用多个from(即多阶段构建)的最基本用例之一。

我想要一个dockerfile,我想要改变一个单词,根据我设置的单词,我得到不同的图像取决于我想要运行,Dev或发布应用程序!

运行-我只是想运行应用程序

Dev -我想编辑代码并运行应用程序

发布——在生产环境中运行应用程序

让我们假设我们在dotnet环境中工作。这里有一个Dockerfile。如果没有多阶段构建,就会有多个文件(构建器模式)

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/runtime:5.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY ["ConsoleApp1/ConsoleApp1.csproj", "ConsoleApp1/"]
RUN dotnet restore "ConsoleApp1/ConsoleApp1.csproj"
COPY . .
WORKDIR "/src/ConsoleApp1"
RUN dotnet build "ConsoleApp1.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "ConsoleApp1.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ConsoleApp1.dll"]

想要运行应用程序?Leave FROM base AS final,因为它目前在上面的dockerfile中。

想在容器中开发源代码?将同一行更改为FROM build AS final

想要释放到刺激?将同一行更改为FROM publish AS final


我同意OP的观点,这个特性对docker很有用!下面是对同一问题的不同看法:

If you had multiple FROMs (or a "FROM" and multiple "MERGE"'s, for example) then you can use the docker registry versioning system for the base docker image AND other container elements, and that is the win here: I have third party development tools which do not exist in .deb format, so these tools must be installed by un-taring a tball and is HUGE, so caching on the docker host will be important but versioning/change control of the image is equally important. I (think I) can simply use "RUN git ....", and docker will deal with the caching of the new layer for me, which is what I want; because another container will have the same base image but a different set of HUGE third party tools, so the caching of the base image and the tools image is really important (the 3rd party tools tar can be as big as the base image of say ubuntu so caching of these is really important too). The (suggested) feature just allows all these elements to be managed in a central repo. versioning system.

换句话说,我们为什么要用FROM呢?如果我只是简单地使用RUN命令为我的“base image/layer”克隆一个ubuntu镜像,这将创建一个新层,docker将缓存它…那么,除了使用docker内部版本系统/语法之外,使用FROM有什么不同/优势吗?