zoukankan      html  css  js  c++  java
  • Dockerfile文件万字全面解析

    Dockerfile是一个文本文件,包含一些Docker指令。执行docker build,Docker就会执行Dockerfile里面的指令,来自动创建镜像。

    用法

    Dockerfile里面的指令可以访问context这些文件。

    context是递归的,PATH包含所有子目录,URL包含所有子模块。

    例子,把当前目录当做context,

    $ docker build .
    
    Sending build context to Docker daemon  6.51 MB
    ...
    

    build是由Docker daemon(守护进程)来运行,而不是CLI。

    build会把整个context发给daemon。所以最好把context设置为空目录,把Dockerfile放进去。只添加需要的文件,为了提高build性能,还可以添加.dockerignore来排除一些文件和目录。

    Warning!不要用系统根目录/作为PATH,不然会把根目录下所有东西都传给Docker daemon。

    一般会把Dockerfile放在context根目录下,也可以使用-f来指定其他路径,

    $ docker build -f /path/to/a/Dockerfile .
    

    指定镜像存放仓库可以使用-t

    $ docker build -t shykes/myapp .
    

    支持多个,

    $ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
    

    Docker daemon在执行Dockfile的指令前,会做检查,如果有语法错误会报错,

    $ docker build -t test/myapp .
    
    Sending build context to Docker daemon 2.048 kB
    Error response from daemon: Unknown instruction: RUNCMD
    

    Docker daemon执行指令,是一个一个执行,一个一个提交的。执行结束会生成镜像ID。自动清理context。

    RUN cd /tmp是无效的,因为daemon是独立执行每条指令的,不会作用到后面的指令。

    为了加速build过程,Docker会重复使用中间镜像(缓存),在console日志中可以看到Using cache

    $ docker build -t svendowideit/ambassador .
    
    Sending build context to Docker daemon 15.36 kB
    Step 1/4 : FROM alpine:3.2
     ---> 31f630c65071
    Step 2/4 : MAINTAINER SvenDowideit@home.org.au
     ---> Using cache
     ---> 2a1c91448f5f
    Step 3/4 : RUN apk update &&      apk add socat &&        rm -r /var/cache/
     ---> Using cache
     ---> 21ed6e7fbb73
    Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh
     ---> Using cache
     ---> 7ea8aef582cc
    Successfully built 7ea8aef582cc
    

    cache来源于之前本地build过的镜像,或者使用docker load加载的镜像。

    如果想直接指定一个镜像作为cache,可以使用--cache-from

    格式

    # Comment
    INSTRUCTION arguments
    

    #开头是注释或者parser directive(提示解析器做特殊处理)。

    指令是忽略大小写的,不过为了和参数区分,一般全大写。

    Dockerfile从上往下顺序执行指令,第一条指令必须是FROM,定义build的parent image(父镜像)。没有parent的镜像叫base image。

    参数里面的#就不是注释了,是参数的一部分,

    # Comment
    RUN echo 'we are running some # of cool things'
    

    注释在Dockerfile指令执行前,会被移除。以下是等价的,

    RUN echo hello \
    # comment
    world
    
    RUN echo hello \
    world
    

    注意,注释不支持换行符\

    注释和指令前面的空格会被忽略,以下是等价的,

            # this is a comment-line
        RUN echo hello
    RUN echo world
    
    # this is a comment-line
    RUN echo hello
    RUN echo world
    

    但是参数里面的空格,是会被保留的,

    RUN echo "\
         hello\
         world"
    

    Parser directives

    # directive=value
    

    Parser directives是一种特殊的注释,用来提示解析器做特殊处理。

    但是Parser directives并不会添加layers到build中,也不会被识别为build step。

    如果注释、空行、或者指令被运行后,Docker就不会再识别Parser directives了,所以必须把Parser directives放在Dockerfile的最前面的最前面。

    Parser directives是忽略大小写的,不过一般约定为全小写。同时约定随后跟一个空行。

    Parser directives不支持换行符。

    以下是一些无效示例,

    无效--换行符

    # direc \
    tive=value
    

    无效--出现了2次

    # directive=value1
    # directive=value2
    
    FROM ImageName
    

    无效--在指令之后就是普通的注释

    FROM ImageName
    # directive=value
    

    无效--在普通注释之后也变成了普通注释

    # About my dockerfile
    # directive=value
    FROM ImageName
    

    无效--未知命令会被视为普通注释,普通注释之后也是普通注释

    # unknowndirective=value
    # knowndirective=value
    

    Parser directives同一行的空格会被忽略,以下是等价的,

    #directive=value
    # directive =value
    #	directive= value
    # directive = value
    #	  dIrEcTiVe=value
    

    目前支持2个Parser directives,

    • syntax,依赖BuildKit
    • escape

    escape

    反斜杠(默认)

    # escape=\
    

    或者反引号

    # escape=`
    

    用来指定转义符。这个在Windows系统很有用,因为\在Windows是路径分隔符。

    比如,

    FROM microsoft/nanoserver
    COPY testfile.txt c:\\
    RUN dir c:\
    

    会执行失败,

    PS C:\John> docker build -t cmd .
    Sending build context to Docker daemon 3.072 kB
    Step 1/2 : FROM microsoft/nanoserver
     ---> 22738ff49c6d
    Step 2/2 : COPY testfile.txt c:\RUN dir c:
    GetFileAttributesEx c:RUN: The system cannot find the file specified.
    PS C:\John>
    

    使用escape可以替换\为`

    # escape=`
    
    FROM microsoft/nanoserver
    COPY testfile.txt c:\
    RUN dir c:\
    

    执行成功,

    PS C:\John> docker build -t succeeds --no-cache=true .
    Sending build context to Docker daemon 3.072 kB
    Step 1/3 : FROM microsoft/nanoserver
     ---> 22738ff49c6d
    Step 2/3 : COPY testfile.txt c:\
     ---> 96655de338de
    Removing intermediate container 4db9acbb1682
    Step 3/3 : RUN dir c:\
     ---> Running in a2c157f842f5
     Volume in drive C has no label.
     Volume Serial Number is 7E6D-E0F7
    
     Directory of c:\
    
    10/05/2016  05:04 PM             1,894 License.txt
    10/05/2016  02:22 PM    <DIR>          Program Files
    10/05/2016  02:14 PM    <DIR>          Program Files (x86)
    10/28/2016  11:18 AM                62 testfile.txt
    10/28/2016  11:20 AM    <DIR>          Users
    10/28/2016  11:20 AM    <DIR>          Windows
               2 File(s)          1,956 bytes
               4 Dir(s)  21,259,096,064 bytes free
     ---> 01c7f3bef04f
    Removing intermediate container a2c157f842f5
    Successfully built 01c7f3bef04f
    PS C:\John>
    

    环境替换

    环境变量(使用ENV指令来定义环境变量)能够用在指令中作为变量,被Dockerfile解释。还可以处理转义符,以便在语句中照字面值地包含variable-like语法。

    使用$variable_name${variable_name}来引用环境变量。

    可以使用双括弧和下划线来命名,如${foo}_bar。同时支持bash修饰符,

    • ${variable:-word} set variable后就是set的值,没有set variable值就是word
    • ${variable:+word} set variable后值就是word,没有set variable就是空字符串

    word既可以是string,也可以是另外一个环境变量。

    可以在变量前加转义符,比如\$foo\${foo}会被分别转义为$foo${foo}

    示例,

    FROM busybox
    ENV foo /bar
    WORKDIR ${foo}   # WORKDIR /bar
    ADD . $foo       # ADD . /bar
    COPY \$foo /quux # COPY $foo /quux
    

    Dockerfile的一下指令都支持环境变量

    • ADD
    • COPY
    • ENV
    • EXPOSE
    • FROM
    • LABEL
    • STOPSIGNAL
    • USER
    • VOLUME
    • WORKDIR
    • ONBUILD (结合以上指令使用)

    需要注意的是,变量替换是针对整条指令的,

    ENV abc=hello
    ENV abc=bye def=$abc
    ENV ghi=$abc
    

    def的值是hello,而不是bye,因为上一条指令赋值的hello。

    ghi的值才会是bye。

    .dockerignore file

    .dockerignore文件位于context根目录,会把匹配到的文件和目录排除在context之外。

    这样就可以在使用ADDCOPY命令时,避免把一些大文件或者敏感信息文件和目录,发送到Docker daemon。

    context是由PATHURL定义的,所以.dockerignore文件会匹配这2个路径。

    /foo/bar == foo/bar

    示例,

    # comment
    */temp*
    */*/temp*
    temp?
    
    Rule Behavior
    # comment 注释忽略
    */temp* 排除root的子目录下,temp开头的文件和目录。
    /somedir/temporary.txt/somedir/temp
    */*/temp* 排除root的二层目录下,temp开头的文件和目录。
    /somedir/subdir/temporary.txt
    temp? 排除root下, temp+1个字符的文件和目录。
    /tempa/tempb

    匹配遵循Go语言的filepath.Match规则。

    Docker还支持**,匹配任意数量的目录(包括0)。如**/*.go排除.go结尾的,包括context root下所有目录。

    如果排除了一堆文件后,想只包含其中几个文件,可以使用异常规则!

    示例,排除.md结尾的文件,包含README.md

    *.md
    !README.md
    

    README-secret.md不会被排除,因为!README*.md能匹配到README-secret.md,又把README-secret.md包含进来了。

    .dockerignore文件甚至可以排除Dockerfile.dockerignore,然而并没有什么卵用,这些文件还是会被发送到Docker daemon,只是ADDCOPY命令不会把它们复制到镜像了。

    FROM

    FROM指令初始化一个新的buid stage,为后面的指令设置Parent Image。

    FROM [--platform=<platform>] <image> [AS <name>]
    

    FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
    

    FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
    

    --platform,用来定义image的平台,如linux/amd64, linux/arm64, 或者windows/amd64,这样就能支持多平台镜像。

    tag digest是可选的,都不填时,默认用最新的tag。如果找不到tag,builder就会报错。

    AS name可以给image取个别名,在后续FROMCOPY --from=<name|index>指令中可以使用这个别名。

    可以在一个Dockerfile文件中使用多个FROM。每个FROM都会把上个指令创建的状态清除。所以在每个新的FROM指令之前,记录commit输出的最后一个image ID。

    ARG是唯一能在FROM之前的指令。

    比如--platform,默认情况下,会使用build请求的默认平台。也可以使用全局build参数,通过automatic platform ARGs(依赖BuildKit)来强制把stage指定为本地build平台(--platform=$BUILDPLATFORM),然后用它来在stage中cross-compile目标平台。

    FROMARG怎么结合使用呢?

    FROM指令支持出现在第一个FROM之前的ARG声明的变量。

    ARG  CODE_VERSION=latest
    FROM base:${CODE_VERSION}
    CMD  /code/run-app
    
    FROM extras:${CODE_VERSION}
    CMD  /code/run-extras
    

    FROM之前声明的ARG是在build stage之外的,所以它不能用在FROM后的任何指令中。如果要用,可以使用在build stage中的不带value的ARG指令,

    ARG VERSION=latest
    FROM busybox:$VERSION
    ARG VERSION
    RUN echo $VERSION > image_version
    

    RUN

    • RUN <command> (shell 格式,Linux /bin/sh -c Windowscmd /S /C)
    • RUN ["executable", "param1", "param2"] (exec 格式)

    RUN指令会在当前镜像之上的新layer中执行命令,commit结果,commit后的镜像会在Dockerfile的下一个step中使用。

    RUN指令的commits符合Docker理念,commit is cheap,containers可以从image历史中任何记录创建,就像source control。

    可以使用不同的SHELL

    shell格式

    RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
    

    exec格式

    RUN ["/bin/bash", "-c", "echo hello"]
    

    shell格式会调用command shell,而exec格式不会,所以exec中$HOME是没用的,要用的话直接执行shell RUN [ "sh", "-c", "echo $HOME" ]

    注意,exec格式被解析为JSON数组,所以只能用双引号。还需注意反斜杠,

    错误

    RUN ["c:\windows\system32\tasklist.exe"]
    

    正确

    RUN ["c:\\windows\\system32\\tasklist.exe"]
    

    默认是会启动RUN的缓存的,比如RUN apt-get dist-upgrade -y会在下次build的时候复用。可以使用docker build --no-cache来禁用缓存。

    使用ADDCOPY指令也可以禁用RUN缓存。

    CMD

    CMDRUN是不同的。RUN指令是在build过程中执行command和commit结果。CMD在build时不会执行任何command,而是为image定义command,在container(镜像创建的容器)启动的时候执行。

    • CMD ["executable","param1","param2"] (exec 格式,首选)
    • CMD ["param1","param2"] (ENTRYPOINT默认参数)
    • CMD command param1 param2 (shell 格式)

    一个Dockerfile只能有一个CMD指令,如果有多个,只有最后一个生效。

    shell格式会调用command shell,而exec格式不会,所以exec中$HOME是没用的,要用的话直接执行shell RUN [ "sh", "-c", "echo $HOME" ]

    注意,exec格式被解析为JSON数组,所以只能用双引号。还需注意反斜杠。

    如果想要container每次运行相同的可执行文件,需要结合 ENTRYPOINT使用。

    如果docker run定义了参数,那么会覆盖CMD定义。

    LABEL

    LABEL <key>=<value> <key>=<value> <key>=<value> ...
    

    LABEL用来给image添加metadata,是key-value键值对的形式。

    示例,

    LABEL "com.example.vendor"="ACME Incorporated"
    LABEL com.example.label-with-value="foo"
    LABEL version="1.0"
    LABEL description="This text illustrates \
    that label-values can span multiple lines."
    

    一个image可以有多个label,一个label可以有多个键值对,以下是等价的,

    LABEL multi.label1="value1" multi.label2="value2" other="value3"
    
    LABEL multi.label1="value1" \
          multi.label2="value2" \
          other="value3"
    

    label会随着image继承,从base image或parent image继承到当前image。

    重复的label,会用最新的覆盖旧的。

    可以使用命令查看image的labels,

    docker image inspect --format='' myimage
    
    {
      "com.example.vendor": "ACME Incorporated",
      "com.example.label-with-value": "foo",
      "version": "1.0",
      "description": "This text illustrates that label-values can span multiple lines.",
      "multi.label1": "value1",
      "multi.label2": "value2",
      "other": "value3"
    }
    

    MAINTAINER

    MAINTAINER 已经弃用了,直接使用LABLE

    LABEL maintainer="SvenDowideit@home.org.au"
    

    EXPOSE

    EXPOSE <port> [<port>/<protocol>...]
    

    EXPOSE定义了container监听的网络端口,支持TCP和UDP,默认TCP。

    EXPOSE并不真正的发布端口,而只是一种预定义。

    真正发布是在docker run的时候,使用-p-P来发布。

    -p发布一个或多个端口,-P发布全部,并映射到高位端口。

    示例,默认TCP,可以定义UDP,

    EXPOSE 80/udp
    

    也可以同时定义TCP和UDP,

    EXPOSE 80/tcp
    EXPOSE 80/udp
    

    如果这里docker run使用了-P,将会暴露一次TCP端口和一次UDP端口,由于会映射到高位端口,它们的端口会不一样。

    使用-p指定端口,

    docker run -p 80:80/tcp -p 80:80/udp ...
    

    也可以使用docker network来创建网络在container之间通信而不需要暴露任何端口。因为container可以使用任何端口通信。

    ENV

    ENV <key> <value>
    ENV <key>=<value> ...
    

    ENV用来设置环境变量。有2种形式,以下是等价的,

    ENV myName="John Doe" myDog=Rex\ The\ Dog \
        myCat=fluffy
    
    ENV myName John Doe
    ENV myDog Rex The Dog
    ENV myCat fluffy
    

    可以使用docker inspect来查看环境变量。也可以使用docker run --env <key>=<value>来修改环境变量。

    ENV的作用域除了build,还包括container running。有时候会有副作用,比如ENV DEBIAN_FRONTEND noninteractive,所有操作都是非交互式的,无需向用户请求输入,直接运行命令。可能会使apt-get用户误认为是一个Debian-based image。正确的做法是为command添加单独的环境变量,如RUN apt-get install -y python3

    ADD

    ADD [--chown=<user>:<group>] <src>... <dest>
    ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
    

    ADD有2种形式,第2种是为了支持路径包含空格,所以加了双引号。

    --chown只适用于Linux container,对Windows无效。

    ADD的作用是从<src>复制新文件,目录或者远程文件URLs,然后添加到<desc>所在的image文件系统。

    src如果是文件和目录,那么就是相对路径,相对于build的context。同时支持通配符,遵循Golang的filepath.Match规则。

    示例,添加所有以"hom"开头的文件,

    ADD hom* /mydir/
    

    ?匹配单个字符,

    ADD hom?.txt /mydir/
    

    <dest>是绝对路径,或者WORKDIR的相对路径。

    示例,绝对路径,

    ADD test.txt /absoluteDir/
    

    相对路径,<WORKDIR>/relativeDir/

    ADD test.txt relativeDir/
    

    如果路径种包含特殊字符(如[]),那么需要进行转义,

    示例,添加一个文件arr[0].txt

    ADD arr[[]0].txt /mydir/
    

    针对Linux,可以使用--chown定义username、groupname或者UID/GID,默认新文件和目录会被设置为UID为0,GID为0。

    如果只设置username不设置groupname,或只设置UID不设置GID,GID会使用和UID相同的数值。

    username和groupname会被container's root filesystem /etc/passwd and /etc/group 转换为UID/GID。如果container没有这2个文件,在设置了username/groupname后,就会报错。可以通过设置UID/GID来避免。

    示例,

    ADD --chown=55:mygroup files* /somedir/
    ADD --chown=bin files* /somedir/
    ADD --chown=1 files* /somedir/
    ADD --chown=10:11 files* /somedir/
    

    如果build使用STDIN (docker build - < somefile),就没有build context,就只能用ADDURL。也可以在使用STDIN时添加压缩包 (docker build - < archive.tar.gz),压缩包根目录的Dockerfile和其他压缩包会当做build context。

    如果src是一个远程文件URL,就会需要600权限(Linux)。如果远程文件有HTTP Last-Modified header,header的timestamp会用来设置到dest文件的mtime。但是mtime不会反映文件是否修改和缓存是否应该更新。

    如果URL文件需要授权,ADD是不支持的,需要使用RUN wget, RUN curl,或者container里面的其他工具。

    ADD遵循以下规则:

    • <src> 必须在build的context 中;不能 ADD ../something /something添加context父目录的东西。因为 docker build 的第一步是把context,目录及其子目录发送到docker daemon。
    • 如果<src> 是URL,<dest> 没有以斜杠结尾,那么文件从直接从URL下载后,然后直接复制到 <dest>
    • 如果 <src> 是URL,<dest> 是以斜杠结尾的,那么会从URL解析出文件名,下载到<dest>/<filename>。比如, ADD http://example.com/foobar dest/ 会创建文件 dest/foobar。URL必须是明确的路径,以保证能找到合适的文件名(http://example.com 是无效的)。
    • 如果 <src> 是目录,那么整个目录都会被复制,包括文件系统的metadata。(目录本身不复制,只是内容)
    • 如果 <src>是本地压缩包(如gzip, bzip2 or xz),那么会被解压成目录。远程URL是不会解压的。解压相当于执行了 tar -x,如果dest路径下有文件冲突,会被重命名为“2”。(压缩包不是根据文件名判断的,而是根据内容,比如一个空文件命名为.tar.gz,是不会被解压复制的)
    • 如果 <src> 是任何其他文件,就会随同它的metadata一起复制。此时 <dest> 以斜杠 /结尾的话,就会被认为是一个目录,<src>的内容会被写到<dest>/base(<src>)
    • 如果<src>定义的是多个资源,不论是直接还是通配符匹配到的, <dest> 必须是一个目录,且以斜杠/结尾。
    • 如果 <dest> 不以斜杠结尾,那么就会被认为是一个普通文件,那么<src> 会被写到<dest>
    • 如果 <dest> 不存在,那么path中的所有未创建的目录都会自动创建。

    如果src内容改变了,在第一次遇到ADD指令后,会禁用后续所有指令的缓存,包括RUN指令的缓存。

    COPY

    COPYADD区别在于ADD可以添加远程URLS,COPY不能。

    COPY [--chown=<user>:<group>] <src>... <dest>
    COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
    

    COPY有2种形式,第2种是为了支持路径包含空格,所以加了双引号。

    --chown只适用于Linux container,对Windows无效。

    COPY的作用是从<src>复制新文件,目录,然后添加到<desc>所在的image文件系统。

    src如果是文件和目录,那么就是相对路径,相对于build的context。同时支持通配符,遵循Golang的filepath.Match规则。

    示例,添加所有以"hom"开头的文件,

    COPY hom* /mydir/
    

    ?匹配单个字符,

    COPY hom?.txt /mydir/
    

    <dest>是绝对路径,或者WORKDIR的相对路径。

    示例,绝对路径,

    COPY test.txt /absoluteDir/
    

    相对路径,<WORKDIR>/relativeDir/

    COPY test.txt relativeDir/
    

    如果路径种包含特殊字符(如[]),那么需要进行转义,

    示例,添加一个文件arr[0].txt

    COPY arr[[]0].txt /mydir/
    

    针对Linux,可以使用--chown定义username、groupname或者UID/GID,默认新文件和目录会被设置为UID为0,GID为0。

    如果只设置username不设置groupname,或只设置UID不设置GID,GID会使用和UID相同的数值。

    username和groupname会被container's root filesystem /etc/passwd and /etc/group 转换为UID/GID。如果container没有这2个文件,在设置了username/groupname后,就会报错。可以通过设置UID/GID来避免。

    示例,

    COPY --chown=55:mygroup files* /somedir/
    COPY --chown=bin files* /somedir/
    COPY --chown=1 files* /somedir/
    COPY --chown=10:11 files* /somedir/
    

    如果build使用STDIN (docker build - < somefile),就没有build context,就不能用COPY

    COPY支持--from=<name|index>,用来指定src为之前buid的image(通过FROM .. AS <name>创建的)来替换build context。既可以是name也可以是index数字(所有使用FROM指令建立的build stages)。如果通过name找不到build stage,就会去找同名的image。

    COPY遵循以下规则:

    • <src> 必须在build的context 中;不能 COPY ../something /something添加context父目录的东西。因为 docker build 的第一步是把context,目录及其子目录发送到docker daemon。
    • 如果 <src> 是目录,那么整个目录都会被复制,包括文件系统的metadata。(目录本身不复制,只是内容)
    • 如果 <src> 是任何其他文件,就会随同它的metadata一起复制。此时 <dest> 以斜杠 /结尾的话,就会被认为是一个目录,<src>的内容会被写到<dest>/base(<src>)
    • 如果<src>定义的是多个资源,不论是直接还是通配符匹配到的, <dest> 必须是一个目录,且以斜杠/结尾。
    • 如果 <dest> 不以斜杠结尾,那么就会被认为是一个普通文件,那么<src> 会被写到<dest>
    • 如果 <dest> 不存在,那么path中的所有未创建的目录都会自动创建。

    如果src内容改变了,在第一次遇到COPY指令后,会禁用后续所有指令的缓存,包括RUN指令的缓存。

    ENTRYPOINT

    exec 格式

    ENTRYPOINT ["executable", "param1", "param2"]
    

    shell 格式

    ENTRYPOINT command param1 param2
    

    ENTRYPOINT用来配置container作为可执行文件来运行。

    示例,使用默认内容启动nginx,监听80端口,

    $ docker run -i -t --rm -p 80:80 nginx
    

    docker run <image>的命令行参数,会被添加到exec格式中的所有元素之后,并覆盖CMD指令定义的元素。这样就可以把参数传递给entry point,也就是docker run <image> -d会把-d传递给entry point。可以使用docker run --entrypoint来覆盖ENTRYPOINT指令(但是只能把binary设置为exec,不能用sh -c)。

    shell格式会禁用掉CMD或者run命令行参数,但是有个缺点就是,ENTRYPOINT就不是作为/bin/sh -c的子命令来启动的了,也就是不能传递signals。也就意味着可执行文件,不是container的PID 1,也不会接收Unix signals(一种软件中断)。这样可执行文件就不会接收来自docker stop <container>SIGTERM

    只有Dockerfile的最后一个ENTRYPOINT才会生效。

    ENTRYPOINT Exec示例

    FROM ubuntu
    ENTRYPOINT ["top", "-b"]
    CMD ["-c"]
    

    当运行container,top是唯一进程,

    $ docker run -it --rm --name test  top -H
    
    top - 08:25:00 up  7:27,  0 users,  load average: 0.00, 0.01, 0.05
    Threads:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.1 us,  0.1 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem:   2056668 total,  1616832 used,   439836 free,    99352 buffers
    KiB Swap:  1441840 total,        0 used,  1441840 free.  1324440 cached Mem
    
      PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
        1 root      20   0   19744   2336   2080 R  0.0  0.1   0:00.04 top
    

    为了验证更多结果,使用docker exec

    $ docker exec -it test ps aux
    
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  2.6  0.1  19752  2352 ?        Ss+  08:24   0:00 top -b -H
    root         7  0.0  0.1  15572  2164 ?        R+   08:25   0:00 ps aux
    

    top -b -H,其中top -bENTRYPOINT设置的,-H是docker命令行参数,添加到了ENTRYPOINT后面,覆盖了CMD-c。

    然后可以优雅地使用docker stop test请求top shut down。

    示例,使用ENTRYPOINT在前台运行Apache(也就是PID 1),

    FROM debian:stable
    RUN apt-get update && apt-get install -y --force-yes apache2
    EXPOSE 80 443
    VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
    ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]
    

    如果想编写单个可执行文件的启动脚本,可以使用execgosu命令,来确保可执行文件能够接收到Unix signals。

    #!/usr/bin/env bash
    set -e
    
    if [ "$1" = 'postgres' ]; then
        chown -R postgres "$PGDATA"
    
        if [ -z "$(ls -A "$PGDATA")" ]; then
            gosu postgres initdb
        fi
    
        exec gosu postgres "$@"
    fi
    
    exec "$@"
    

    最后,如果在shutdown的时候需要做一些额外的清理(或者和其他containers交互),或者是多个协调而不是单个可执行文件,就可能需要确保ENTRYPOINT脚本能够接收Unix signals,传递,然后做更多工作,

    #!/bin/sh
    # Note: I've written this using sh so it works in the busybox container too
    
    # USE the trap if you need to also do manual cleanup after the service is stopped,
    #     or need to start multiple services in the one container
    trap "echo TRAPed signal" HUP INT QUIT TERM
    
    # start service in background here
    /usr/sbin/apachectl start
    
    echo "[hit enter key to exit] or run 'docker stop <container>'"
    read
    
    # stop service and clean up here
    echo "stopping apache"
    /usr/sbin/apachectl stop
    
    echo "exited $0"
    

    如果使用docker run -it --rm -p 80:80 --name test apache来运行这个image,那么就可以使用docker execdocker top来验证container处理,然后使用脚本停止Apache,

    $ docker exec -it test ps aux
    
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  0.1  0.0   4448   692 ?        Ss+  00:42   0:00 /bin/sh /run.sh 123 cmd cmd2
    root        19  0.0  0.2  71304  4440 ?        Ss   00:42   0:00 /usr/sbin/apache2 -k start
    www-data    20  0.2  0.2 360468  6004 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
    www-data    21  0.2  0.2 360468  6000 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
    root        81  0.0  0.1  15572  2140 ?        R+   00:44   0:00 ps aux
    
    $ docker top test
    
    PID                 USER                COMMAND
    10035               root                {run.sh} /bin/sh /run.sh 123 cmd cmd2
    10054               root                /usr/sbin/apache2 -k start
    10055               33                  /usr/sbin/apache2 -k start
    10056               33                  /usr/sbin/apache2 -k start
    
    $ /usr/bin/time docker stop test
    
    test
    real	0m 0.27s
    user	0m 0.03s
    sys	0m 0.03s
    

    shell格式会调用command shell,而exec格式不会,所以exec中$HOME是没用的,要用的话直接执行shell RUN [ "sh", "-c", "echo $HOME" ]

    注意,exec格式被解析为JSON数组,所以只能用双引号。还需注意反斜杠。

    ENTRYPOINT Shell示例

    ENTRYPOINT定义一个简单的string,然后它就会在/bin/sh -c中执行。shell格式使用shell processing来替代shell environment variables,然后会忽略任何CMDdocker run命令行参数。为了确保docker stop能直接signal任何运行的ENTRYPOINT可执行文件,记住使用exec开始,

    FROM ubuntu
    ENTRYPOINT exec top -b
    

    运行这个image时,你会看到单个PID 1进程,

    $ docker run -it --rm --name test top
    
    Mem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cached
    CPU:   5% usr   0% sys   0% nic  94% idle   0% io   0% irq   0% sirq
    Load average: 0.08 0.03 0.05 2/98 6
      PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
        1     0 root     R     3164   0%   0% top -b
    

    执行docker stop,也会干净的退出,

    $ /usr/bin/time docker stop test
    
    test
    real	0m 0.20s
    user	0m 0.02s
    sys	0m 0.04s
    

    如果忘了在ENTRYPOINT前添加exec

    FROM ubuntu
    ENTRYPOINT top -b
    CMD --ignored-param1
    

    运行(为下一步设置一个name),

    $ docker run -it --name test top --ignored-param2
    
    Mem: 1704184K used, 352484K free, 0K shrd, 0K buff, 140621524238337K cached
    CPU:   9% usr   2% sys   0% nic  88% idle   0% io   0% irq   0% sirq
    Load average: 0.01 0.02 0.05 2/101 7
      PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
        1     0 root     S     3168   0%   0% /bin/sh -c top -b cmd cmd2
        7     1 root     R     3164   0%   0% top -b
    

    你就会看到ENTRYPOINT定义的top不是PID 1

    如果执行docker stop test,container就不会干净地退出。stop命令会在超时后被强制发送一个SIGKILL

    $ docker exec -it test ps aux
    
    PID   USER     COMMAND
        1 root     /bin/sh -c top -b cmd cmd2
        7 root     top -b
        8 root     ps aux
    
    $ /usr/bin/time docker stop test
    
    test
    real	0m 10.19s
    user	0m 0.04s
    sys	0m 0.03s
    

    real 10.19s超时。

    CMD和ENTRYPOINT如何结合使用

    CMDENTRYPOINT指令都定义了运行container时,哪些命令会执行。他们的结合有一些规则,

    1. Dockerfile应该定义至少一个CMDENTRYPOINT
    2. 如果使用container作为可执行文件,应该定义ENTRYPOINT
    3. 如果需要给ENTRYPOINT 定义默认参数,或者在container中执行ad-hoc(临时)命令,应该使用CMD
    4. 以可选参数运行container时会覆盖CMD

    下面这个表格展示了CMDENTRYPOINT指令的不同组合

    No ENTRYPOINT ENTRYPOINT exec_entry p1_entry ENTRYPOINT [“exec_entry”, “p1_entry”]
    No CMD error, not allowed /bin/sh -c exec_entry p1_entry exec_entry p1_entry
    CMD [“exec_cmd”, “p1_cmd”] exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry exec_cmd p1_cmd
    CMD [“p1_cmd”, “p2_cmd”] p1_cmd p2_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry p1_cmd p2_cmd
    CMD exec_cmd p1_cmd /bin/sh -c exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd

    注意,如果CMD是从base image定义的,那么设置ENTRYPOINT会重置CMD为空值。此时如果要使用CMD,必须在当前image重新定义。

    VOLUME

    VOLUME ["/data"]
    

    VOLUME指令用来创建挂载点,把container挂载到native host(宿主机)或其他container。

    value可以是JSON array,如VOLUME ["/var/log/"],也可以是string,如VOLUME /var/logVOLUME /var/log /var/db

    docker run命令会用base image中定义的location中存在的任何数据,来初始化新创建的volumn。

    示例,

    FROM ubuntu
    RUN mkdir /myvol
    RUN echo "hello world" > /myvol/greeting
    VOLUME /myvol
    

    docker run会在/myvol创建一个挂载点,然后把greeting复制到新创建的volumn。

    遵循规则,

    • **基于Windows的containers **: volumn的目标路径必须是以下之一:
      • 不存在的或者空的目录
      • C:以外的驱动
    • 在Dockerfile里面修改volumn: 在volumn已经被声明之后的任何build steps尝试修改volumn数据,都会被忽略。
    • JSON formatting: 要用双引号,不要用单引号.
    • 在container run-time才会声明主机目录(挂载点): 挂载点是依赖主机的。因为主机目录不能保证对所有主机都是有用的,为了保证image的可移植性,不能在Dockerfile中挂载主机目录,而是必须在创建或运行container的时候。VOLUME指令也不支持host-dir这样的参数。

    USER

    USER <user>[:<group>]
    

    USER <UID>[:<GID>]
    

    USER指令用于RUN, CMDENTRYPOINT指令执行时指定user name / group。USER指令可以设置user name(或UID),可选用user group(或GID)。

    如果定义了user group,那么这个user就只有这个group的membership,任何其他配置的group memberships都会被忽略。

    如果user没有primary group,那么image(或者下一条指令)就会以root group运行。

    在Windows,如果不是内建账号,必须先创建。可以在Dockerfile中调用net user命令,

    FROM microsoft/windowsservercore
    # Create Windows user in the container
    RUN net user /add patrick
    # Set it for subsequent commands
    USER patrick
    

    WORKDIR

    WORKDIR /path/to/workdir
    

    WORKDIRRUN, CMD, ENTRYPOINT, COPY and ADD指令设置工作目录。

    如果WORKDIR不存在,即使后面的Dockerfile不会用到,它仍然会被创建。

    WORKDIR指令可以在Dockerfile中定义多次。如果是相对路径,那么就是相对于上一条WORKDIR指令的路径。

    示例,

    WORKDIR /a
    WORKDIR b
    WORKDIR c
    RUN pwd
    

    pwd的结果是/a/b/c

    WORKDIR可以引用ENV定义的环境变量,示例,

    ENV DIRPATH /path
    WORKDIR $DIRPATH/$DIRNAME
    RUN pwd
    

    pwd的结果是/path/$DIRNAME

    ARG

    ARG <name>[=<default value>]
    

    ARG指令定义变量,用户可以在使用docker build命令带参数--build-arg <varname>=<value>,在build-time传递这个变量给builder。如果用户指定了一个build参数而没有在Dockerfile中定义,build会报warning,

    [Warning] One or more build-args [foo] were not consumed.
    

    一个Dockerfile可以包含一个或多个ARG指令。

    示例,

    FROM busybox
    ARG user1
    ARG buildno
    # ...
    

    警告!不建议使用build-time变量来传递私密数据,如github keys,用户认证信息等。因为image的任何用户都可以使用docker history查看build-time变量。

    默认值

    ARG指令可以设置默认值(可选),

    FROM busybox
    ARG user1=someuser
    ARG buildno=1
    # ...
    

    如果ARG指令有默认值,在build-time没有值传递,那么builder会用这个默认值。

    范围

    ARG指令是在它被定义那一行生效的,而不是命令行被使用的时候,或者其他地方。

    示例,

    FROM busybox
    USER ${user:-some_user}
    ARG user
    USER $user
    # ...
    

    用户build这个文件,调用,

    $ docker build --build-arg user=what_user .
    

    第2行的USER结果为some_user因为user变量是在第3行定义的。

    第4行的USER结果为what_user,因为user变量已经被定义了,在命令行传递了what_user值。

    ARG指令定义之前,任何变量使用结果都是空string。

    ARG定义的build stage结束时,ARG指令就超出范围了。为了在多个stages使用同一个arg,每个stage都必须包括ARG指令,

    FROM busybox
    ARG SETTINGS
    RUN ./run/setup $SETTINGS
    
    FROM busybox
    ARG SETTINGS
    RUN ./run/other $SETTINGS
    

    使用ARG变量

    可以使用ARGENV指令来为RUN指令定义变量。ENV定义的环境变量始终都会覆盖ARG定义的同名变量。

    示例,

    FROM ubuntu
    ARG CONT_IMG_VER
    ENV CONT_IMG_VER v1.0.0
    RUN echo $CONT_IMG_VER
    

    假设使用这条命令build image,

    $ docker build --build-arg CONT_IMG_VER=v2.0.1 .
    

    RUN会使用v1.0.0而不是ARG传递的v2.0.1。这个行为有点类似于shell脚本,一个局部变量会覆盖通过参数传递的变量,或者从环境定义继承的变量。

    还是上面的例子,定义不同的ENV会把ARGENV结合的更好用,

    FROM ubuntu
    ARG CONT_IMG_VER
    ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
    RUN echo $CONT_IMG_VER
    

    不像ARGENV的值会在build image中持久化。如果不用--build-arg build,

    $ docker build .
    

    用这个Dockerfile,CONT_IMG_VER仍然会持久化在这个image,它的值是v1.0.0,因为在第3行用ENV定义了默认值。

    在这个示例中,通过ENV指令,可以把命令行参数传递进来,然后持久化到最终的image,实现了变量扩展。变量扩展只支持Dockerfile指令的一部分指令。

    • ADD
    • COPY
    • ENV
    • EXPOSE
    • FROM
    • LABEL
    • STOPSIGNAL
    • USER
    • VOLUME
    • WORKDIR
    • ONBUILD (结合以上指令使用)

    预定义ARGs

    Docker有一些预定义的ARG变量,你可以不使用ARG指令,直接用这些变量。

    • HTTP_PROXY
    • http_proxy
    • HTTPS_PROXY
    • https_proxy
    • FTP_PROXY
    • ftp_proxy
    • NO_PROXY
    • no_proxy

    直接在命令行使用,

    --build-arg <varname>=<value>
    

    默认这些预定义的变量是不会输出到docker history中的。这样可以降低在HTTP_PROXY变量中意外泄露敏感认证信息的风险。

    示例,使用--build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com来build Dockerfile,

    FROM ubuntu
    RUN echo "Hello World"
    

    HTTP_PROXY变量不会输出到docker history,也不会被缓存。如果代理服务器变成了http://user:pass@proxy.sfo.example.com,后续的build不会导致cache miss。

    可以使用ARG来覆盖这个默认行为,

    FROM ubuntu
    ARG HTTP_PROXY
    RUN echo "Hello World"
    

    当build这个Dockerfile的时候,HTTP_PROXY会存到docker history中,如果它的值改变了,会把build缓存禁用掉。

    对缓存的影响

    ARG变量并不会像ENV持久化到image,但是会以类似的方式,影响到build缓存。如果Dockerfile定义了一个ARG变量,这个变量和前一个build不一样,那么在第一次用这个变量的时候会发生"cache miss"(不是定义的时候)。尤其是,所有ARG后面的RUN指令一般都会使用ARG变量,这样就会导致cache miss。但是所有预定义ARGs是没有影响cache的,除非是在Dockerfile中有一个同名的ARG指令。

    示例,2个Dockerfile

    FROM ubuntu
    ARG CONT_IMG_VER
    RUN echo $CONT_IMG_VER
    
    FROM ubuntu
    ARG CONT_IMG_VER
    RUN echo hello
    

    如果在命令行指定--build-arg CONT_IMG_VER=<value>,以上2个示例在第2行都不会cache miss,第3行会cache miss。ARG CONT_IMG_VER会导致RUN那一行被认为是执行了CONT_IMG_VER=<value> echo hello,所以如果<value>改变了,就cache miss了。

    另外一个示例,

    FROM ubuntu
    ARG CONT_IMG_VER
    ENV CONT_IMG_VER $CONT_IMG_VER
    RUN echo $CONT_IMG_VER
    

    第3行会发生cache miss。因为ENV引用的ARG变量通过命令行改变了。另外,在这个示例中,ENV会导致image包含这个value(ENV会持久化到image中)。

    如果ENVARG指令重复,

    FROM ubuntu
    ARG CONT_IMG_VER
    ENV CONT_IMG_VER hello
    RUN echo $CONT_IMG_VER
    

    第3行就不会发生cache miss,因为CONT_IMG_VER的值是常量(hello)。因此第4行RUN指令用到的环境变量和值在build之间不会改变。

    ONBUILD

    ONBUILD <INSTRUCTION>
    

    ONBUILD指令会在image中添加一个trigger,这个trigger会在image作为base的时候触发。trigger会在下游的 build context中执行,就像在下游的Dockerfile 中,在 FROM指令之后,它就已经被立即嵌入了。

    任何build指令都可以注册为trigger。

    如果你build一个image,这个image会作为base来build其他images,这就很有用。比如,一个应用build环境或者一个deamon自定义配置。

    示例,如果一个image是可复用的Python应用builder(用来build新的应用image),那么它需要把应用源码添加到一个特定目录,然后调用build脚本。此时ADDRUN指令是无法访问应用源码的,每个应用build的源码也可能不一样。你可以简单地,给应用开发者提供Dockerfile样本文件来复制粘贴到他们的应用中,但这是低效、易出错和困难去做更新的,因为这个和“应用定义”代码混淆了。

    可以使用ONBUILD指令来提前注册指令,在下个build stage再运行。

    过程如下,

    1. 当碰到ONBUILD 指令,builder就会添加trigger到正在build的image的metadata。这条指令不会影响当前build。
    2. 在build的最后,所有的triggers都会被存储到image的manifest,在key OnBuild下面。可以用 docker inspect 命令查看。
    3. 然后image可能会被用来作为新build的base,使用 FROM 指令。 FROM 指令在处理时,下游builder会查找 ONBUILD triggers,然后按它们注册的顺序执行。如果有trigger失败了,FROM指令就会中断,build失败。如果triggers都成功了,那么FROM会完成,build成功。
    4. Triggers会在执行后,从最后一个image中清除。也就是说,它们是不会随着“父子”build继承的。

    比如你可能会添加这样的内容,

    ONBUILD ADD . /app/src
    ONBUILD RUN /usr/local/bin/python-build --dir /app/src
    

    注意,1.链式ONBUILD ONBUILD是不允许的。2.ONBUILD可能不会trigger FROMMAINTAINER指令。

    STOPSIGNAL

    STOPSIGNAL signal
    

    STOPSIGNAL指令设置system call signal,发送到container退出。signal可以是有效的unsigned number(匹配kernel’s syscall table里的position,比如9),也可以是SIGNAME(比如SIGKILL)。

    HEALTHCHECK

    2种格式,

    • HEALTHCHECK [OPTIONS] CMD command (通过运行container里面的命令来检查container)
    • HEALTHCHECK NONE (禁用健康检查,从base image继承)

    HEALTHCHECK 指令用来告诉Docker怎样测试container是否还在工作。比如虽然server一直在运行,但是实际上已经死循环了,无法处理新连接了。

    当container定义了健康检查,就会把健康状态添加到status中。status初始化是starting。无论健康检查什么时候通过,它都会变为healthy(无论之前是什么状态)。在一定数量的连续失败后,它会变为unhealthy

    第一种格式的OPTION可以是,

    • --interval=DURATION (default: 30s)
    • --timeout=DURATION (default: 30s)
    • --start-period=DURATION (default: 0s)
    • --retries=N (default: 3)

    在container开始后的interval seconds ,会运行健康检查。每个健康检查完成后,等待interval seconds再次运行。

    如果健康检查运行的时候超过了timeout seconds,就认为失败。

    失败的次数如果达到了retries的值,就认为unhealthy

    start period指定了container需要启动的时间。在这期间探针失败(Probe failure)不会记作重试次数。但是,如果在这期间健康检查通过了,那么container就认为已经启动了,这之后的失败(all consecutive failures)就会记作重试次数。

    一个Dockerfile只能有一个HEALTHCHECK 指令。如果有多个,那么只有最后一个HEALTHCHECK 生效。

    第1种格式的command既可以是shell命令(如,HEALTHCHECK CMD /bin/check-running),也可以是exec数组。

    command的退出状态反应了container的健康状态,

    • 0: success - the container is healthy and ready for use
    • 1: unhealthy - the container is not working correctly
    • 2: reserved - do not use this exit code

    示例,每5分钟检查1次,以确保web服务器能在3秒内为网站首页提供服务,

    HEALTHCHECK --interval=5m --timeout=3s \
      CMD curl -f http://localhost/ || exit 1
    

    为了帮助debug失败探针(failing probes),任何写到stdout或stderr输出文本(UTF-8编码)都会被存储到健康状态,并且可以使用docker inspect查询。而且输出应该简短(目前只有最开始的4096 bytes会被存储)。

    当container的健康状态改变了,会用新的状态生成一个health_status事件。

    SHELL

    SHELL ["executable", "parameters"]
    

    SHELL指令允许重写shell格式命令的默认shell。Linux的默认shell是["/bin/sh", "-c"],Windows的默认shell是["cmd", "/S", "/C"]SHELL指令必须在Dockfile中写成JSON格式。

    SHELL指令在Windows特别有用,因为Windows有2个常用的不同的原生shell,cmdpowershell,也有可选用的shell,包括sh

    SHELL指令可以出现多次。每个SHELL指令会覆盖所有之前的SHELL指令,影响随后的指令。

    示例,

    FROM microsoft/windowsservercore
    
    # Executed as cmd /S /C echo default
    RUN echo default
    
    # Executed as cmd /S /C powershell -command Write-Host default
    RUN powershell -command Write-Host default
    
    # Executed as powershell -command Write-Host hello
    SHELL ["powershell", "-command"]
    RUN Write-Host hello
    
    # Executed as cmd /S /C echo hello
    SHELL ["cmd", "/S", "/C"]
    RUN echo hello
    

    shell格式的RUN CMDENTRYPOINT出现在Dcokerfile中时,SHELL指令能影响这些指令。

    示例,Windows上常见的模式,可以通过使用SHELL指令进行简化,

    RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
    

    docker调用的命令,

    cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
    

    这个有点低效,有2个原因。首先,有一个不必要的cmd.exe命令行处理器(aka shell)被调用了。其次,shell格式的RUN指令需要额外的前缀命令powershell -command

    为了更高效,有2种机制。其一是使用JSON格式,

    RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]
    

    JSON格式是清晰的,不会使用不必要的cmd.exe。但是需要双引号和转义符,显得有点冗余。

    。其二是用SHELL指令和shell格式,这样可以给Windows用户更自然的语法,特别是和escape parser directive结合使用的时候,

    # escape=`
    
    FROM microsoft/nanoserver
    SHELL ["powershell","-command"]
    RUN New-Item -ItemType Directory C:\Example
    ADD Execute-MyCmdlet.ps1 c:\example\
    RUN c:\example\Execute-MyCmdlet -sample 'hello world'
    

    结果是,

    PS E:\docker\build\shell> docker build -t shell .
    Sending build context to Docker daemon 4.096 kB
    Step 1/5 : FROM microsoft/nanoserver
     ---> 22738ff49c6d
    Step 2/5 : SHELL powershell -command
     ---> Running in 6fcdb6855ae2
     ---> 6331462d4300
    Removing intermediate container 6fcdb6855ae2
    Step 3/5 : RUN New-Item -ItemType Directory C:\Example
     ---> Running in d0eef8386e97
    
    
        Directory: C:\
    
    
    Mode                LastWriteTime         Length Name
    ----                -------------         ------ ----
    d-----       10/28/2016  11:26 AM                Example
    
    
     ---> 3f2fbf1395d9
    Removing intermediate container d0eef8386e97
    Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\
     ---> a955b2621c31
    Removing intermediate container b825593d39fc
    Step 5/5 : RUN c:\example\Execute-MyCmdlet 'hello world'
     ---> Running in be6d8e63fe75
    hello world
     ---> 8e559e9bf424
    Removing intermediate container be6d8e63fe75
    Successfully built 8e559e9bf424
    PS E:\docker\build\shell>
    

    SHELL指令也能被用来修改shell操作方式。比如在Windows用SHELL cmd /S /C /V:ON|OFF,可以修改延迟环境变量扩展语义。

    SHELL指令也可以用在Linux上,可选的shell有zsh, csh, tcsh等。

    Dockerfile示例

    # Nginx
    #
    # VERSION               0.0.1
    
    FROM      ubuntu
    LABEL Description="This image is used to start the foobar executable" Vendor="ACME Products" Version="1.0"
    RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server
    
    # Firefox over VNC
    #
    # VERSION               0.3
    
    FROM ubuntu
    
    # Install vnc, xvfb in order to create a 'fake' display and firefox
    RUN apt-get update && apt-get install -y x11vnc xvfb firefox
    RUN mkdir ~/.vnc
    # Setup a password
    RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
    # Autostart firefox (might not be the best way, but it does the trick)
    RUN bash -c 'echo "firefox" >> /.bashrc'
    
    EXPOSE 5900
    CMD    ["x11vnc", "-forever", "-usepw", "-create"]
    
    # Multiple images example
    #
    # VERSION               0.1
    
    FROM ubuntu
    RUN echo foo > bar
    # Will output something like ===> 907ad6c2736f
    
    FROM ubuntu
    RUN echo moo > oink
    # Will output something like ===> 695d7793cbe4
    
    # You'll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with
    # /oink.
    

    以下内容可查看参考资料进一步阅读。

    • BuildKit(第三方工具)
    • Parser directives的命令syntax(依赖BuildKit)
    • RUN已知bug(Issue 783
    • External implementation features(依赖BuildKit)
    • Automatic platform ARGs in the global scope(依赖BuildKit)

    参考资料

    https://docs.docker.com/engine/reference/builder/

    下一篇《Dockerfile最佳实践》,欢迎持续关注哦。


    所有文章公众号首发!
    如果你觉得这篇文章写的还不错的话,关注公众号“dongfanger”,你的支持就是我写文章的最大动力。

    版权申明:本文为博主原创文章,转载请保留原文链接及作者。
  • 相关阅读:
    PHP date函数时间相差8个小时解决办法
    PHP中include()与require()的区别说明
    post与get区别
    MFC 对话框控件自动布局
    Apache php Mysql部署(一)下载安装
    apache 重定向
    clistctrl失去焦点高亮显示选中行
    格式化字符串
    unicode编码与utf-8 区别
    string的实现
  • 原文地址:https://www.cnblogs.com/df888/p/13560590.html
Copyright © 2011-2022 走看看