镜像的定制实际上就是定制每一层所添加的配置、文件。
若是咱们能够把每一层修改、安装、构建、操做的命令都写入一个脚本,用这个脚原本构建、定制镜像,那么以前说起的没法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。
这个脚本就是 Dockerfile。php
Dockerfile 是一个文本文件,其内包含了一条条的 指令(Instruction),每一条指令构建一层,所以每一条指令的内容,就是描述该层应当如何构建。前端
由于每一条指令构建一层,并且每一层构建好后,就不会再变化。为了使镜像尽量地小并且层次清晰,每一层都应该围绕一个特定的目标进行构建,而且在构建结束前,要清理掉全部缓存和其余无关的东西!node
Docker 如今最多只能支持 127 层,尽可能让每一条命令都完成一个完整的目标,不要每条 shell 命令都对应一个 RUN,这是至关糟糕的作法。python
在撰写 Dockerfile 的时候,要常常提醒本身,这并非在写 Shell 脚本,而是在定义每一层该如何构建。mysql
docker commit
用法相似 git commit
,用于将当前容器层的修改,固化成一个新的镜像层。linux
# 1. 首先启动了一个容器 # 2. 经过 exec 命令登入该容器作一些修改 # 3. 可使用下列命令查看容器层的具体改动 docker diff <container id> # 4. 使用 commit 命令提交容器层的改动 docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]] # 模板 docker commit \ --author "Tao Wang <twang2218@gmail.com>" \ --message "修改了默认网页" \ webserver \ nginx:v2 # 5. 使用 history 命令查看镜像的构建历史 docker history nginx:v2
利用 Dockerfile 定制镜像,其实就是在基础镜像上启动一个临时容器,而后在该容器上一条条地运行 Dockerfile 内的指令。android
每跑完一个指令,就将当前的修改固化层一个新的镜像层(这就相似在此时执行 docker commit
)。nginx
指令跑完了,一个分层的镜像也就生成了,这时再清除掉构建用的临时容器。git
Dockerfile 经常使用的有十多个指令:github
FROM
:指定基础镜像LABEL
:镜像的一些标签,如 maintainer/licenceUSER
:能用普通用户,就不要用 root 来作。建议使用普通用户来运行不须要 root 权限的服务。ENV
:设置环境变量,可用于设置 PATH 或者其余环境变量。
--env XXX=xxx
来设置或者修改环境变量。--env
可屡次重复使用ARG
:构建时的参数,只在构建期有用。(而 ENV 就至关于运行期参数)
docker build -build-arg <varname>=<value> xxx
来修改构建参数。WORKDIR
:用于制定下一个镜像层的工做目录(容器内部的),类比 cd xxx
。
ADD/COPY
:都是添加文件的命令,更推荐使用 COPY,ADD 最好只用在 tar.gz/tar.xz
等文件的添加上(会自动解压)。
RUN
:最经常使用的构建指令,会建立新的镜像层,因此最好让每条 RUN 命令都完成一个目标的构建,减小层数。VOLUME
:指定数据层挂载点。
VOLUME ["/data", "/var/log/"]
EXPOSE
:暴露端口。
-p xx:xx
作端口映射,才能和本机的端口绑定!ENTRYPOINT
:镜像的“入口”,也就是启动镜像时会执行的命令。
ENTRYPOINT ["executable", "param1", "param2"]
docker run
命令的全部其余参数,都会被看成 "入口"命令的参数传入!CMD
:在不使用 ENTRYPOINT
的状况下,它就是镜像的默认命令。
CMD ["executable","param1","param2"]
ENTRYPOINT
的状况下,CMD
建议设置为 CMD ["--help"]
,而且紧跟在 ENTRYPOINT
命令以后。ONBUILD
:该指令适合用在基础镜像的构建中。
FROM
一个使用了 ONBUILD
指令的镜像,会先执行该指令,而后才执行 Dockerfile 里面的指令。须要注意的是,如今只有 RUN/ADD/COPY
这三条指令,才会建立新的镜像层。其余的指令只会在构建过程当中建立临时镜像层,它们不会出如今最终的镜像中。
所谓定制镜像,那必定是以一个镜像为基础,在其上进行定制。
在 Docker Hub 上有很是多的高质量的官方镜像,有能够直接拿来使用的服务类的镜像,如 nginx
、redis
、mongo
、mysql
、httpd
、php
、tomcat
等;也有一些方便开发、构建、运行各类语言应用的镜像,如 node
、openjdk
、python
、ruby
、golang
等。能够在其中寻找一个最符合咱们最终目标的镜像为基础镜像进行定制。
若是没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操做系统镜像,如 ubuntu
、debian
、centos
、fedora
、alpine
等,这些操做系统的软件库为咱们提供了更广阔的扩展空间。
除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch
。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。
FROM scratch ...
若是你以 scratch
为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将做为镜像第一层开始存在。
不以任何系统为基础,直接将可执行文件复制进镜像的作法并不罕见,好比 swarm
、coreos/etcd
。对于 Linux 下静态编译的程序来讲,并不须要有操做系统提供运行时支持,所需的一切库都已经在可执行文件里了,所以直接 FROM scratch
会让镜像体积更加小巧。使用 Go 语言 开发的应用不少会使用这种方式来制做镜像,这也是为何有人认为 Go 是特别适合容器微服务架构的语言的缘由之一。
docker build --tag <image name>:tag .
中的 .
并不只仅指 Dockerfile 的路径!
build 命令的最后一个参数,是镜像构建上下文的路径,这个路径能够是文件夹路径,能够是一个 tar 压缩包,也能够是一个 url,甚至 git 仓库地址也是支持的。
Docker 是 Client/Server 模式的程序,build 命令会将该 [文件夹/tar 压缩包/url] 的内容发送给 Server 端(Docker 引擎)用于构建,
所以后面构建中的 COPY/ADD
指令,只能使用上下文里面的内容,更不支持 ../xxx
这样的路径。
多阶段构建中,不一样的阶段使用不一样的基础镜像(所以有多个 FROM),前面的阶段大都是为了生成一些须要的文件(先后端编译等)。
在最后一个阶段,使用 COPY
将须要的文件从前几个阶段生成的镜像中 COPY 过来,这样就获得了一个只包含运行时的镜像。
前端编译基于前端相关的镜像,后端用后端的编译镜像,最后放到只包含运行时的镜像里。
FROM golang:1.9-alpine as builder RUN apk --no-cache add git WORKDIR /go/src/github.com/go/helloworld/ RUN go get -d -v github.com/go-sql-driver/mysql COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest as prod RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=0 /go/src/github.com/go/helloworld/app . CMD ["./app"]
一些 Jenkins 构建,可供参考/使用的 Dockerfile: