Docker可以从Dockerfile中读取指令来自动构建镜像。Dockerfile是一个文本文件,它包含了用户可以在命令调用以制作镜像的命令。用户可以使用docker build连续执行一些命令行指令来开启一个自动构建。此文档描述了在Dockerfile中可以使用的命令。当你读完这个文档时,请参阅Dockfile最佳实践获取进阶指南。使用docker build命令从Dockerfile和上下文构建镜像。构建上下文是特定路径或URL的文件集合。该路径是你本地文件系统的一个目录。URL是一个Git仓库地址。上下文会被递归处理。所以,路径包含的任意字母路合URL包含的仓库及其子模块也会被处理。一下实例展示了一个使用当前目录作为上下文的build命令:$ docker build .Sending build context to Docker daemon 6.51 MB…构建由Docker daemon执行, 而非cli。构建进程的第一件事是(递归的)发送上下文给守护进程(daemon)。在大多数情况下,最好以一个空目录下作为上下文发送给守护进程并且保持Dockerfile在该目录下。只为构建Dockerfile增加必须的文件。CMDCMD指令有三种用法:CMD [“executable”,“param1”,“param2”] (exec形式, 这是首选形式)CMD [“param1”,“param2”] (作为ENTRYPOINT默认参数)CMD command param1 param2 (shell 形式)一个Dockerfile里只能有一个CMD指令。如果你有多个CMD指令,只有 最后一个 生效。CMD的主要目的是为运行容器提供默认值。 默认值可以包含一个可执行文件,也忽略可执行文件,在此情况下必须同时指定ENTRYPOINT指令。注: 如果CMD用于为ENTRYPOINT指令提供默认参数,CMD和ENTRYPOINT都应该使用json数组格式。注: exec形式传递json数组,意味着你必须使用双引号(")而不是单引号(’)引用字符注: 与shell形式不同,exec形式不会像,那样调用命令行shell。这意味着没有通常的shell处理。例如,CMD [ “echo”, “$HOME” ]将不会对$HOME做变量替换。如果你想使用shell处理可使用shell形式或直接执行一个shell,例如:[“sh”, “-c”, “echo $HOME”]。当使用exec形式并且直接执行一个shell,在这种情况下shell形式,执行环境变量扩展的是shell,而不是docker。当使用shell或exec格式时,CMD指令设置镜像运行时执行的命令。如果你使用CMD的shell形式,<command>将以/bin/sh -c的形式运行:FROM ubuntuCMD echo “This is a test.” | wc -如果你想不使用shell运行你的<command>就必须以JSON数组的形式表示并且使用可执行文件的完整路径。数组形式是CMD的首选格式。任何独立的参数都必须表达为数组的一个独立的字符串。FROM ubuntuCMD ["/usr/bin/wc","–help"]如果你系统容器每次运行相同的可执行文件,你应该考虑ENTRYPOINT和CMD结合使用。如果用户为docker run指定了参数,那么他们将覆盖CMD中指定的默认参数。注:不要混淆RUN和CMD。RUN实际上运行命令并提交结果;CMD在构建时什么都不执行,只是指定镜像将要执行的命令。EXPOSEEXPOSE <port> [<port>…]EXPOSE指令通知Docker容器运行时监听指定的网络端口。EXPOSE不会使容器端口对宿主机可访问。要那么做,你必须使用-p标记来发布一系列端口或者-P标记发布所有暴露端口。你可以暴露一个端口号并可以使用另一个端口对外发布。要在宿主机系统上设置端口重定向,使用-P标记。Docker网络功能支持网络内创建网络而不需要暴露端口,详细信息请查看功能概述。ADDADD有两种形式:ADD <src>… <dest>ADD ["<src>",… “<dest>”] (路径中包含空格需要这种形式)ADD指令ENTRYPOINTENTRYPOINT有2中形式:ENTRYPOINT [“executable”, “param1”, “param2”] (exec 形式, 首选)ENTRYPOINT command param1 param2 (shell 形式)ENTRYPOINT允许你配置一个将作为可执行程序运行的容器。例如,以下命令将启动一个nginx默认监控80端口:docker run -i -t –rm -p 80:80 nginxdocker run <image>的命令行参数将被追加到以exec形式的ENTRYPOINT所有元素后面,并且覆盖使用CMD指定的所有元素。这使得参数可以被传递给入口, 例如,docker run <image> -d将传递 -d参数给入口。你可以使用docker run –entrypoint标记覆盖ENTRYPOINT执行。shell形式阻止任何CMD或者run的命令行参数被使用,但是有个弊端,你的ENTRYPOINT将被作为/bin/sh -c的一个子命令启动,不能传递信号。这意味着可执行程序不是容器ID为1的进程 - 并且不会接受Unix信号 - 所以你的可执行程序不会接受来自docker stop <container>的SIGTERM。只有Dockerfile最后一个ENTRYPOINT指令会生效。VOLUMEVOLUME ["/data"]VOLUME指令创建一个具有指定名称的挂载点并且将其标记作为从宿主机或者其他容器外部挂载卷。值可以是一个JSON数组,VOLUME ["/var/log"],或者有多参数的纯字符串,比如:VOLUME /var/log或者VOLUME /var/log /var/db。更多Docker客户端的挂载指令信息/例子,移步文档通过卷共享目录。docker run命令使用基础镜像内指定位置存在的任意数据初始化新创建的卷。比如,认为以下Dockerfile片段:FROM ubuntuRUN mkdir /myvolRUN echo “hello world” > /myvol/greetingVOLUME /myvol这个Dockerfile的结果是致使docker run会创建一个新的挂载点/myvol并且拷贝gretting文件到新创建的卷。指定volumes的注意事项关于Dockerfile中的volumes,请注意以下事项。基于Windows容器的Volumes: 当使用基于Windows的容器,容器内volume的目标位置必须是以下之一:一个不存在的或者空目录C盘以外的驱动器:从Dockerfile内更改卷: 如果任何构建步骤在volume声明之后修改了数据,这些修改将会被丢弃。JSON 格式: 列表将会被作为一个JSON数组解析。你必须使用双引号(")而不是单引号(’)将单词包起来。主机目录在容器运行时声明: 主机目录(挂载点)本质上是与主机相关的。这是为了保证镜像的可移植性。因为一个指定的主机目录不能保证在所有的主机上可用。因此,你不能在Dockerfile内挂载一个主机目录。VOLUME指令不支持指定一个主机目录参数。你必须在容器创建或运行时指定挂载点。Exec形式ENTRYPOINT实例你可以使用ENTRYPOINT的exec形式设置相当稳定的默认命令和参数,然后使用CMD任意一种形式设置额外的更可能被修改的其他附加默认值。FROM ubuntuENTRYPOINT [“top”, “-b”]CMD ["-c"]但你运行该容器时,你仅仅可以看到top进程:$ docker run -it –rm –name test top -Htop - 08:25:00 up 7:27, 0 users, load average: 0.00, 0.01, 0.05Threads: 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 stKiB Mem: 2056668 total, 1616832 used, 439836 free, 99352 buffersKiB 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 auxUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 1 2.6 0.1 19752 2352 ? Ss+ 08:24 0:00 top -b -Hroot 7 0.0 0.1 15572 2164 ? R+ 08:25 0:00 ps aux并且你可以使用docker stop test请求top优雅的退出。以下Dockerfile展示了使用ENTRYPOINT在前端运行Apache(例如,PID为1)。FROM debian:stableRUN apt-get update && apt-get install -y –force-yes apache2EXPOSE 80 443VOLUME ["/var/www", “/var/log/apache2”, “/etc/apache2”]ENTRYPOINT ["/usr/sbin/apache2ctl", “-D”, “FOREGROUND”]如果你需要为单个可执行程序写一个启动脚本,你可以使用exec和gosu命令来确保最终执行程序可以收到Unix信号。#!/usr/bin/env bashset -eif [ “$1” = ‘postgres’ ]; then chown -R postgres “$PGDATA” if [ -z “$(ls -A “$PGDATA”)” ]; then gosu postgres initdb fi exec gosu postgres “$@“fiexec “$@“最后,如果你需要在退出时做一些额外的清理(或者与其他容器通信),或者配合执行多个可执行文件,你可能需要确保ENTRYPOINT脚本接受Unix信号,传递他们并做更多工作:#!/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 containertrap “echo TRAPed signal” HUP INT QUIT TERM# start service in background here/usr/sbin/apachectl startecho “[hit enter key to exit] or run ‘docker stop <container>’“read# stop service and clean up hereecho “stopping apache”/usr/sbin/apachectl stopecho “exited $0"如果你使用docker run -it -p 80:80 –name test apache运行该镜像,然后你可以使用docker exec检查容器进程,或者docker top,并且可以通过脚本停止Apache。$ docker exec -it test ps auxUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 1 0.1 0.0 4448 692 ? Ss+ 00:42 0:00 /bin/sh /run.sh 123 cmd cmd2root 19 0.0 0.2 71304 4440 ? Ss 00:42 0:00 /usr/sbin/apache2 -k startwww-data 20 0.2 0.2 360468 6004 ? Sl 00:42 0:00 /usr/sbin/apache2 -k startwww-data 21 0.2 0.2 360468 6000 ? Sl 00:42 0:00 /usr/sbin/apache2 -k startroot 81 0.0 0.1 15572 2140 ? R+ 00:44 0:00 ps aux$ docker top testPID USER COMMAND10035 root {run.sh} /bin/sh /run.sh 123 cmd cmd210054 root /usr/sbin/apache2 -k start10055 33 /usr/sbin/apache2 -k start10056 33 /usr/sbin/apache2 -k start$ /usr/bin/time docker stop testtestreal 0m 0.27suser 0m 0.03ssys 0m 0.03s注:你可以使用–entrypoint覆盖ENTRYPOINT配置,但是这只会将二进制设置为exec(sh -c不会被使用)。注:exec形式被解析为JSON数组,意味着你必须使用双引号(")包裹单词而不是单引号(’)。注:不像shell形式,exec形式并不会调用shell命令。这意味着不会做普通的shell处理。例如,ENTRIPOIN [“echo”, “$HOME”]将不能对$HOME做变量置换。如果你既想shell处理又想使用shell形式或直接执行一shell,例如:ENTRYPOINT [“sh”, “-c”, “echo $HOME”]。当使用exec形式和直接执行shell时,在shell形式这种情况下,是shell做的环境变量扩展,而不是docker。Shell形式ENTRYPOINT实例你可以为ENTRYPOINT指定一个纯文本的字符串,它会以/bin/sh -c的形式运行。这种形式将使用shell处理shell代替shell环境变量,并且将忽略任何CMD或者docker run命令的命令行参数。为了确保docker stop能够正常发出信号给任何长时间运行的ENTRYPOINT可执行文件,您需要记住使用exec启动它:FROM ubuntuENTRYPOINT exec top -b当你启动镜像,你会看到PID为1的进程:$ docker run -it –rm –name test topMem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cachedCPU: 5% usr 0% sys 0% nic 94% idle 0% io 0% irq 0% sirqLoad 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 testtestreal 0m 0.20suser 0m 0.02ssys 0m 0.04s如果你忘记了在ENTRYPOINT开头增加exec:FROM ubuntuENTRYPOINT top -bCMD –ignored-param1你可以启动它(为了下一步给它指定名称):$ docker run -it –name test top –ignored-param2Mem: 1704184K used, 352484K free, 0K shrd, 0K buff, 140621524238337K cachedCPU: 9% usr 2% sys 0% nic 88% idle 0% io 0% irq 0% sirqLoad 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你可以看到top的输出,ENTRYPOINT指定的不是PID 1。如果你接下来执行docker stop test,容器不会被彻底退出 - 超时以后top命令会被发送一个SIGKILL。$ docker exec -it test ps auxPID USER COMMAND 1 root /bin/sh -c top -b cmd cmd2 7 root top -b 8 root ps aux$ /usr/bin/time docker stop testtestreal 0m 10.19suser 0m 0.04ssys 0m 0.03s理解CMD和ENTRYPOINT如何交互CMD和ENTRYPOINT指令都定义了当启动一个容器时执行什么命令。描述他们如何一起工作的规则很少。Dockerfile至少应该指定一个CMD或ENTRYPOINT命令。当容器做一个可执行程序时,ENTRYPOINT应该被定义。CMD应该被用作一种给ENTRYPOINT定义默认参数的方式,或在容器中执行ad-hoc命令的方式。当运行容器时是用了交互参数时,CMD将被会被覆盖。下表显示了对不同ENTRYPOINT / CMD组合执行的命令: No ENTRYPOINTENTRYPOINT exec_entry p1_entryENTRYPOINT [“exec_entry”, “p1_entry”]No CMDerror, not allowed/bin/sh -c exec_entry p1_entryexec_entry p1_entryCMD [“exec_cmd”, “p1_cmd”]exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry exec_cmd p1_cmdCMD [“p1_cmd”, “p2_cmd”]p1_cmd p2_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry p1_cmd p2_cmdCMD exec_cmd p1_cmd/bin/sh -c exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry /bin/sh -c exec_cmd p1_cmd