参考文档:
官方文档:https://docs.docker.com/engine/reference/builder/
菜鸟教程:https://www.runoob.com/docker/docker-dockerfile.html
博客园:https://www.cnblogs.com/panwenbin-logs/p/8007348.html
什么是Dockerfile
Dockerfile 是一个用来构建Docker镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。
如何通过Dockerfile构建镜像
通过docker build
命令可以从文本、http、stdin等方式,读取Dockerfile来构建镜像,可以参考官方文档查看详细使用方法,常用方法:
$ docker build -t nginx_test:1.0 -f ./My_Dockerfile .
-t
指定标签,-f
指定具体所使用的Dockerfile,其中最后一个.
表示使用当前目录作为构建目录(环境)。
注意:最好使用单独的目录来构建镜像,目录里包含所需的文件,不要使用/
作为构建环境,因为构建时会读取整个构建目录环境,以下是官方说明
使用docker images nginx_test
查看刚刚构建的镜像
编写Dockerfile
上面介绍了如何使用Dockerfile来构建一个镜像,下面详细介绍一下如何来编写这个Dockerfile,以及各个指令的作用
Dockerfile最基本的格式如下:
# 备注信息
指令 参数
与shell类似使用#
作为注释符,以\
结尾用作断行。指令不区分大小写。但是,约定是小写的。docker build
以从上到下的顺序运行Dockerfile的指令。为了指定基本映像,第一条指令必须是FROM。
Dockerfile指令集
Dockerfile主要组成部分:
基础镜像信息 FROM centos:6.8
制作镜像操作指令RUN yum insatll openssh-server -y
容器启动时执行指令 CMD [“/bin/bash”]
Dockerfile常用指令:
FROM 指定基础镜像
MAINTAINER 维护者信息(已弃用)
LABEL 标签信息(用于取代MAINTAINER)
RUN 执行命令,结果会保存到层
ADD 把本地的文件或目录或远程的URL资源,复制到镜像文件系统(可以理解为增强的COPY)
WORKDIR 进入某个目录(类似cd命令)
VOLUME 设置卷,挂载主机目录
EXPOSE 指定对外的端口
CMD 默认的容器启动执行命令
Dockerfile其他指令:
COPY 复制本地文件到镜像文件系统
ENV 设置环境变量,在容器构建和运行的容器都可使用
ARG 构建时变量,只有在使用ARG指令在Dockerfile中“定义”之时才可用,直到构建映像为止。
ENTRYPOINT 容器启动后执行的命令
指令详细用法
FROM指定基础镜像
示例
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
tag
或digest
值是可选的。如果您省略其中任何一个,构建器默认使用一个latest
标签。使用AS可以重命名,在FROM
或COPY --from=<name>
中使用。
配合ARG指令使用:
ARG CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD /code/run-app
FROM extras:${CODE_VERSION}
CMD /code/run-extras
RUN执行命令
示例
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
RUN ["/bin/bash", "-c", "echo hello"] # 推荐使用EXEC格式
CMD启动容器默认命令
示例
CMD ["executable","param1","param2"] # 推荐使用EXEC格式
CMD ["param1","param2"] # 作为ENTRYPOINT的默认参数
CMD command param1 param2 # shell格式
ENTRYPOINT启动后执行命令
默认情况下,Docker 会为你提供一个隐含的 ENTRYPOINT,即:/bin/sh -c
示例
ENTRYPOINT ["executable", "param1", "param2"] # 推荐使用EXEC格式
ENTRYPOINT command param1 param2
CMD和ENTRYPOINT的使用说明
- Dockerfile至少要有
CMD
或ENTRYPOINT
命令其中一个。 ENTRYPOINT
应在将容器用作可执行文件时进行定义。(让容器变成像命令一样使用)CMD
应该用作定义ENTRYPOINT
命令的默认参数或在容器中执行临时命令的一种方式。CMD
使用替代参数运行容器时将被覆盖。
当指定了 ENTRYPOINT 后, CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:
<ENTRYPOINT> "<CMD>"
可以参考这篇博客:https://www.cnblogs.com/reachos/p/8609025.html
下表显示了针对不同ENTRYPOINT
/CMD
组合执行的命令:(官方文档)
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 |
示例
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."
使用docker image inspect --format='' myimage
可以查看镜像的标签信息
EXPOSE暴露端口
示例
EXPOSE <port> [<port>/<protocol>...]
EXPOSE 80/tcp
EXPOSE 80/udp
在启动容器时,可以使用-p
来覆盖暴露的端口,使用-P
来随机暴露端口
docker run -p 80:80/tcp -p 80:80/udp ...
查看镜像需要暴露的端口:
docker inspect --format='{{.Config.ExposedPorts}}' nginx
map[80/tcp:{}]
查看当前容器的端口映射:
docker port <container_id>
ENV环境变量
示例
# 定义单个变量
ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy
ENV MY_VAR my-value
# 同时定义多个变量
ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
MY_CAT=fluffy
使用docker run --env <key>=<value>
可以覆盖环境变量
ARG构建时变量
示例
ARG user1=someuser # 设置user1默认值为someuser
使用docker build --build-arg user=what_user .
可以覆盖默认的ARG设置
ADD复制文件(建议用于压缩文件和远程文件的复制)
示例
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
# 使用通配符
ADD hom* /mydir/
ADD hom?.txt /mydir/
# 使用url
ADD http://test.com/1.txt /mydir/
# 指定UID:GID
ADD --chown=10:11 files* /somedir/
# 自动解压src文件
ADD test.tar.gz /mydir/
COPY复制文件(建议用于本地文件的复制)
示例
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
# 使用通配符
COPY hom* /mydir/
COPY hom?.txt /mydir/
# 指定UID:GID
COPY --chown=10:11 files* /somedir/
VOLUME挂载卷
挂载卷是实现数据的持久化即数据不随着Container的结束而结束,需要将数据从宿主机挂载到容器中。
示例
VOLUME ["/data"]
VOLUME /myvol
查看镜像或容器挂载设置
docker inspect --format='{{.Mounts}}' <object>
通过-v
来设置自定义挂载方式
docker volume create myvol // 创建一个自定义容器卷
docker volume ls // 查看所有容器卷
docker volume inspect myvol // 查看指定容器卷详情信息
[
{
"CreatedAt": "2021-09-16T12:19:36+08:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/myvol/_data",
"Name": "myvol",
"Options": {},
"Scope": "local"
}
]
# volumes容器卷方式,默认挂载目录是/var/lib/docker/volumes
docker run -it -v myvol:/myvol nginx # 把自定义容器卷myvol挂载到容器的/myvol目录
# bind mounts方式
docker run -it -v /data/myvol:/myvol nginx # 把宿主机的/data/myvol目录挂载到容器的/myvol目录
# 使用tmpfs挂载,参考https://www.cnblogs.com/benjamin77/p/9513429.html
docker run --mount type=tmpfs,destination=/myvol,tmpfs-mode=1770 -it nginx sh
USER 指定当前用户
示例
USER <user>[:<group>]
USER <UID>[:<GID>]
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]
WORKDIR工作目录
示例
WORKDIR /path/to/workdir
类似于cd命令,可以在 docker run命令中用 -w参数覆盖掉WORKDIR指令的设置:
docker run -w / myimage
ONBUILD父容器触发器
ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。如果是再利用子镜像构造新的镜像时,那个ONBUILD指令就无效了,也就是说只能再构建子镜像中执行,对孙子镜像构建无效。
示例
ONBUILD <INSTRUCTION>
FROM ubuntu
MAINTAINER hello
ONBUILD RUN mkdir mydir
HEALTHCHECK健康检查
示例
HEALTHCHECK [OPTIONS] CMD command # 通过运行命令进行健康检查
HEALTHCHECK NONE # 关闭之前的健康检查
# 参数
--interval=<间隔>:两次健康检查的间隔,默认为 30 秒;
--timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
--start-period=<时间>:开始监控检查的时间,默认 0 秒;
--retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。
# 退出状态
0: success - 健康可用
1: unhealthy - 不能正常工作
2: reserved - 保留,暂不使用
HEALTHCHECK --interval=5m --timeout=3s CMD curl -f http://localhost/ || exit 1
可以使用docker inspect
查看健康检查的结果
Dockerfile最佳实践
Docker 镜像由只读层组成,每个层代表一个 Dockerfile 指令。这些层是堆叠的,每一层都是前一层变化的增量。
使用独立的上下文环境来构建镜像
使用一个独立的目录,包含所有构建需要的文件:
mkdir myproject && cd myproject
echo "hello" > hello
echo -e "FROM busybox\nCOPY /hello /\nRUN cat /hello" > Dockerfile
docker build -t helloapp:v1 .
通过stdin构建镜像
docker build -<<EOF
FROM busybox
RUN echo "hello world"
EOF
排除不必要的文件
使用 .dockerignore 排除不必要的文件,参考https://docs.docker.com/engine/reference/builder/#dockerignore-file
使用多阶段构建
因为镜像是在构建过程的最后阶段构建的,所以您可以通过利用构建缓存来最小化镜像层。
# syntax=docker/dockerfile:1
FROM golang:1.16-alpine AS build
# Install tools required for project
# Run `docker build --no-cache .` to update dependencies
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep
# List project dependencies with Gopkg.toml and Gopkg.lock
# These layers are only re-built when Gopkg files are updated
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
# Install library dependencies
RUN dep ensure -vendor-only
# Copy the entire project and build it
# This layer is rebuilt when a file changes in the project directory
COPY . /go/src/project/
RUN go build -o /bin/project
# This results in a single layer image
FROM scratch
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["--help"]
不要安装不必要的包
为了减少复杂性、依赖性、文件大小和构建时间,避免安装额外或不必要的包
解耦应用程序
每个容器应该只有一个关注点。将应用程序解耦到多个容器中,可以更轻松地进行水平扩展和重用容器。
尽量减少层数
在旧版本的 Docker 中(1.10以前),尽量减少镜像中的层数以确保它们的性能非常重要。新版本的Docker只有说明RUN
, COPY
,ADD
创建层。其他指令会创建临时中间层,并且不会增加构建的大小。