1、前言

Dockerfile由一行行命令语句组成,并且支持以#开头的注释行。
基础的小linux系统。jdk;
一般而言,Dockerfile可以分为四部分
基础镜像信息 维护者信息 镜像操作指令 启动时执行指令

指令 说明
FROM 指定基础镜像
MAINTAINER 指定维护者信息,已经过时,可以使用LABEL maintainer=xxx 来替代
MAINTAINER 会将 image 的制作者相关的信息写入到 image 中
当对该 image 执行 docker inspect 命令时,输出中有相应的字段记录该信息
RUN RUN 指令是构建指令,用于安装软件,在构建镜像的时候运行,可以为当前镜像增加功能,比如yum -y jdk 使当前镜像拥有jdk环境
RUN 可以运行任何被基础 image 支持的命令
1. 如果基础 image 选择了 ubuntu,那么软件管理部分只能使用 ubuntu 的命令
2. 如果基础 image 选择了 centos,那么软件管理部分职能使用 centos 的命令
CMD 指定启动容器时默认的命令 v
ENTRYPOINT 指定镜像的默认入口.运行命令 v
EXPOSE 声明镜像内服务监听的端口 v
ENV 指定环境变量,可以在docker run的时候使用-e改变 v;会被固化到image的 config里面
ADD 复制指定的src路径下的内容到容器中的dest路径下,src可以为url会自动下载, 可以为tar文件,会自动解压
COPY 复制本地主机的src路径下的内容到镜像中的dest路径下,但不会自动解压等
LABEL 指定生成镜像的元数据标签信息
VOLUME 创建数据卷挂载点
USER 指定运行容器时的用户名或UID
WORKDIR 配置工作目录,为后续的RUN、CMD、ENTRYPOINT指令配置工作目录
ARG 指定镜像内使用的参数(如版本号信息等),可以在build的时候,使用—buildargs改变 v
OBBUILD 配置当创建的镜像作为其他镜像的基础镜像是,所指定的创建操作指令
STOPSIGNAL 容器退出的信号值
HEALTHCHECK 健康检查
SHELL 指定使用shell时的默认shell类型

2、 FROM 指定基础镜像

FROM 指定基础镜像,最好挑一些apline,slim之类的基础小镜像
scratch镜像是一个空镜像,常用于多阶段构建
如何确定我需要什么要的基础镜像?

  • Java应用当然是java基础镜像(SpringBoot应用)或者Tomcat基础镜像(War应用)
  • JS模块化应用一般用nodejs基础镜像
  • 其他各种语言用自己的服务器或者基础环境镜像,如python、golang、java、php等

3、LABEL 镜像标签相关信息

标注镜像的一些说明信息。 比如作者信息等

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

4、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. RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
    2. #上面等于下面这种写法
    3. RUN ["/bin/bash", "-c", "echo hello"]
    ```dockerfile

    测试案例

    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. 总结:什么是shellexec形式
  1. shell 是 /bin/sh -c 的方式,
  2. exec [“/bin/sh”,”-c”,command] 的方式== shell方式 也就是exec 默认方式不会进行变量替换 ```

5、 CMD和ENTRYPOINT 容器启动时候执行的命令

都可以作为容器启动入口

1、CMD 的三种写法 :

  • CMD ["executable","param1","param2"] ( exec 方式, 首选方式)
  • CMD ["param1","param2"] (为ENTRYPOINT提供默认参数)
  • CMD command param1 param2 ( shell 形式)

2、ENTRYPOINT 的两种写法:

  • ENTRYPOINT ["executable", "param1", "param2"] ( exec 方式, 首选方式)
  • ENTRYPOINT command param1 param2 (shell 形式)

3、一个示例

  1. FROM alpine
  2. LABEL maintainer=lshady
  3. CMD ["1111"]
  4. CMD ["2222"]
  5. ENTRYPOINT ["echo"]
  6. #构建出如上镜像后测试
  7. docker run xxxx:效果 echo 1111

