FreezeJ' Blog

Dockerfile参考文档

2021-09-16

参考文档:
官方文档: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>]

tagdigest值是可选的。如果您省略其中任何一个,构建器默认使用一个latest标签。使用AS可以重命名,在FROMCOPY --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的使用说明

  1. Dockerfile至少要有CMDENTRYPOINT命令其中一个。
  2. ENTRYPOINT 应在将容器用作可执行文件时进行定义。(让容器变成像命令一样使用)
  3. CMD应该用作定义ENTRYPOINT命令的默认参数或在容器中执行临时命令的一种方式。
  4. 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创建层。其他指令会创建临时中间层,并且不会增加构建的大小。