如何从一个镜像生成Dockerfile?

398

是否可以从镜像生成Dockerfile?有两个原因想知道:

  1. 我可以从仓库下载镜像,但想看到生成它们的配方。

  2. 我喜欢保存快照的想法,但一旦完成,有一个结构化的格式来审查所做的事情会很不错。


1
你可以使用Portainer.io https://portainer.io/。它是一个运行在Docker容器内的Web应用程序,用于管理所有(几乎所有)与容器相关的内容,甚至包括镜像配方。 - Vincent
11个回答

259
如何从镜像生成或反向生成Dockerfile?
大多数情况下可以。
注意:它不会生成一个你可以直接用于docker build的Dockerfile;输出仅供参考。
alias dfimage="docker run -v /var/run/docker.sock:/var/run/docker.sock --rm alpine/dfimage"
dfimage -sV=1.36 nginx:latest

它将自动拉取目标 Docker 镜像并导出 Dockerfile。参数 -sV=1.36 不总是必需的。
参考链接:https://hub.docker.com/r/alpine/dfimage 现在 hub.docker.com 直接显示图像层以及详细命令,如果您选择特定的标签。

enter image description here

奖金

如果你想知道每个层次中有哪些文件被更改了

alias dive="docker run -ti --rm  -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive"
dive nginx:latest

enter image description here

在左边,你可以看到每个层级的命令,在右边(用Tab键跳转),黄线表示该层级中有一些文件发生了变化的文件夹。
(使用空格键折叠目录)

旧答案

下面是旧答案,它已经不再适用。
$ docker pull centurylink/dockerfile-from-image
$ alias dfimage="docker run -v /var/run/docker.sock:/var/run/docker.sock --rm centurylink/dockerfile-from-image"
$ dfimage --help
Usage: dockerfile-from-image.rb [options] <image_id>
    -f, --full-tree                  Generate Dockerfile for all parent layers
    -h, --help                       Show this message

