Dockerfile

Dockerfile

指令 说明
FROM 指定基础镜像
MAINTAINER 指定维护者信息,已经过时,可以使用 LABEL maintainer=xxx 来替代
RUN 运行命令
CMD 指定启动容器时默认的命令
ENTRYPOINT 指定镜像的默认入口,运行命令
EXPOSE 声明镜像内服务监听的端口
ENV 指定环境变量,可以在 docker run 的时候使用 -e 改变,会被固化到 image 的 config 里面
ADD 复制指定的 src 路径下的内容到容器中的 dest 路径下,src 可以为 url 会自动下载,可以为 tar 文件,会自动解压
COPY 复制本地主机的 src 路径下的内容到镜像中的 dest 路径下,但不会自动解压等
LABEL 指定生成镜像的元数据标签信息
VOLUME 创建数据卷挂载点
USER 指定运行容器时的用户名或 UID
WORKDIR 配置工作目录,为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录
ARG 指定镜像内使用的参数(如版本号信息等),可以在 build 的时候,使用 –buildargs 改变
OBBUILD 配置当创建的镜像作为其他镜像的基础镜像是,所指定的创建操作指令
STOPSIGNAL 容器退出的信号值
HEALTHCHECK 健康检查
SHELL 指定使用 shell 时的默认 shell 类型

FROM

FROM 指定基础镜像,最好挑一些 apline,slim 之类的基础小镜像,scratch 镜像是一个空镜像,常用于多阶段构建

LABEL

标注镜像的一些说明信息

1
2
3
4
LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"

RUN

RUN 指令在当前镜像层顶部的新层执行任何命令,并提交结果,生成新的镜像层
生成的提交映像将用于 Dockerfile 中的下一步,分层运行 RUN 指令并生成提交符合 Docker 的核心概念,就像源代码控制一样
exec 形式可以避免破坏 shell 字符串,并使用不包含指定 shell 可执行文件的基本映像运行 RUN 命令,可以使用 SHELL 命令更改 shell 形式的默认 shell,在 shell 形式中,您可以使用 \ 将一条 RUN 指令继续到下一行

RUN (shell 形式, /bin/sh -c 的方式运行,避免破坏 shell 字符串)
RUN [“executable”, “param1”, “param2”] (exec 形式)

1
2
3
4
5
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
# 上面等于下面这种写法
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
RUN ["/bin/bash", "-c", "echo hello"]
1
2
3
4
5
6
7
8
9
10
11
12
# 测试案例
FROM alpine
LABEL maintainer=leifengyang xx=aa
ENV msg='hello atguigu itdachang'
RUN echo $msg
RUN ["echo","$msg"]
RUN /bin/sh -c 'echo $msg'
RUN ["/bin/sh","-c","echo $msg"]
CMD sleep 10000
# 总结:由于 [] 不是 shell 形式,所以不能输出变量信息,而是输出 $msg,其他任何 /bin/sh -c 的形式都可以输出变量信息
1. shell 是 /bin/sh -c <command> 的方式,
2. exec ["/bin/sh","-c",command] 的方式 == shell 方式,也就是exec 默认方式不会进行变量替换

CMD 和 ENTRYPOINT

都可以作为容器启动入口

CMD 的三种写法:
CMD [“executable”,”param1”,”param2”] ( exec 方式, 首选方式)
CMD [“param1”,”param2”] (为ENTRYPOINT提供默认参数)
CMD command param1 param2 ( shell 形式)
ENTRYPOINT 的两种写法:
ENTRYPOINT [“executable”, “param1”, “param2”] ( exec 方式, 首选方式)
ENTRYPOINT command param1 param2 (shell 形式)

1
2
3
4
5
6
7
8
# 一个示例
FROM alpine
LABEL maintainer=leifengyang
CMD ["1111"]
CMD ["2222"]
ENTRYPOINT ["echo"]
# 构建出如上镜像后测试
docker run xxxx:效果 echo 1111
只能有一个 CMD