4、只能有一个CMD

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

5、 CMD为ENTRYPOINT提供默认参数

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

6、组合最终效果

image.png

7、docker run启动参数会覆盖CMD内容

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

6、 ARG和ENV

1、ARG

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

2、ENV

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

    1. ENV MY_MSG hello
    2. ENV MY_NAME="John Doe"
    3. ENV MY_DOG=Rex\ The\ Dog
    4. ENV MY_CAT=fluffy
    5. #多行写法如下
    6. ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
    7. MY_CAT=fluffy
  • docker run —env 可以修改这些值

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

    1. FROM alpine
    2. ENV arg=1111111
    3. ENV runcmd=$arg
    4. RUN echo $runcmd
    5. CMD echo $runcmd
    6. #ENV的固化问题: 改变arg,会不会改变 echo的值,会改变哪些值,如何修改这些值?

    3、综合测试示例

    1. FROM alpine
    2. ARG arg1=22222
    3. ENV arg2=1111111
    4. ENV runcmd=$arg1
    5. RUN echo $arg1 $arg2 $runcmd
    6. CMD echo $arg1 $arg2 $runcmd

    7、ADD和COPY

    1、COPY

    COPY的两种写法 :

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

    2、ADD

    同COPY用法,不过 ADD拥有自动下载远程文件和解压的功能。
    注意:

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

8、WORKDIR和VOLUME

1、WORKDIR

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

    1. WORKDIR /a
    2. WORKDIR b
    3. WORKDIR c
    4. RUN pwd
    5. #结果 /a/b/c
  • 也可以用到环境变量

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

2、VOLUME 把容器的某些文件夹映射到主机外部

作用:把容器的某些文件夹映射到主机外部
吧容器的某些文件夹 以匿名卷挂载的方式挂载出去
写法:

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

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

9、USER

写法:

  1. USER <user>[:<group>]
  2. USER <UID>[:<GID>]
  • USER指令设置运行映像时要使用的用户名(或UID)以及可选的用户组(或GID),以及Dockerfile 中USER后面所有RUN,CMD和ENTRYPOINT指令。

10、EXPOSE

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

11、 multi-stage builds 多阶段构建

1、使用