4
@jenson,这并不完全相同,可以覆盖95%。但是不能用于压缩过的图像。 - BMW
5
@BMW,您能帮忙解决一下从您的示例中运行镜像时出现的问题吗?/usr/lib/ruby/gems/2.2.0/gems/excon-0.45.4/lib/excon/unix_socket.rb:14:in `connect_nonblock':连接被拒绝 - connect(2) for /var/run/docker.sock (Errno::ECONNREFUSED)(Excon::Errors::SocketError) - long
11
"centurylink/dockerfile-from-image" 无法与新版本的 Docker 兼容。这个可以用:https://hub.docker.com/r/chenzj/dfimage/ - aleung
6
imagelayers.io似乎出了问题。它无法找到任何镜像,包括其演示镜像。 - Robert Lugg
2
它给了我一些输出,但没有显示我感兴趣的“FROM”部分。我错过了什么还是这不能完成? - xbmono
显示剩余8条评论

190

要了解一个 Docker 镜像是如何构建的,可以使用 docker history --no-trunc 命令。

你可以从一个镜像文件构建一个 Docker 文件,但它并不包含您完全了解图像生成方式所需的全部内容。您可以提取的是 Dockerfile 的 MAINTAINER、ENV、EXPOSE、VOLUME、WORKDIR、ENTRYPOINT、CMD 和 ONBUILD 部分。

以下脚本应该适合您:

#!/bin/bash
docker history --no-trunc "$1" | \
sed -n -e 's,.*/bin/sh -c #(nop) \(MAINTAINER .*[^ ]\) *0 B,\1,p' | \
head -1
docker inspect --format='{{range $e := .Config.Env}}
ENV {{$e}}
{{end}}{{range $e,$v := .Config.ExposedPorts}}
EXPOSE {{$e}}
{{end}}{{range $e,$v := .Config.Volumes}}
VOLUME {{$e}}
{{end}}{{with .Config.User}}USER {{.}}{{end}}
{{with .Config.WorkingDir}}WORKDIR {{.}}{{end}}
{{with .Config.Entrypoint}}ENTRYPOINT {{json .}}{{end}}
{{with .Config.Cmd}}CMD {{json .}}{{end}}
{{with .Config.OnBuild}}ONBUILD {{json .}}{{end}}' "$1"

我将其用作重建运行容器为镜像的脚本的一部分: https://github.com/docbill/docker-scripts/blob/master/docker-rebase

Dockerfile主要有用的是,如果您想重新打包一个镜像。

需要记住的是,Docker镜像实际上可以只是真实或虚拟机器的tar备份。我已经用这种方式制作了几个Docker镜像。即使构建历史记录显示我导入一个巨大的tar文件作为创建镜像的第一步...


1
它让我感到困惑:json:无法将数组解组为类型为types.ContainerJSON的Go值。 - Mohsen
你能更详细地描述一下你最后的评论吗?所有东西都像正常情况下那样被压缩成tar文件了吗?还是有特殊情况? - Robert Lugg
我认为这是一个六年前的答案,但我收到了“守护程序错误响应:页面未找到”的错误。 - João Ciocca

101

我有些绝对错过了被接受答案中的实际命令,因此在此再次列出它,更加突出地显示在自己的段落中,看看像我这样的人有多少

$ docker history --no-trunc <IMAGE_ID>

1
那么我们为什么需要 ub.docker.com/r/chenzj/dfimage?这甚至是一个更近期的答案。 - lucid_dreamer
3
我猜是因为 docker history 命令以相反的顺序打印 Dockerfile 的行,并且会删除 RUN 指令(你只能得到命令本身,没有前面的 RUN 关键字)和其他东西,所以你需要手动编辑才能得到可构建的 Dockerfile。那个其他工具可能可以自动为您完成此编辑(我没有尝试过,所以不确定)。 - user7610
1
@user7610,您的命令只显示了从 hub 拉取的镜像历史记录。我该如何查看docker镜像上的命令? - BarzanHayati
2
@user7610 我可以问它,但是一旦我问了,我必须删除它,因为其他用户会给我负分,认为这是重复的问题。 - BarzanHayati
@user7610 我会检查它。 - BarzanHayati
显示剩余3条评论

85
一个bash解决方案:
docker history --no-trunc $argv | tac | tr -s ' ' | cut -d " " -f 5- | sed 's,^/bin/sh -c #(nop) ,,g' | sed 's,^/bin/sh -c,RUN,g' | sed 's, && ,\n  & ,g' | sed 's,\s*[0-9]*[\.]*[0-9]*\s*[kMG]*B\s*$,,g' | head -n -1

逐步解释:
tac : reverse the file
tr -s ' '                                       trim multiple whitespaces into 1
cut -d " " -f 5-                                remove the first fields (until X months/years ago)
sed 's,^/bin/sh -c #(nop) ,,g'                  remove /bin/sh calls for ENV,LABEL...
sed 's,^/bin/sh -c,RUN,g'                       remove /bin/sh calls for RUN
sed 's, && ,\n  & ,g'                           pretty print multi command lines following Docker best practices
sed 's,\s*[0-9]*[\.]*[0-9]*\s*[kMG]*B\s*$,,g'   remove layer size information
head -n -1                                      remove last line ("SIZE COMMENT" in this case)

例子:

 ~  dih ubuntu:18.04
ADD file:28c0771e44ff530dba3f237024acc38e8ec9293d60f0e44c8c78536c12f13a0b in /
RUN set -xe
   &&  echo '#!/bin/sh' > /usr/sbin/policy-rc.d
   &&  echo 'exit 101' >> /usr/sbin/policy-rc.d
   &&  chmod +x /usr/sbin/policy-rc.d
   &&  dpkg-divert --local --rename --add /sbin/initctl
   &&  cp -a /usr/sbin/policy-rc.d /sbin/initctl
   &&  sed -i 's/^exit.*/exit 0/' /sbin/initctl
   &&  echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup
   &&  echo 'DPkg::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' > /etc/apt/apt.conf.d/docker-clean
   &&  echo 'APT::Update::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' >> /etc/apt/apt.conf.d/docker-clean
   &&  echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' >> /etc/apt/apt.conf.d/docker-clean
   &&  echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/docker-no-languages
   &&  echo 'Acquire::GzipIndexes "true"; Acquire::CompressionTypes::Order:: "gz";' > /etc/apt/apt.conf.d/docker-gzip-indexes
   &&  echo 'Apt::AutoRemove::SuggestsImportant "false";' > /etc/apt/apt.conf.d/docker-autoremove-suggests
RUN rm -rf /var/lib/apt/lists/*
RUN sed -i 's/^#\s*\(deb.*universe\)$/\1/g' /etc/apt/sources.list
RUN mkdir -p /run/systemd
   &&  echo 'docker' > /run/systemd/container
CMD ["/bin/bash"]

当它分解多行RUN语句时,这不会添加尾部反斜杠。我已经根据此添加了自己的答案。 - Scott Centoni
tac 在 Mac 上不可用,所以你可以使用下面的 awk 命令:| awk '{print NR,$0}' | sort -nr | sed 's/^[0-9]* //'| - phulei
我支持这个,因为它可以与 Podman 一起使用(而且系统中没有安装 Docker)。 - Kirill Taran
1
tac for the mac: tac() { awk '{ l[NR]=$0} END {for(i=NR;i>0;--i)print l[i];}' "$@";}。与phulei的解决方案相比,这种方法更加节约内存并且总的CPU时间稍微少一些。 - Otheus
2
同样在 Mac 上,将 head -n -1 替换为 sed '$p' - Otheus

20

更新2018年12月至宝马的回答

chenzj / dfimage-如hub.docker.com上所述,可以从其他映像重新生成Dockerfile。 因此,您可以按以下方式使用它:

docker pull chenzj/dfimage
alias dfimage="docker run -v /var/run/docker.sock:/var/run/docker.sock --rm chenzj/dfimage"
dfimage IMAGE_ID > Dockerfile

5
似乎不能在19.03.8版本上运行:docker: 守护进程的错误响应: OCI运行时创建失败:container_linux.go:349: 启动容器进程导致 "exec: \"cc6cb8df58e2\": 可执行文件在 $PATH 中未找到": 未知。 - Trevor Hickey
1
@TrevorHickey 我也遇到了同样的问题。你是否将 chenzj/dfimage 重命名为你要求的 Docker 镜像? - 404pio
1
这个的Git仓库在哪里? - Jason

18

如果你对Docker hub注册表中的某个镜像感兴趣,并想要查看其Dockerfile,可以按以下步骤操作:

例如:

如果你希望查看名为"jupyter/datascience-notebook"的镜像的Dockerfile,直接在浏览器地址栏中输入单词“Dockerfile”即可,如下所示。

https://hub.docker.com/r/jupyter/datascience-notebook/ enter image description here

https://hub.docker.com/r/jupyter/datascience-notebook/Dockerfile

enter image description here

注意: 并不是所有的镜像都有Dockerfile,例如https://hub.docker.com/r/redislabs/redisinsight/Dockerfile。 有时,这种方法比在Github中搜索Dockerfile更快捷。


1
超级有用的解决方案!可惜你必须阅读一堆繁琐的解决方案才能找到它(更多的赞将改变这种情况)。 - D. Woods

14
这是从@fallino的答案中提取出来的,通过在docker history中使用输出格式选项进行了一些调整和简化。由于macOS和Gnu/Linux有不同的命令行工具,因此Mac需要不同的版本。如果您只需要其中一个,您可以直接使用这些行。
#!/bin/bash
case "$OSTYPE" in
    linux*)
        docker history --no-trunc --format "{{.CreatedBy}}" $1 | # extract information from layers
        tac                                                    | # reverse the file
        sed 's,^\(|3.*\)\?/bin/\(ba\)\?sh -c,RUN,'             | # change /bin/(ba)?sh calls to RUN
        sed 's,^RUN #(nop) *,,'                                | # remove RUN #(nop) calls for ENV,LABEL...
        sed 's,  *&&  *, \\\n \&\& ,g'                           # pretty print multi command lines following Docker best practices
    ;;
    darwin*)
        docker history --no-trunc --format "{{.CreatedBy}}" $1 | # extract information from layers
        tail -r                                                | # reverse the file
        sed -E 's,^(\|3.*)?/bin/(ba)?sh -c,RUN,'               | # change /bin/(ba)?sh calls to RUN
        sed 's,^RUN #(nop) *,,'                                | # remove RUN #(nop) calls for ENV,LABEL...
        sed $'s,  *&&  *, \\\ \\\n \&\& ,g'                      # pretty print multi command lines following Docker best practices
    ;;
    *)
        echo "unknown OSTYPE: $OSTYPE"
    ;;
esac

12
docker pull chenzj/dfimage

alias dfimage="docker run -v /var/run/docker.sock:/var/run/docker.sock --rm chenzj/dfimage"

dfimage image_id

以下是dfimage命令的输出结果:
$ dfimage 0f1947a021ce

FROM node:8
WORKDIR /usr/src/app

COPY file:e76d2e84545dedbe901b7b7b0c8d2c9733baa07cc821054efec48f623e29218c in ./
RUN /bin/sh -c npm install
COPY dir:a89a4894689a38cbf3895fdc0870878272bb9e09268149a87a6974a274b2184a in .

EXPOSE 8080
CMD ["npm" "start"]

12

目前尚不可能(除非图片的作者明确包含了 Dockerfile 文件)。

然而,这肯定是有用的!有两件事将有助于获得此功能。

  1. 可信构建(在此 docker-dev 讨论中详细说明)
  2. 生成的后续图像中有更详细的元数据。从长远来看,元数据应指示哪个构建命令生成了该图像,这意味着可以从一系列图像重构出 Dockerfile。

0
只需两个步骤就可以实现。首先拉取镜像,然后运行docker history命令。同时,在屏幕截图中也有显示。
docker pull kalilinux/kali-rolling
docker history --format "{{.CreatedBy}}" kalilinux/kali-rolling --no-trunc

enter image description here


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