Docker学习笔记 —— Docker 介绍
Docker学习笔记 —— Docker 安装
Docker学习笔记 —— Docker 常用命令
Docker学习笔记 —— Docker 存储
Docker学习笔记 —— Docker 网络
Docker学习笔记 —— Dockerfile
文章目录
- 系列文章目录
- 一、 docker build
- 二、环境置换(Environment replacement)
- 三、 dockerfile 指令介绍
- 3.1 FROM
- 3.2 LABEL
- 3.3 MAINTAINER (已弃用)
- 3.4 RUN
- 3.5 CMD / ENTRYPOINT
- 3.5.1 CMD
- 3.5.2 ENTRYPOINT
- 3.5.3 CMD、ENTRYPOINT关系
- 3.6 ARG / ENV
- 3.6.1 ARG
- 3.6.2 ENV
- 3.7 ADD / COPY
- 3.7.1 ADD
- 3.7.2 COPY
- 3.8 WORKDIR / VOLUME
- 3.8.1 WORKDIR
- 3.8.2 VOLUME
- 3.9 USER
- 3.10 EXPOSE
- 四、多阶段构建
- 4.1 Images瘦身
一、 docker build
- Docker 可以通过读取Docker文件中的说明自动生成镜像。
- Docker 是一个文本文档,其中包含用户可以在命令行上调用以组装镜像的所有命令,用户可以使用docker build创建一个连续执行多个命令行执行的自动构建;
二、环境置换(Environment replacement)
docker build 命令从Dockerfile和上下文构建镜像,生成的上下文是可以指定为本地文件系统上的目录,还可以指定为Git存储库位置。构建上下文是递归处理的;
构建是由Docker守护进程运行,而不是由CLI运行;
- 构建过程要做的第一件事是(递归处理)将整个上下文发送到守护进程,大多数情况下,最该从一个空目录作为上下文开始,并将Dockerfile 保存在该目录中,不要使用根目录 / 作为 PATH 构建上下文,因为它会导致构建将硬盘驱动器的全部内容传输到Docker守护进程;
- 传统上,Dockerfile位于上下文的根目录中,也可以通过在 docker build中使用-f 来指向文件系统中任何位置的Dockerfile,还可以通过 -t来制定存储库和tag;
三、 dockerfile 指令介绍 3.1 FROM
环境变量(用ENV语句声明)也可以在某些指令中用作由 Dockerfile 解释的变量;
环境变量在Dockerfile中用$variable_name 或 ${variable_name} 表示,大括号的语法通常用于解决变量名没有空格的问题如${variable}_name;
${variable_name}还支持一下标准Bash修饰符:
- ${variable:-word},表示如果设置了变量,则结果将是该值,如果未设置,则结果将为 word;
- ${variable:+word},表示如果设置了变量,则机构将是该值,如果未设置,则结果将是空字符串;
格式:
- FROM [--platform=
] [AS ] - FROM [--platform=
] [: ] [AS ] - FROM [--platform=
] [@ ] [AS ] FROM 指令初始化一个新的构建阶段并为后续指令设置基础镜像。因此,有效的Dockerfile 必须以FROM指令开头(ARG可以先于FROM),镜像可以是任何有效的镜像,最好挑一些apline,slim之类的基础小镜像,scratch镜像是一个空镜像,常用于多阶段构建;
FROM可以在单个Dockerfile中多次出现,以创建多个镜像或将一个构建阶段用作另一个构建阶段的依赖项。只需要在每条新FROM指令之前几下commit输出的最后一个镜像ID。每个FROM指令都会清除以前的指令创建的任何状态;
可以通过添加 AS name 到 FROM指令来为新的构建阶段命名,这个名称可在后续FROM和COPY --from=
说明中用于引用此阶段构建的镜像; tag 和 digest 值是可选的,如果省略不写,默认使用 lastest ,如果找不到,将返回错误;
如何确定我需要什么要的基础镜像?
- Java应用当然是java基础镜像(SpringBoot应用)或者T omcat基础镜像(War应用)
- JS模块化应用一般用nodejs基础镜像
- 其他各种语言用自己的服务器或者基础环境镜像,如python、golang、java、php等
ARG version=3.13 #不可以引用多个 FROM alpine:$version CMD ["/bin/sh","-c","echo 11111"]3.2 LABEL
格式:
- LABEL
= = = ... 键值对结构,该指令向镜像添加元数据,标注镜像的一些说明信息,LABEL值中包含空格可以使用引号、 来处理;
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."3.3 MAINTAINER (已弃用)
3.4 RUN
格式:
- MAINTAINER
MAINTAINER指令设置生成图像的作者字段,已弃用,用 LABEL 代替;
格式:
RUN
- shell形式,命令在 shell 中运行,默认/bin/sh -c在 Linux 或cmd /S /CWindows 上;
- shell形式,/bin/sh -c 的方式;
RUN ["executable", "param1", "param2"]
exec形式,不调用命令shell,不会进行shell处理
例如:RUN[“echo”,"$HOME"]不会对变量 $HOME进行替换,如果想要shell处理,那么:
- 使用shell形式;
- 要么直接执行shell,例如 RUN["sh","-c","echo $HOME"];
RUN命令将在当前图层之上的新层中执行任何命令并提交结果,生成的提交镜像将用于 Dockerfile的下一步;
分层运行 RUN 指令并生成提交符合Docker的核心概念,在Docker中,提交很便宜,可以源代码管理一样从镜像历史中的任何点创建容器;
exec形式可以避免破坏shell字符串,并使用不包含指定shell可执行文件的基本映像运行RUN命令。
可以使用SHELL命令更改shell形式的默认shell。 在shell形式中,您可以使用(反斜杠)将一条 RUN指令继续到下一行;
#指定构建参数【构建时】 ARG version=3.13 #不可以引用多个 FROM alpine:$version #给镜像加元数据信息 LABEL maintainer=dozezz date=20210924 #指定构建参数【构建时】 ARG dozezz=dozezz ARG doze=doze #指定环境变量【为RUN以及CMD指定环境变量时】 ENV msg="hello dockerfile !" # shell* 形式; bash -c "echo 11111" RUN echo $msg # exec 形式。$msg 默认拿不到ENV RUN ["echo","$msg"] RUN ["echo","$dozezz"] # shell* 形式; bash -c "echo 11111" RUN /bin/sh -c "echo $msg" # exec 形式。$msg 默认拿不到ENV RUN ["/bin/sh","-c","echo $msg"] CMD ["/bin/sh","-c","sleep 1000;echo $dozezz;echo $msg;"]3.5 CMD / ENTRYPOINT 3.5.1 CMD
有三种形式:
- CMD ["executable","param1","param2"](EXEC形式,这是首选形式);
- CMD ["param1","param2"](作为ENTRYPOINT 的默认参数);
- CMD command param1 param2(SHELL 形式);
Doeckerfile 中只能有一个CMD指令,如果列出多个命令,则只有最后一个生效;
CMD的主要用途是为正在执行的容器踢动默认值,这些默认值可以包括可执行文件,也可以省略可执行文件,但是必须指 ENTRYPOINT;
CMD在当做 ENTRYPOINT 指令提供默认参数时候,应该使用JSON数组格式指定CMD和ENTRYPOINT指令,这意味着参数等使用双引号,而不是单引号;
*exec*形式不调用命令 shell。这意味着不会发生正常的 shell 处理。例如:
- CMD [ "echo", "$HOME" ]不会对 进行变量替换$HOME。如果你想要 shell 处理,那么要么使用shell形式,要么直接执行 shell,例如:CMD [ "sh", "-c", "echo $HOME" ]
FROM ubuntu CMD echo "This is a test." | wc -
FROM CMD ["/usr/bin/wc","--help"]3.5.2 ENTRYPOINT
3.5.3 CMD、ENTRYPOINT关系
有两种形式:
ENTRYPOINT ["executable", "param1", "param2"](EXEC形式,这是首选形式);
- docker run
-d 将-d参数传递给入口点。您可以ENTRYPOINT使用docker run --entrypoint 标志覆盖指令; ENTRYPOINT command param1 param2(SHELL 形式);
- 这种形式将使用 shell 处理来替换 shell 环境变量,并且将忽略任何CMD或docker run命令行参数;
| 无ENTRYPOINT | ENTRYPOINT exec_entry p1_entry | ENTRYPOINT [“exec_entry” ,“p1_entry”] | |
|---|---|---|---|
| 无CMD | 错误, 不允许的写法 | /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 |
3.6.2 ENV
格式:
- ARG
[= ] ARG指令定义了一个变量,用户可以在构建时使用–build-arg = 传递,docker build命令会将其传递给构建器,–build-arg 指定参数会覆盖Dockerfile 中指定的同名参数;
如果用户指定了未在 Dockerfile 中定义的构建参数,则构建会输出警告,ARG只在构建期有效,运行期无效;
不建议使用构建时变量来传递诸如github密钥,用户凭据等机密。因为构建时变量值使用docker history是可见的。
ARG变量定义从Dockerfile中定义的行开始生效,使用ENV指令定义的环境变量始终会覆盖同名的ARG指令;
格式:
- ENV
= ... ENV指令将环境变量
设置为 ; 此值将在构建阶段的所有后续指令的环境中,并且也可以在许多中内联替换。
与命令行解析一样,引号和反斜杠可用于在值中包含空格。
#可以在任意位置定义,并在以后取值使用, #使用--build-arg version=3.13 改变;以我们传入的为准 ARG version=3.13 #不可以引用多个 FROM alpine:$version #给镜像加元数据信息 LABEL maintainer=dozezz date=20210924 #指定构建参数【构建时】 #ARG不像ENV不能并排写 ARG dozezz=dozezz ARG doze=doze #指定环境变量【为RUN以及CMD指定环境变量时】 #构建期+运行期都可以生效;但是只能在运行期加参数 -e 修改(docker run -e msg=aaa ), 构建期不能修改 ENV的值 ENV msg="hello dockerfile !" #构建时期我们会运行的指令(根据Dockerfile创建一个镜像的整个过程时期) # shell* 形式; bash -c "echo 11111" RUN echo $dozezz RUN echo $msg # exec 形式。$msg 默认拿不到ENV RUN ["echo","$msg"] RUN ["echo","$dozezz"] # 错误语法 #RUN ["/bin/bash","-c","$msg"] #RUN ["/bin/bash","-c","$dozezz"] #运行时期我们会运行的指令(根据之前创建的镜像启动一个容器,容器启动默认运行的命令) CMD ["/bin/sh","-c","echo $dozezz;echo $msg;"]3.7 ADD / COPY 3.7.1 ADD
格式:
- ADD [--chown=
: ] ... - ADD [--chown=
: ] [" ",... " "] - 该--chown功能仅在用于构建 Linux 容器的 Dockerfile 上受支持,不适用于 Windows 容器;
ADD指令从中复制新文件、目录或远程文件 URL
,并将它们添加到路径 处的图像文件系统中 ;
可以指定多个资源,但如果它们是文件或目录,则它们的路径被解释为相对于构建上下文的源; 每个都
可能包含通配符,匹配将使用 Go 的 filepath.Match规则完成;
是一个绝对路径,或相对于WORKDIR的路径,源文件将被复制到目标容器中。;
3.7.2 COPY
ADD遵守以下规则:
该
路径必须是内部语境的构建;
- 你不能ADD ../something /something,因为docker构建的第一步 docker build是将上下文目录(和子目录)发送到 docker 守护进程;
如果
是 URL 并且 不以斜杠结尾,则从 URL 下载文件并将其复制到 ; 如果
是 URL 并且 确实以斜杠结尾,则从 URL 推断文件名并将文件下载到 / ;
- 例如,ADD http://example.com/foobar /将创建文件/foobar;
- http://example.com 将不起作用;
如果
是目录,则复制目录的全部内容,包括文件系统元数据(不会复制目录本身,只会复制其内容); 如果
是可识别的压缩格式(identity、gzip、bzip2 或 xz)的本地tar 存档,则将其解压缩为目录。来自远程URL 的资源不会被解压缩; 如果
是任何其他类型的文件,则将其与其元数据一起单独复制。在这种情况下,如果 以斜杠结尾/,它将被视为一个目录,其内容 将被写入 /base( ); 如果
直接指定了多个资源,或者由于使用了通配符,则 必须是目录,并且必须以斜杠结尾/; 如果
不以斜杠结尾,则将其视为常规文件,并将其内容 写入 ; 如果
不存在,则会将其与路径中所有缺失的目录一起创建;
格式:
- COPY [--chown=
: ] ... - COPY [--chown=
: ] [" ",... " "] COPY指令从中复制新文件或目录
并将它们添加到容器的文件系统中的路径 ;
可以指定多个资源,但文件和目录的路径将被解释为相对于构建上下文的源; 每个都
可能包含通配符,匹配将使用 Go 的 filepath.Match规则完成;
是一个绝对路径,或相对于WORKDIR的路径,源文件将被复制到目标容器中。 COPY接受一个标志--from=
,该标志可用于将源位置设置为上一个生成阶段(使用from..AS 创建),该生成阶段将代替用户发送的生成上下文。如果找不到具有指定名称的生成阶段,将尝试使用具有相同名称的镜像。
COPY 遵守以下规则:
该
路径必须是内部语境的构建;
- 不能 copy ../something/something,因为docker构建的第一步是将上下文目录(和子目录)发送到docker守护进程;
如果
是目录,则复制目录的全部内容,包括文件系统元数据(不会复制目录本身,只会复制其内容); 如果
是任何其他类型的文件,则将其与其元数据一起单独复制。在这种情况下,如果 以斜杠结尾/,它将被视为一个目录,其内容 将被写入 /base( ); 如果
直接指定了多个资源,或者由于使用了通配符,则 必须是目录,并且必须以斜杠结尾/; 如果
不以斜杠结尾,则将其视为常规文件,并将其内容 写入 ; 如果
不存在,则创建它及其路径中所有丢失的目录;
FROM alpine #把上下文Context指定的内容添加到镜像中 # 1.如果是远程文件,自动下载; ADD https://download.redis.io/releases/redis-6.0.0.tar.gz /dest/ RUN cd /dest/ RUN cd /dest && ls -l # 2.本地linux系统docker上下文的内容文件添加镜像中,如果是压缩包,自动解压; # docker build -t demo:test -f Dockerfile 【.:上下文的文件路径】 : .代表上下文环境;代表Dockerfile所在的当前目录 ADD *.tar.gz /app/ # RUN指令上下并没有上下文关系;例如:第一个cd a/ ,第二个 cd b/ 不会进入 a/b/ RUN cd /app/ RUN cd /app && ls -l # RUN指令上下并没有上下文关系; RUN cd /dest RUN ls -l RUN cd /app RUN ls -l # 开用户 #RUN adduser -u 1000 -g 1000 # 以后的所有命令会用 abc:abc 来执行。有可能没有执行权限 # 容器中的ROOT虽然不是linux宿主机的真实root,但是可以改掉这个镜像的所有 USER 1000:1000 # 把复制来的文件给用户所有权 COPY --chown=1000:1000 *.txt /a.txt RUN ls -l / #不是root不能写 RUN echo 2222 >> a.txt CMD sleep 203.8 WORKDIR / VOLUME 3.8.1 WORKDIR
格式:
- WORKDIR /path/to/workdir
WORKDIR指令为Dockerfile中跟随它的所有 RUN,CMD,ENTRYPOINT,COPY,ADD 指令设置工作目录。 如果WORKDIR不存在,即使以后的Dockerfile指令中未使用它也将被创建。
WORKDIR指令可在Dockerfile中多次使用。 如果提供了相对路径,则它将相对于上一个WORKDIR指令的路径。
FROM alpine RUN pwd && ls -l # 为以下所有的命令运行指定了基础目录 WORKDIR /app # 为以下所有的命令运行指定了基础目录 WORKDIR /app0001 # 可以为进入容器指定一个默认目录 WORKDIR abc # docker exec -it时候会进入 WORKDIR,比如我们的nginx镜像可以做成这样 #WORKDIR /usr/share/nginx/html # /app/abc 多个WORKDIR可以嵌套 RUN pwd && ls -l #复制到当前目录下 COPY *.txt ./ RUN pwd && ls -l CMD ping baidu.com3.8.2 VOLUME
格式:
- VOLUME ["/data"]
VOLUME指令创建一个具有指定名称的挂载点,并将其标记为保存来自本机主机或其他容器的外部挂载卷;
该值可以是 JSON 数组、VOLUME ["/var/log/"]或带有多个参数的纯字符串,例如
- VOLUME /var/log
- VOLUME /var/log /var/db
用 VOLUME 声明了卷,那么以后对于卷内容的修改会被丢弃,所以, 一定在volume声明之前修改内容;
FROM alpine RUN mkdir /hello && mkdir /app RUN echo 1111 > /hello/a.txt RUN echo 222 > /app/b.txt #挂载 容器的指定文件夹,如果不存在就创建,指定了 VOLUME ,即使启动容器没有指定 -v 参数,也会自动进行匿名卷挂载 # 容器内的 /hello ,/app 文件夹,在使用镜像启动容器的时候,自动给宿主机上挂载 # VOLUME挂载出去的东西,容器改变也不会最终commit的时候生效,使用 VOLUME和-v挂载出去的目录(外面变,容器里面变)。但是所有改变也生效了, # 1)、但是 docker commit 提交当前容器的所有变化为镜像的时候,就会丢弃 # 2)、VOLUME [ "/hello","/app" ] 容器以后自动挂载,在Dockerfile中对VOLUME的所有修改都不生效 # 3) 、挂载只有一点就是方便在外面修改,或者把外面的东西直接拿过来, 例如:JAVA 日志都要挂外面 /app/log,VOLUME ["/log"] # VOLUME 指定的挂载目录 VOLUME [ "/hello","/app" ] # 这两句话没有生效,用VOLUME声明了卷,那么以后对于卷内容的修改会被丢弃 RUN echo 6666 >> /hello/a.txt RUN echo 8888 >> /app/b.txt RUN cd /hello && echo 88888 >>a.txt #暴露 ,这个只是一个声明;给程序员看。docker也能看到 # docker -d -P(随机分配端口,) EXPOSE 8080 EXPOSE 999 CMD ping baidu.com3.9 USER
3.10 EXPOSE
格式
- USER
[: ] - USER
[: ] USER指令设置运行镜像时要使用的用户名(或UID)以及可选的用户组(或GID),以及Dockerfile中USER后面所有RUN,CMD和ENTRYPOINT指令;
四、多阶段构建
格式:
- EXPOSE
[ / ...] EXPOSE指令通知Docker容器在运行时在指定的网络端口上进行侦听。 可以指定端口是侦听TCP还是UDP,如果未指定协议,则默认值为TCP。
EXPOSE指令实际上不会发布端口。 它充当构建映像的人员和运行容器的人员之间的一种文档,即有关打算发布哪些端口的信息。
要在运行容器时实际发布端口,请在docker run上使用-p标志发布并映射一个或多个端口,或使用-P标志发布所有公开的端口并将其映射到高阶端口。
遇到个坑,dockerfile中没有 EXPOSE,在docker run时 -P没有端口暴露,加上就好了的坑;
FROM maven:alpine AS builderapp #设置工作目录 WORKDIR /app #拷贝宿主机文件到镜像中 COPY ./ ./ #RUN pwd && ls -l #RUN cd /app/src&&ls -l #编译文件,跳过测试 RUN mvn clean package -Dmaven.test.skip=true #RUN cd /app/target/ && ls -l #RUN cp /app/target/*.jar /app/app.jar #RUN pwd&&ls -l #ENTRYPOINT java -jar app.jar #EXPOSE 8080 FROM openjdk:8-jre-alpine LABEL maintainer=dozezz # 上个阶段复制内容 COPY --from=builderapp /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" ]4.1 Images瘦身
- 选择最小的基础镜像
- 合并RUN环节的所有指令,少生成一些层
- RUN期间可能安装其他程序会生成临时缓存,要自行删除。如:
- 使用 .dockerignore 文件 ,排除上下文中无需参与构建的资源
- 使用多阶段构建
- 合理使用构建缓存加速构建。[–no-cache]
- 学习更多Dockerfile的写法:https://github.com/docker-library/
# 开发期间,逐层验证正确的 RUN xxx RUN xxx RUN aaa aaa vvv #生产环境 RUN apt-get update && apt-get install -y bzr cvs git mercurial subversion && rm -rf /var/lib/apt/lists/*