https://docs.docker.com/develop/develop-images/multistage-build/
解决:如何让一个镜像变得更小; 多阶段构建的典型示例

  1. ### 我们如何打包一个Java镜像
  2. FROM maven
  3. WORKDIR /app
  4. COPY . .
  5. RUN mvn clean package
  6. COPY /app/target/*.jar /app/app.jar
  7. ENTRYPOINT java -jar app.jar
  8. ## 这样的镜像有多大?
  9. ## 我们最小做到多大??

2、生产示例

  1. #以下所有前提 保证Dockerfile和项目在同一个文件夹
  2. # 第一阶段:环境构建; 用这个也可以
  3. FROM maven:3.5.0-jdk-8-alpine AS builder
  4. WORKDIR /app
  5. ADD ./ /app
  6. RUN mvn clean package -Dmaven.test.skip=true
  7. # 第二阶段,最小运行时环境,只需要jre;第二阶段并不会有第一阶段哪些没用的层
  8. #基础镜像没有 jmap; jdk springboot-actutor(jdk)
  9. FROM openjdk:8-jre-alpine
  10. LABEL maintainer="534096094@qq.com"
  11. # 从上一个阶段复制内容
  12. COPY --from=builder /app/target/*.jar /app.jar
  13. # 修改时区
  14. RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo
  15. 'Asia/Shanghai' >/etc/timezone && touch /app.jar
  16. ENV JAVA_OPTS=""
  17. ENV PARAMS=""
  18. # 运行jar包
  19. ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom
  20. $JAVA_OPTS -jar /app.jar $PARAMS" ]
  1. <!--为了加速下载需要在pom文件中复制如下 -->
  2. <repositories>
  3. <repository>
  4. <id>aliyun</id>
  5. <name>Nexus Snapshot Repository</name>
  6. <url>https://maven.aliyun.com/repository/public</url>
  7. <layout>default</layout>
  8. <releases>
  9. <enabled>true</enabled>
  10. </releases>
  11. <!--snapshots默认是关闭的,需要开启 -->
  12. <snapshots>
  13. <enabled>true</enabled>
  14. </snapshots>
  15. </repository>
  16. </repositories>
  17. <pluginRepositories>
  18. <pluginRepository>
  19. <id>aliyun</id>
  20. <name>Nexus Snapshot Repository</name>
  21. <url>https://maven.aliyun.com/repository/public</url>
  22. <layout>default</layout>
  23. <releases>
  24. <enabled>true</enabled>
  25. </releases>
  26. <snapshots>
  27. <enabled>true</enabled>
  28. </snapshots>
  29. </pluginRepository>
  30. </pluginRepositories>
  1. ######小细节
  2. RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo
  3. 'Asia/Shanghai' >/etc/timezone
  4. 或者
  5. RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo
  6. 'Asia/Shanghai' >/etc/timezone
  7. 可以让镜像时间同步。
  8. ## 容器同步系统时间 CST(China Shanghai Timezone)
  9. -v /etc/localtime:/etc/localtime:ro
  10. #已经不同步的如何同步?
  11. docker cp /etc/localtime 容器id:/etc/

docker build —build-arg url=”git address” -t demo:test . :自动拉代码并构建镜像

  1. FROM maven:3.6.1-jdk-8-alpine AS buildapp
  2. #第二阶段,把克隆到的项目源码拿过来
  3. # COPY --from=gitclone * /app/
  4. WORKDIR /app
  5. COPY pom.xml .
  6. COPY src .
  7. RUN mvn clean package -Dmaven.test.skip=true
  8. # /app 下面有 target
  9. RUN pwd && ls -l
  10. RUN cp /app/target/*.jar /app.jar
  11. RUN ls -l
  12. ### 以上第一阶段结束,我们得到了一个 app.jar
  13. ## 只要一个JRE
  14. # FROM openjdk:8-jre-alpine
  15. FROM openjdk:8u282-slim
  16. RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo
  17. 'Asia/Shanghai' >/etc/timezone
  18. LABEL maintainer="534096094@qq.com"
  19. # 把上一个阶段的东西复制过来
  20. COPY --from=buildapp /app.jar /app.jar
  21. # docker run -e JAVA_OPTS="-Xmx512m -Xms33 -" -e PARAMS="--spring.profiles=dev -
  22. -server.port=8080" -jar /app/app.jar
  23. # 启动java的命令
  24. ENV JAVA_OPTS=""
  25. ENV PARAMS=""
  26. ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom
  27. $JAVA_OPTS -jar /app.jar $PARAMS" ]

自己 写一个多阶段构建

  1. 自动从git下载指定的项目
  2. 把项目自动打包生成镜像
  3. 我们只需要运行镜像即可

12、 Images瘦身实践

  • 选择最小的基础镜像
  • 合并RUN环节的所有指令,少生成一些层
  • 使用 .dockerignore 文件,排除上下文中无需参与构建的资源
  • 使用多阶段构建
  • 合理使用构建缓存加速构建。[—no-cache]
  • 学习更多Dockerfile的写法:https://github.com/docker-library/
  • RUN期间可能安装其他程序会生成临时缓存,要自行删除。如:
    1. # 开发期间,逐层验证正确的
    2. RUN xxx
    3. RUN xxx
    4. RUN aaa \
    5. aaa \
    6. vvv \
    7. #生产环境
    8. RUN apt-get update && apt-get install -y \
    9. bzr \
    10. cvs \
    11. git \
    12. mercurial \
    13. subversion \
    14. && rm -rf /var/lib/apt/lists/*

13、springboot java 最终写法

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