简单 Dockerfile 示例
FROM ubuntu:14.04 MAINTAINER James Turnbull "jam@example.com" RUN apt-get update && apt-get install -y nginx RUN echo "Hi, I am in your container. " > /usr/share/nginx/html/index.html EXPOSE 80
Dockerfile 由指令和参数组成,每个指令为大写字母,后跟参数,按照从上到下的顺序执行;
每个指令都会创建一个新的镜像层,并进行提交。Dockerfile 的执行流程如下:
1)从基础镜像(使用 FROM 指定)运行一个容器;
2)执行 Dockerfile 中的一条指令,对容器进行修改;
3)在指令结束后,执行提交动作,提交一个新的镜像层;
4)基于刚才提交的镜像层,运行一个新的容器;
5)回到 2)直到所有指令执行完成;
如果 Dockerfile 执行失败了,我们也会得到一个可以的镜像,然后可以在这个镜像的基础上进行调试;
默认情况下,命令在 /bin/sh -c 中执行,如果在不支持 shell 的平台上、或希望使用 exec 执行,则可以使用 RUN 命令的另一种格式:
RUN [ "apt-get", "install", "-y", "nginx" ]
使用数组来传递参数;
指令 EXPOSE 用于告诉 Docker 该”容器内的应用程序将会使用哪些端口“,但这并不意味着可以自动访问任意容器运行中服务的端口(这里是 80)。出于安全原因,需要用户使用 docker run 运行容器时来指定打开的端口;
常用 Dockerfile 指令
Dockerfile reference
Dockerfile COPY vs ADD: key differences and best practices
CMD
Dockerfile reference | Docker Documentation / CMD
指定容器启动时运行的命令,和 docker run 命令非常相似。但是,在 docker run 中指定的命令会覆盖 CMD 指定的命令;
该指令有三种形式:
1)CMD [“executable”,”param1″,”param2″] (exec form, this is the preferred form)
2)CMD [“param1″,”param2”] (as default parameters to ENTRYPOINT)
3)CMD command param1 param2 (shell form)
该指令可以使用多次,但是只有最后一个会生效
例如CMD ["/bin/bash"],或者传递参数CMD ["/bin/bash", "-l"]
最好通过数组的形式传递命令和参数。如果直接指定命令,则 Docker 会在命令前添加 /bin/sh -c 来执行命令,容易产生意料之外的行为;
ENTRYPOINT
Dockerfile reference | Docker Documentation / ENTRYPOINT
# The exec form ENTRYPOINT ["executable", "param1", "param2"] # The shell form ENTRYPOINT command param1 param2
该命令与 RUN 命令非常相似,但是该命令不会被 docker run 所覆盖,而在 docekr run 中指定的命令,会被当作参数传递给 ENTRYPOINT 指定的命令。除此之外,二者的语法、数组形式是一样的;
能够将 CMD 与 ENTRYPOINT 组合使用:
ENTRYPINT ["/usr/sbin/nginx"] CMD ["-h"]
当执行 docker run 时,如果未指定命令,则容器启动时执行 /usr/sbin/nignx -h 命令。如果指定了命令,如 -g “daemon off;”,则会覆盖 CMD 指令,执行指定的命令,这里是 /usr/sbin/nginx -h 命令;
如果要覆盖 ENTRYPOINT 命令,需要使用 –entrypoint 选项;
当构建镜像时,Only the last ENTRYPOINT instruction in the Dockerfile will have an effect. 如果未指定 Entrypoint 指令,则容器会保留基础镜像(FROM)中的 Entrypoint 配置;
RUN
Best practices for writing Dockerfiles | Docker Documentation
当使用 apt-get 指令时,官方最佳实践建议采用如下形式:
RUN apt-get update && apt-get install -y \ package-bar \ package-baz \ && rm -rf /var/lib/apt/lists/*
WORKDIR
切换工作目录。在 Docker 中切换到指定工作目录,然后执行命令。使用-w选项覆盖工作目录;
ENV
在构建过程中定义环境变量。从 Docker 1.4 开始,一条指令可以同时定义多个环境变量;
ENV RVM_PATH=/home/rvm RVM_ARCHFLAGS="-arch i386"
在其他指令中,可以直接使用这些环境变量:WORKDIR $RVM_PATH
如果有必要可以使用反斜线进行转义;
这些变量会保存到容器中,即在容器中执行 env 时,可以看到这些环境变量;
也可以通过 docker run -e 选项传递变量,但是这些变量只会在运行时有效;
USER
指定以什么用户去运行。可以使 UID 和 GID 的组合:
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:gorup
比如:USER nginx
可以使用 docker run -u 来覆盖。如果未设置 USER 指令,则默认为 root 用户;
6.VOLUME
向基于镜像创建的容器中添加卷。卷可以存在于一个或多个容器内的特定的目录。该目录可以绕过联合文件系统,并提供向下共享数据、数据持久化;
一个容器可以不是必须和其他容器共享卷;
对卷的修改立即生效;
对卷的修改不会对更新镜像产生影响;
卷会一个存在知道没有任何容器再使用它;
通过该功能可以将数据添加到镜像,而不是提交到镜像,并且可以在多个容器之间共享这些内容。可以通过该功能测试容器、内部程序源码,管理日志等等;
例如:VOLUME ["/opt/project", "/data"]
「docker cp | Docker Documentation」「Use volumes | Docker Documentation」
7.ADD
将文件复制到镜像中,文件只能位于构建上下文中;
例如:ADD foo.txt /opt/application/foo.txt
「源文件」文件、目录、URL。另外,如果「源文件」是归档文件(gzip, bzip2, xz)则会自动解压目的目录中;解压时,如果目的文件已存在,则不会复制,即不会覆盖。另外,从 URL 中读取的归档文件不会自动解压;
Docker 通过「地址参数末尾的字符」来判断文件是「目录」还是「文件」;
如果目的目录不存在,Docker 会递归创建。新文件和目录的权限默认为 0755,UID=0,GID=0;
通过 ADD 会使构建缓存无效,其后的指令不会使用构建缓存;
COPY
在构建上下文中,复制其内的文件和目录到容器中,与 ADD 不同的是,COPY 不会做额外的操作;
文件或目录的 UID=0,GID=0;文件和目录的元数据也会被复制到容器中的文件和目录上。同样,如果目录不存在,Docker 会递归创建;
Q:复制文件中的内容到镜像中,而非目录本身?
A:COPY downloads/ /tmp/downloads/,That will copy the contents of the downloads directory into a directory called /tmp/downloads/ in the image.
R:ADD or COPY a folder in Docker
9.LABEL
用于添加元数据,已键值对的形式存在:
LABEL lacation=”Somewhere” type=”Data Center” role=”Web Server”
一条指令中可以存在多个元数据,键值对形式,元数据间以空格分隔。推荐多条元数据放在一个 LABEL 指令中,以防止创建过多的镜像层。通过 docker inspect 来查看元数据;
10.STOPSIGNAL
指定 docker stop 时发送给容器的信号。这个信号必须是内核中合法的信号值,例如 9,或者信号名 SIGNAME,例如 SIGKILL;
11.ARG
在 Docker 1.9 中引入该指令,该指令指定在 docker build 命令运行时,传递给构建运行时的变量,只需要在构建时指定–build-arg 标志即可。用户只能在构建时指定定义过的参数;
ARG webapp_user=root
上面的示例中,第二条 ARG 设置了默认值 root。如果未使用–build-arg 指定该参数,则使用默认值;
例如,docker build --build-arg build=1234 -t jamti/webapp .,设置 build 参数的值为 1234,而 webapp_user 将使用默认值 root;
!!!不要使用该功能传递密钥等类型的参数;
在 Docker 中预定义了一组 ARG 变量,可以在构建时直接使用,无需再定义一次。「Predefined ARGs」
12.ONBUILD
为镜像添加触发器。当该镜像会用做其他镜像的基础镜像时,该镜像中触发器会被执行;
触发器在构建过程中执行当初添加的指令,可以将其视为实在 FROM 之后执行的
ONBUILD RUN cd /app/src && make
在构建镜像时,这些触发器会被加入到镜像中。使用docker inspect来查看加入镜像中的触发器;
通过这种方式可以为应用程序进行一些特定的配置和设置构建信息;
!!!触发器只能被继承一次!!!有些命令不能放在触发器中,这是为了防止产生递归调用;
性质
多阶段构建 | Multi-stage builds
https://docs.docker.com/build/building/multi-stage/
FROM golang:1.23 AS build WORKDIR /src COPY <<EOF /src/main.go package main EOF RUN go build -o /bin/hello ./main.go FROM scratch COPY --from=build /bin/hello /bin/hello CMD ["/bin/hello"]
应用
场景 | 编写 Dockerfile 最佳实践
Best practices for writing Dockerfiles
通过 wget 下载,需要注意如下事项:
- 合并 RUN 指令:
- 通过 && 将多个命令合并为一个 RUN 指令,减少镜像层数;
- 用 \ 换行提高可读性;
- 清理无用文件:
- 删除下载的临时文件,例如 rm -f xxxxxxx.tar.xz
- 使用 Alpine 的 –virtual .deps 标记临时依赖,安装后通过 apk del .deps 删除
RUN wget -O node.tar.xz "https://nodejs.org/dist/v20.12.2/SHASUMS256.txt" && \ grep "node-v20.12.2-linux-x64.tar.xz" SHASUMS256.txt | sha256sum -c - && \ wget -O node.tar.xz "https://nodejs.org/dist/v20.12.2/node-v20.12.2-linux-x64.tar.xz" RUN apk add --no-cache --virtual .deps wget tar && \ wget ... && \ apk del .deps
场景 | 从镜像中还原 Dockerfile 文件
How to generate a Dockerfile from an image?
从镜像中生成 Dockerfile 文件:
#!/bin/sh 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
存在局限性;
场景 | 解析 Dockerfile 文件
docker-java-parser – dockerfile 解析类库(Java)
GitHub/ThStock/docker-java-parser
Home » com.github.thstock » docker-java-parser
Dockerfile df = Dockerfile.parse(new File("Dockerfile")); df.getFrom(); // alpine:3.7 df.getLabels(); // {maintainer=ThStock@example.org} df.getEnv(); // {VERSION=9}