Dockerfile 中只能有一条 CMD 指令,如果您列出多个 CMD,则只有最后一个 CMD 才会生效。
CMD 的主要目的是为执行中的容器提供默认值,这些默认值可以包含可执行文件,也可以省略可执行文件,在这种情况下,您还必须指定 ENTRYPOINT 指令

CMD 为 ENTRYPOINT 提供默认参数

如果使用 CMD 为 ENTRYPOINT 指令提供默认参数,则 CMD 和 ENTRYPOINT 指令均应使用 JSON 数组格式指定

docker run 启动参数会覆盖 CMD 内容
1
2
3
4
5
6
7
8
# 一个示例
FROM alpine
LABEL maintainer=leifengyang
CMD ["1111"]
ENTRYPOINT ["echo"]
# 构建出如上镜像后测试
docker run xxxx:什么都不传则 echo 1111
docker run xxx arg1:传入arg1 则echo arg1

ARG 和 ENV

ARG

ARG 指令定义了一个变量,用户可以在构建时使用 –build-arg = 传递,docker build 命令会将其传递给构建器
–build-arg 指定参数会覆盖 Dockerfile 中指定的同名参数
如果用户指定了“未在 Dockerfile 中定义的构建参数”,则构建会输出“警告”
ARG 只在构建期有效,运行期无效
不建议使用构建时变量来传递诸如 github 密钥,用户凭据等机密,因为构建时变量值使用 dockerhistory 是可见的
ARG 变量定义从 Dockerfile 中定义的行开始生效
使用 ENV 指令定义的环境变量始终会覆盖同名的 ARG 指令

ENV

在构建阶段中所有后续指令的环境中使用,并且在许多情况下也可以内联替换
引号和反斜杠可用于在值中包含空格
ENV 可以使用 key value 的写法,但是这种不建议使用了,后续版本可能会删除

1
2
3
4
5
6
7
ENV MY_MSG hello
ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy
# 多行写法如下
ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
MY_CAT=fluffy

docker run –env 可以修改这些值
容器运行时 ENV 值可以生效
ENV 在 image 阶段就会被解析并持久化(docker inspect image 查看),参照下面示例

1
2
3
4
5
6
FROM alpine
ENV arg=1111111
ENV runcmd=$arg
RUN echo $runcmd
CMD echo $runcmd
# ENV 的固化问题: 改变 arg,会不会改变 echo的值,会改变哪些值,如何修改这些值?
综合测试示例
1
2
3
4
5
6
FROM alpine
ARG arg1=22222
ENV arg2=1111111
ENV runcmd=$arg1
RUN echo $arg1 $arg2 $runcmd
CMD echo $arg1 $arg2 $runcmd

ADD 和 COPY

COPY

COPY 的两种写法

1
2
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

–chown 功能仅在用于构建 Linux 容器的 Dockerfiles 上受支持,而在 Windows 容器上不起作用
COPY 指令从 src 复制新文件或目录,并将它们添加到容器的文件系统中,路径为 dest
可以指定多个 src 资源,但是文件和目录的路径将被解释为相对于构建上下文的源
每个 src 都可以包含通配符,并且匹配将使用 Go 的 filepath.Match 规则进行

1
2
3
4
5
6
7
8
9
10
COPY hom* /mydir/ # 当前上下文,以 home 开始的所有资源
COPY hom?.txt /mydir/ # ? 匹配单个字符
COPY test.txt relativeDir/ # 目标路径如果设置为相对路径,则相对与 WORKDIR 开始
# 把 “test.txt” 添加到 <WORKDIR>/relativeDir/
COPY test.txt /absoluteDir/ # 也可以使用绝对路径,复制到容器指定位置
# 所有复制的新文件都是 uid(0)/gid(0) 的用户,可以使用 --chown 改变
COPY --chown=55:mygroup files* /somedir/
COPY --chown=bin files* /somedir/
COPY --chown=1 files* /somedir/
COPY --chown=10:11 files* /somedir/
ADD

