我们已经看过一些Dockerfile中可用的指令,如RUN和EXPOSE。
实际上还可以在Dockerfile中放入很多其他指令,如CMD、ENTRYPOINT、ADD、COPY、VOLUME、WORKDIR、USER、ONBUILD和ENV等。
可在https://docs.docker.com/engine/reference/builder/#arg查看Dockerfile中可以使用的全部指令清单。下面将介绍这些指令的具体功能。
CMD
CMD指令用于制定一个容器启动时要运行的命令。
这有点类似于RUN指令,只是RUN指令是指定镜像被构建时要运行的命令,而CMD是指定容器被启动时要运行的命令。
三种格式分别为:
- CMD ["executable", "param1", "param2"] 使用exec执行,推荐方式;
- CMD command param1 param2 在/bin/sh中执行,提供给需要交互的应用;
- CMD ["param1", "param2"] 提供给ENTRYPOINT的默认参数;
这和使用docker run命令启动容器时指定要运行的命令非常类似:
# docker run -it test/static_web /bin/true
## 上面的命令等效于在Dockerfile中使用如下代码
CMD ["/bin/true"]
也可以为要运行的命令指定参数:
## 这里将 -l 参数传递给 /bin/bash 命令
CMD ["/bin/bash","-l"]
## 需要注意的是,要运行的命令是存放在一个数组结构中。
使用docker run命令可以覆盖CMD指令。如果我们在Dockerfile里指定了CMD指令,而同时在docker run命令行中也指定了要运行的命令,命令行中指定的命令会覆盖Dockerfile中的CMD指令。
## 假设我们Dockerfile文件中加入CMD
#vi Dockerfile
# Version: 0.0.1
FROM ubuntu:14.04
MAINTAINER Bourbon Tian "bourbon@1mcloud.com"
ENV REFRESHED_AT 2017-05-18
RUN apt-get update
RUN apt-get install -y nginx
RUN echo 'Hi, I am in your container' > /usr/share/nginx/html/index.html
RUN sed -i '22d' /etc/nginx/sites-enabled/default
EXPOSE 80
CMD ["/bin/bash"]
## 重新构建一个新的镜像,镜像名为static_test,并基于这个镜像启动一个新的容器
# docker build -t="test/static_test" .
# docker run -t -i test/static_test
root@40786fce97d2:/# exit
## 这里docker run命令的末尾我们并没有指定要运行什么命令。
## 实际上,Docker使用了CMD中的命令。
## 如果我们指定了要运行的命令,如这里运行的/bin/ps,容器并没有启动shell
## 通过命令行参数覆盖了CMD中指定的命令,容器运行后列出正在运行的进程列表,之后停止了容器。
# docker run -t -i test/static_test /bin/ps
PID TTY TIME CMD
1 ? 00:00:00 ps
在Dockerfile中只能指定一条CMD指令。如果指定了多条,也只有最后一条CMD指令会被使用。
ENTRYPOINT
ENTRYPOINT指令与CMD指令非常类似,区别在于我们可以在docker run命令行中覆盖CMD命令,而ENTRYPOINT指令提供的命令则不容易(如果确实需要,可以在运行时通过docker run的--entrypoint标志覆盖)在启动时被覆盖。实际上,docker run命令行中指定的任何参数都会被当做参数再次传递给ENTRYPOINT指令中指定的命令。
有两种格式:
- ENTRYPOINT ["executable", "param1", "param2"]
- ENTRYPOINT command param1 param2(shell中执行)
ENTRYPOINT ["/usr/sbin/nginx"]
## 通过数组的方式为命令指定相应的参数
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
重新构建镜像,添加ENTRYPOINT ["/usr/sbin/nginx"]指令:
ENTRYPOINT ["/usr/sbin/nginx"]
# docker build -t="test/static_web" .
# docker run -d -p 80 test/static_web -g "daemon off;"
207886d3d33e78ce3bafe07ae3b5589e37c29e4691f23c77a61c6a0e56d9a95a
# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
207886d3d33e test/static_web "/usr/sbin/nginx -g 10 seconds ago Up 9 seconds 0.0.0.0:32774->80/tcp jolly_kilby
在这里-g "daemon off;"参数传递给了ENTRYPOINT指定的命令,在这里命令为/usr/sbin/nginx -g "daemon off;"。我们也可以组合ENTRYPOINT和CMD指令完成一些巧妙的工作。
ENTRYPOINT ["/usr/sbin/nginx"]
CMD ["-h"]
## 如果启动容器时不指定任何参数,CMD指令会被传递,显示Nginx帮助信息;
## 同时也支持通过docker run命令行为该命令指令指定可覆盖的选项或标志。
每个Dockerfile中只能有一个ENTRYPOINT,当指定多个时,只有最后一个起效。
WORKDIR
WORKDIR指令用来在从镜像创建一个新容器时,在容器内部设置一个工作目录,ENTRYPOINT和/或CMD指定的程序会在这个目录下执行。
我们可以使用该指令为Dockerfile中后续的一些列指令设置工作目录,也可以为最终的容器设置工作目录。
格式为: WORKDIR /path/to/workdir
## 为特定的指令设置不同的工作目录
WORKDIR /opt/webapp/db
## 将工作目录切换为/opt/webapp/db 后运行bundle install
RUN bundle install
WORKDIR /opt/webapp
## 将工作目录切换为/opt/webapp后设置ENTRYPOINT指令来启动rackup命令。
ENTRYPOINT ["rackup"]
通过-w标志在运行时覆盖工作目录,该命令会将容器内的工作目录设置为/var/log。
docker run -it -w /var/log ubuntu pwd
/var/log
ENV
ENV指令用来在镜像构件过程中设置环境变量,格式为: ENV <key> <value>
ENV RVM_PATH /home/rvm
这个新的环境变量可以在后续的任何RUN指令中使用,这就如同在命令前面指定了环境变量前缀一样
RUN gem install unicorn
## 这条RUN指令就会以如下的方式执行
RVM_PATH=/home/rvm/ gem install unicorn
## 也能在其他命令中直接使用这些环境变量
ENV TARGET_DIR /opt/app
WORKDIR $TARGET_DIR
## 这里设定了一个TARGET_DIR的环境变量,并在WORKDIR中调用了这个环境变量
## 所以这里的WORKDIR指令的值会被设置为/opt/app
这些环境变量也会被持久保存到从我们的镜像创建的任何容器中。如果我们在使用ENV指令构建容的容器中运行env命令,将会看到我们所设置的环境变量。
也通过docker run命令行的 -e 标志来传递环境变量。这些环境变量只会在运行时有效
# docker run -it -e "WEB_PORT=8080" ubuntu env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=2688f9db5f34
TERM=xterm
WEB_PORT=8080
HOME=/root
可以看到,在容器中WEB_PORT环境变量被设置为了8080。
USER
USER指令用来指定该镜像会以什么样的用户去运行
格式为:USER daemon
USER nginx
基于该镜像启动的容器会以nginx用的身份来运行。我们可以指定用户名或UID以及组或GID,甚至是两者结合。
USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group
也可以在docker run命令中通过-u选项来覆盖该指令指定的值,如果不通过USER指令指定用户,默认用户为root。
VOLUME
VOLUME指令用来向基于镜像创建的容器添加卷。一个卷是可以存在于一个或者多个容器内的特定的目录,这个目录可以绕过联合文件系统,并提供如下共享数据或者对数据进行持久化的功能。
- 卷可以在容器间共享和重用。
- 一个容器可以不是必须和其他容器共享卷。
- 对卷的修改是立时生效的。
- 对卷的修改不会对更新镜像产生影响。
- 卷会一直存在直到没有任何容器再使用它。
卷功能让我们可以将数据(如源代码)、数据库或者其他内容添加到镜像中而不是将这些内容提交到镜像中,并且允许我们在多个容器间共享这些内容。我们可以利用此功能来测试容器和内部的应用程序代码,管理日志,或者处理容器内部的数据库。
格式为: VOLUME ["/data"]
也可以通过指定数组的方式指定多个卷
格式为: VOLUME ["/data1", "/data2"]
ADD
ADD指令用来将构建环境下的文件和目录复制到镜像中。格式为:ADD <src> <dest>
ADD software.lic /opt/application/software.lic
这里的ADD指令将会将构建目录下的software.lic文件复制到镜像中的/opt/application/software.lic。指向源文件的位置参数可以是一个URL,或者构建上下文或环境中文件名或者目录。不能对构建目录或者上下文之外的文件进行ADD操作。
在ADD文件时,Docker通过目的地址参数末尾的字符来判断文件源是目录还是文件。以/结尾,那么Docker就认为是一个目录,如果不是以/结尾,那么Docker就认为是个文件。
源文件也可以使用URL的格式:
ADD http://wordpress.org/latest.zip /root/wordpress.zip
最后值得一提的是,ADD在处理本地归档文件(tar archive)时还有一些小魔法。如果将一个归档文件(合法的归档文件包括gzip、bzip2、xz)指定为源文件,Docker会自动将归档文件解开(unpack)
ADD latest.tar.gz /var/www/wordpress/
这里会将归档文件latest.tar.gz 解压到/var/www/wordpress/目录下。如果目的位置不存在的话,Docker将会为我们创建这个全路径,包括路径中的任何目录。新创建的文件和目录的模式为0755,并且UID和GID都是0。
ADD指令会使得构建缓存变得无效,如果通过ADD指令向镜像添加一个文件或者目录,那么这将使Dockerfile中的后续指令都不能继续使用之前的构建缓存。
COPY
COPY指令非常类似于ADD,它们根本的不同是COPY只关心在构建上下文中复制本地文件,而不会去做文件提取(extraction)和解压(decompression)的工作。
格式为:COPY <src> <dest>
COPY conf.d/ /etc/apache2/
这条指令会把本地conf.d目录中的文件复制到/etc/apache2/目录中。文件源路径必须是一个与当前构建环境相对的文件或者目录,本地文件都放到和Dockerfile同一个目录下。不能复制该目录之外的任何文件,因为构建环境将会上传到Docker守护进程,而复制是在Docker守护进程中进行的,任何位于构建环境之外的东西都不可用。COPY指令的目的位置则必须是容器内部的一个绝对路径。
任何由指定创建的文件或者目录的UID和GID都会设置为0。如果目的位置不存在,Docker将会自动创建所有需要的目录结构。
ONBUILD
ONBUILD指令能为镜像添加触发器(trigger)。当一个镜像被用作其他镜像的基础镜像,该镜像的触发器将会被执行。触发器会在构建过程中插入新指令,我们可以认为这些指令是紧跟在FROM之后指定的。触发器可以是任何构建指令。
格式为:ONBUILD [INSTRUCTION]
比如,我们为apache镜像构建一个全新的Dockerfile,该镜像名为static_apache2
# Version: 0.0.1
FROM ubuntu:14.04
MAINTAINER Bourbon Tian "bourbon@1mcloud.com"
RUN apt-get update
RUN apt-get install -y apache2
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
ONBUILD ADD . /var/www/
EXPOSE 80
ENTRYPOINT ["/usr/sbin/apache2"]
CMD ["-D", "FOREGROUND"]
# docker build -t="test/static_apache2" .
在新构建的镜像中包含一条ONBUILD指令,该指令会使用ADD指令将构建环境所在的目录下的内容全部添加到镜像中的/var/www/目录下。我们可以将这个Dockerfile作为一个通用的Web应用程序的模板,可以基于这个模板来构建Web应用程序。
我们可以通过构建一个名为webapp的镜像来看看如何使用镜像模板功能:
FROM test/static_apache2
MAINTAINER Bourbon Tian "bourbon@1mcloud.com"
ENV APPLICATION_name webapp
ENV ENVIRONMENT development
# docker build -t="test/webapp" .
Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon
Step 0 : FROM test/static_apache2
# Executing 1 build triggers
Trigger 0, ADD . /var/www/
Step 0 : ADD . /var/www/
---> 92a790340715
Removing intermediate container cf7accb5ca5b
Step 1 : MAINTAINER Bourbon Tian "bourbon@1mcloud.com"
...
Successfully built 36ae30d2e972
可以看到,在FROM指令之后,Docker插入了一条ADD指令,这条ADD指令就在ONBUILD触发器中指定的。执行完该ADD指令后,Docker才会继续执行构建文件中的后续指令。这种机制使我每次都会将本地源代码添加到镜像,就像上面我们做到的那样,也支持我为不同的应用程序进行一些特定的配置或者设置构建信息。这时,可以将test/static_apache2当做一个镜像模板。
ONBUILD触发器会按照在父镜像中指定的顺序执行,并且只能被继承一次。如果再基于test/webapp构建一个镜像,则新镜像不会继承test/static_apache2中的触发器。
值得注意的是,有好几条指令是不能用在ONBUILD指令中,其中包括FROM、MAINTAINER和ONBUILD本身。之所以这么规定是为了防止在Dockerfile构建过程中产生递归调用的问题。