Dockerfile必须放在工作目录中,而且必须开头大写,一般都叫做Docerfile。
先甩一个小demo:
FROM ubuntu:latest
LABEL author="xxx"
LABEL description="xxxxx"
RUN apt-get install python
COPY hello-world.py
CMD python hello-world.py
# ###################################
docker build -t xxx . # 镜像的名字
# 也可以标记个名字
docker tag image_id tag_name
docker tag 镜像ID xxxx
docker build -t xxxx
再来一个demo
FROM python:2.7
LABEL maintainer="XX"
RUN pip install flask
COPY app.py /app # 本地写好的app.py
WORKDIR /app/
ECPOSE 5000
CMD ["python","app.py"]
构建上下文
构建上下文就是特定路径或URL处提供的一个或一组文件。
待议
docker build https://github.com/xxx/xxx .
git#mybranch
dockerignore
忽略代码清单是通过一个名为.dockerignore的文件提供的,当Docker CLI发现这个文件时,它就会修改上下文以便排除该文件中所提供的文件/模式。
下面是.dockerignore文件,它排除了temp目录,.git 目录,.DS Store目录
*/temp*
.DS_Store
.git
粗鄙插入
shell 格式
RUN apt-get install -y vim # 加个-y, 省得系统问你是不是安装它找到的这个包
CMD echo "xxx"
ENTRYPOINT echo "xxxx"
exec格式
RUN ["apt-get","install","-y","vim"]
CMD ["/bin/echo","xxxx"]
ENTRYPOINT ["/bin/echo", "xxxx"] # echo命令在/bin/echo 目录下
- 在shell格式下,命令实在shell中运行的,命令本身是最为参数来提供的。这一格式提供了一个shell环境,其中可以使用shell变量,子命令,命令管道及命令链。
- 在exex格式下,命令不会调用shell。这意味着常规的shell处理($VARIABLE替换、管道等)将不再有效
- 例如,shelll 格式下 CMD /bin/httpd -f -h ${WEB_DOC_ROOT} (-f 前台, -h 指定host,我猜的,猜错也不重要。),后面执行的命令是shell的子进程,shell进程的pid是1。
- exec格式, CMD ["/bin/httpd", "-f", "-h ${WEB_DOC_ROOT"] 会报错,说找不到这个变量,因为默认不是shell的子进程,这个命令的pid是1,但是变量是属于shell的,所以这个exec格式会报错
- CMD ["/bin/shell","-c","/bin/httpd","-f","-h ${WEB_DOC_ROOT"] 就可以了
Dockerfile 指令
- FROM
- WORKDIR
- ADD
- COPY
- RUN
- CMD
- ENTRYPOINT
- ENV
- VILUME
- LABEL
- EXPOSE
1 FROM
FROM <image> [AS <name>]
FROM <image[:tag]> [AS <name>]
每个镜像都要个基础镜像作为开始。FROM 指令会告知Docker引擎将哪个基础镜像用于后续的指令。
当要自己制作镜像,且不依赖基础镜像, 要这样写 FROM scratch
2 WOEKDIR
WORKDIR指令会设置用于RUN CMD ENTRYPOINT COPY 和ADD 指令的当前工作目录
WORKDIR /path/to/directory
FROM ubuntu:latest
WORKDIR /usr
CMD pwd
docker build -t xxxx .
docker run xxxx
显示的结果
/usr
FORM ubuntu:latest
WORKDIR /usr
WORKDIR src
WORKDIR app
CMD pwd
docker build -t xxx .
docker run xxx
显示的结果
/usr/src/app
3 ADD和COPY
ADD <source> <destination>
COPY <source> <destination> 推荐
src必须是build的上下文路径,docker build -t xxxx . 。 这个. 是当前目录,实际不是传的Dockerfile文件,而是传的上下文的路径。
它们大体上类似。允许将文件从宿主机传递到容器的文件系统。COPY支持基础的文件复制到容器的功能,而ADD支持类似tarball文件自动提取和远程URL这样的特性。
ADD xx.tar,gz / ADD支持解压缩。把原文件的压缩包自动挤压到镜像的根目录
对于Linux容器的Dockerfile而言, ADD和COPY, 默认UID和GID都是0,所以可以如下设置
ADD --chown=<user>:<group> <source> <destination>
COPY --chown=<user>:<group> <source> <destination>
支持通配符
ADD *.py /apps/
COPY *.py /apps/
yum.repos.d 是个路径,下面有很多文件,
COPY yum.repos.d /etc/yum.repos.d/ 要这么写,而且后面要以/结尾。
关于ADD和COPY的几点注意:
- 如果镜像中没有<destination>,则会创建它
- 如果<source>是个目录。如 var/foo/fuckit.txt 实际上复制到<destination>的是fuckit.txt而不是var/foo/fuckit.txt
- 对于所有新创建的文件/目录,且UID和GID都是0,可以用--chown来标记
- 如果文件/目录包含特殊字符,那么它们需要被转义
- <destination>可以是绝对路径,也可以是相对路径。在相对路径下,其相对性会根据WORKDIR指令所设置的路径来推断
- 如果<destination>不是以反斜杠结尾,那么它将视为一个文件,并且<source>的内容将被写入<destination>中。
- 如果<source>被指定为一种通配符模式,那么<destination>就必须是一个目录(以反斜杠结尾),否则构建会失败
对于ADD的一些注意
- 如果<source>是一个URL, 而<destination> 不是一个目录(不以反斜杠结尾),那么URL下载的文件将会复制到<destination>
- 如果<source>是一个URL,而<destination> 是一个目录(以反斜杠结尾), 那么会根据URL推断文件名,那后下载该文件并将其复制到<destination>/<filename>
4 RUN
RUN指令会在当前镜像上的一个新层中执行所有命令,并会创建一个新层用于执行Dockerfile中的后续步骤
RUN <command> shell 格式
RUN ["executable", "parameter 1", "paramter 2"] exec格式
在shell格式,该命令在一个shell中运行,并且需要一个命令作为参数。在其中使用shell变量、子命令和管道命令及命令链都是可以的。
RUN echo `uname -rv` > $HOME/kernel-info
但在exec格式中是无法完成的,RUN是一个构建时的命令,因此, 它是在构建Docker镜像时运行的,而非是镜像运行时才运行的。
Docker 推荐将多个RUN命令链接成单条命令:如下
RUN apt-get update
RUN apt-get install foo
RUN apt-get install bar
RUN apt-get install baz
更好的做法是, 写成一条, 可以减少层数
RUN apt-get update && RUN apt-get install -y \
foo \
bar \
baz
5 CMD 和 ENTRYPOINT
CMD和ENTRYPOINT指令会定义在运行容器时执行哪个命令。ENTRYPOINT让容器以应用程序或者服务的形式运行。
CMD ["executable","param1","param2"] exec格式
CMD command param1 param2 shell格式
ENTRYPOINT ["executable","param1","param2"] exec格式
ENTRYPOINT command param1 param2 shell格式
几点注意
- 容器启动时默认执行的命令
- docker run 指定了其他命令,CMD被忽略
- 如果定义多个CMD, 只有一个会被执行
- 而ENTRYPOINT里的命令一定会被执行
FROM centos
ENV name Docker
CMD echo "xxx"
(CMD echo "OOOO")
执行 docker run [image] 会打印 xxx
执行 docker run -it [image] /bin/bash 会执行/bin/bash,不会打印xxx
如果Dockerfile里面有两个CMD, 只会执行最后一个,这里只会打印OOOO
下面是个小demo1
FROM ubuntu:latest
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
CMD curl
执行run
docker run xxx wttr.in (给curl传的参数)
还会报错,正如docker run之后的参数会重写CMD指令,要让重写一切正常,还需要传入可执行文件。
这样才能正常运行
docker run xxx curl -s wttr.in
每次这样写很麻烦,这就用到了ENTRTYPOINT
FROM ubuntu:latest
RUN apt-get update && \
apt=get install -y curl && \
rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["curl","-s"]
docker run xxx wttr.in
小demo2
shell格式
FROM centos
ENV name Docker
ENTRYPOINT echo "$name"
docker build -t xx/xx
docker run xx/xx
结果是
hello docker
exec格式
没打印出变量
FROM centos
ENV name Docker
ENTRYPOINT ["/bin/echo","heollo $name"]
docker -t xx/xx
docker run xx/xx
结果是hello $name
要这样写
FROM centos
ENV name Docker
ENTRTPOINT ["/bin/echo","-c","hello $name"]
docker build -t xx/xx
docker run xx/xx
结果是
hello docker
6 ENV
ENV 指令会将环境变量设置到镜像,
ENV <key> <value>
ENV <key>=<value> ...
在第一种格式中,<key>之后的整个字符串将被视作值,包括其中的空白字符。在这种格式中,每一行只能设置一个变量。
在第二种格式中,可以一次性设置多个变量,使用等号将值赋给键。
FROM ubuntu:latest
ENV LOGS_DIR="/var/log" \
xxx="xxxx"
ENV APPS_DIR /apps/
docker build -t xxx/xx .
可以使用以下命令来检验环境变量
docker inspect xxx/xx | jq .[0].Config.ENV
[
一堆东西
]
可以在运行容器时通过-e标记来修改为容器定义的环境变量。注意build后就可以查看环境变量了
docker run -it -e LOGS_DIR="/logs" xx/xxx
可以这样确定修改后的值
printenv | grep LOGS
7 VOLUME
VOLUME指令会告知Docker在宿主机创建一个目录并将其挂载到该指令中所指定的路径
VOLUME /var/logs/nginx
这个指令会告知Docker在Docker宿主机上创建一个目录,并且指向指定目录,从容器指向宿主机目录。
(其实还不知道咋用)(现在知道是啥子了)
8 EXPOSE
EXPOSE 指令会告知Docker,容器会在运行时监听指定的网络端口。
EXPOSE <port> [<port>/<protocol>]
例如开放80端口
EXPOSE 80
在tcp和udp开放53端口
EXPOSE 53/tcp
EXPOSE 53/udp
小demo
FROM nginx:alpone
EXPOSE 80
docker build -t xxx:xx .
运行这个容器,必须提供与之映射的宿主机端口。我们要将宿主机上的8080端口映射到容器的80端口。
docker run -d -p 8080:80 xxx:xx
-d 后台运行, -p 指定映射端口
9 LABEL
LABEL <key>=<value> <key>=<value> <key>=<value>
这个真的没啥说的,相当于注释,只要格式对,咋写都不报错。
编写Dockerfile的原则和建议
(有很多一知半解, 希望随着继地深入学习能够迎刃而解)
- 容器的生命周期应该时短暂的。为此,我们应该能够对容器进行最小化设置和配置的情况下,随时停止、销毁和重启容器。
- 使用多阶段构建
- 跳过不必要的包
- 最小化层的数量,只有RUN、COPY和ADD指令可以创建层。
关于多阶段构建
多阶段构建尤其适用于构建需要一些额外依赖项但运行时却不需要这些依赖项的应用程序镜像。构建镜像可以包含生成的二进制或部件所需的构建工具,并且在第二阶段,这些部件可以被复制到运行时镜像中,因而大幅减少了运行的时镜像的大小。(感觉很有道理,但实际上我看不太懂)
书上的例子:
demo1
Dockfile
FROM python:3-alpine
LABEL author="XXXX"
LABEL description="ssss"
COPY hello-world.py /app/
ENV NAME=desky # 指定环境变量,py脚本能拿到变量
CMD python3 /app/hello-world.py
hello-world.py
#!/usr/bin/env python3
from os import getenv
if getenv('NAME') is None:
name='World!'
else:
name=getenv('NAME')
print("Hello {}".format(name))
docker build -t xxx:xxx .
docker run xxx:xxx
返回的结果是
Hello, desky
可以在运行时重写环境变量,
docker run -e NAME=aki xxx:xxx
返回的结果
Hello,John
demo2
FROM python:3
COPY requirement.txt
RUN pip install -r requirement.txt # 就是根据这个文件安装py包
docker build -t xxx/xxx .
docker images xxx/xxx # 查看指定的镜像的详细信息
大小是698M
使用多阶段构建
FROM python:3 as python-base
COPY requirements.txt .
RUN pip install -r requirements.txt
FROM python-base
COPY --from=python-base /root/.cache /root/.cache
COPY --form=python-base requirements.txt .
RUN pip install -r requirements.txt && rm -rf /root/.cache
该Dockefile的差异之处在于,其中有多个FROM语句,这表明有多个不同的阶段,在第一个阶段中,我们使用python3镜像构建了所需的包,这个镜像中具有必要的构建工具。在第二个阶段,我们复制了第一个阶段所安装的文件,重新安装了他们(注意,pip会抓取缓存文件,但不会再次构建它们),然后删除了缓存的安装文件。
最后查看镜像大小,才99M。