同 COPY 用法,不过 ADD 拥有自动下载远程文件和解压的功能。
注意:
src 路径必须在构建的上下文中,不能使用 ../something /something 这种方式,因为 docker 构建的第一步是将上下文目录(和子目录)发送到 docker 守护程序
如果 src 是 URL,并且 dest 不以斜杠结尾,则从 URL 下载文件并将其复制到 dest
如果 dest 以斜杠结尾,将自动推断出 url 的名字(保留最后一部分),保存到 dest
如果 src 是目录,则将复制目录的整个内容,包括文件系统元数据

WORKDIR 和 VOLUME

WORKDIR

WORKDIR 指令为 Dockerfile 中跟随它的所有 RUN,CMD,ENTRYPOINT,COPY,ADD 指令设置工作目录。 如果 WORKDIR 不存在,即使以后的 Dockerfile 指令中未使用它也将被创建
WORKDIR 指令可在 Dockerfile 中多次使用,如果提供了相对路径,则它将相对于上一个 WORKDIR 指令的路径,例如:

1
2
3
4
5
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
# 结果 /a/b/c

也可以用到环境变量

1
2
3
4
ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd
# 结果 /path/$DIRNAME
VOLUME

作用:把容器的某些文件夹映射到主机外部
写法:

1
2
3
VOLUME ["/var/log/"] # 可以是 JSON 数组
VOLUME /var/log # 可以直接写
VOLUME /var/log /var/db # 可以空格分割多个

注意:用 VOLUME 声明了卷,那么以后对于卷内容的修改会被丢弃,所以一定在 volume 声明之前修改内容

USER

写法:

1
2
USER <user>[:<group>]
USER <UID>[:<GID>]

USER 指令设置运行映像时要使用的用户名(或 UID)以及可选的用户组(或 GID),以及 Dockerfile 中 USER 后面所有 RUN,CMD 和 ENTRYPOINT 指令

EXPOSE

EXPOSE 指令通知 Docker 容器在运行时在指定的网络端口上进行侦听,可以指定端口是侦听 TCP 还是 UDP,如果未指定协议,则默认值为 TCP
EXPOSE 指令实际上不会发布端口,它充当构建映像的人员和运行容器的人员之间的一种文档,即有关打算发布哪些端口的信息,要在运行容器时实际发布端口,请在 docker run 上使用 -p 标志发布并映射一个或多个端口,或使用 -P 标志发布所有公开的端口并将其映射到高阶端口

1
2
3
4
EXPOSE <port> [<port>/<protocol>...]
EXPOSE [80,443]
EXPOSE 80/tcp
EXPOSE 80/udp

multi-stage builds

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 以下所有前提 保证 Dockerfile 和项目在同一个文件夹
# 第一阶段:环境构建,用这个也可以
FROM maven:3.5.0-jdk-8-alpine AS builder
WORKDIR /app
ADD ./ /app
RUN mvn clean package -Dmaven.test.skip=true
# 第二阶段,最小运行时环境,只需要 jre;第二阶段并不会有第一阶段哪些没用的层
# 基础镜像没有 jmap;jdk,springboot-actutor(jdk)
FROM openjdk:8-jre-alpine
LABEL maintainer="chenkaixin12121@163.com"
# 从上一个阶段复制内容
COPY --from=builder /app/target/*.jar /app.jar
# 修改时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone && touch /app.jar
ENV JAVA_OPTS=""
ENV PARAMS=""
# 运行 jar 包
ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar /app.jar $PARAMS" ]

示例

1
2
3
4
5
6
7
8
FROM openjdk:8-jre-alpine
LABEL maintainer="chenkaixin12121@163.com"
COPY target/*.jar /app.jar
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone && touch /app.jar
ENV JAVA_OPTS=""
ENV PARAMS=""
ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar /app.jar $PARAMS" ]
# 运行命令 docker run -e JAVA_OPTS="-Xmx512m -Xms33 -" -e PARAMS="--spring.profiles=dev --server.port=8080" -jar /app/app.jar