深入DockerFile

一、概述

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

指令

说明

FROM

指定基础镜像

MAINTAINER

指定维护者信息, 已经过时, 可以使用 LABEL maintainer=xxx 来替代

RUN

运行命令v

CMD

指定启动容器时默认的命令 v

ENTRYPOINT

指定镜像的默认入口 . 运行命令 v

EXPOSE

声明镜像内服务监听的端口 v

ENV

指定环境变量, 可以在 docker run 的时候使用 -e 改变 v ; 会被固化到 i mage 的 config 里面

ADD

复制指定的 src 路径下的内容到容器中的 dest 路径下, src 可以为 url 会自动下载, 可以为 tar 文件, 会自动解压

COPY

复制本地主机的 src 路径下的内容到镜像中的 dest 路径下, 但不会自动解压等

LABEL

指定生成镜像的元数据标签信息

VOLUME

创建数据卷挂载点

USER

指定运行容器时的用户名或 UID

WORKDIR

配置工作目录, 为后续的 RUN 、 CMD 、 ENTRYPOINT 指令配置工作目录

ARG

指定镜像内使用的参数(如版本号信息等) , 可以在 build 的时候, 使用 --build args 改变 v

OBBUILD

配置当创建的镜像作为其他镜像的基础镜像是, 所指定的创建操作指令

STOPSIGNAL

容器退出的信号值

HEALTHCHECK

健康检查

SHELL

指定使用 shell 时的默认 shell 类型

FROM  指定基础镜像

MAINTAINER  指定维护者信息,可以没有

RUN  你想让它干啥(在命令前面加上RUN即可)

ADD  添加宿主机的文件到容器内,还多了一个自动解压的功能
# RUN tar -Zxf /opt/xx.tgz 	# 报错!该tgz文件不存在! !

COPY  作用和ADD是一样的,都是拷贝宿主机的文件到容器内, COPY就是仅仅拷贝

WORKDIR  相当于cd命令,设置当前工作目录

VOLUME  设置目录映射,挂载主机目录

EXPOSE  指定对外的端口,在容器内暴露一个端口,端口 EXPORT 80

CMD  指定容器启动后的要干的事情

ENTRYPOINT	作用和CMD一样,都是在指定容器启动程序以及参数。
# 当指定了ENTRYPOINT之后,CMD指令的语义就有了变化,而是把CMD的内容当作参数传递给ENTRYPOINT指令。

ARG	 设置环境变量
# ARG只是用于构建镜像需要设置的变量,容器运行时就消失了

ENV   和ARG一样,都是设置环境变量
# 区别在于ENV无论是在镜像构建时,还是容器运行,该变量都可以使用

USER  用于改变环境,用于切换用户

二、常用指令

1、LABEL

标注镜像的一些说明信息。

LABEL multi.label1="value1 " multi.label2="value2" other="value3"
LABEL multi.label1="value1 " \ 
      multi.label2="value2" \ 
      other="value3"

2、RUN

RUN 指令在当前镜像层顶部的新层执行任何命令, 并提交结果, 生成新的镜像层。

生成的提交映像将用于 Dockerfile 中的下一步。 分层运行 RUN 指令并生成提交符合 Docker 的核心概 念, 就像源代码控制一样。

RUN:构建时期的指令
CMD:运行时期的指令

exec 形式可以避免破坏 shell 字符串, 并使用不包含指定 shell 可执行文件的基本映像运行 RUN 命令。 可以使用 SHELL 命令更改 shell 形式的默认 shell 。 在 shell 形式中, 您可以使用 \ (反斜杠) 将一条 RUN 指令继续到下一行。

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

测试案例

[root@jacson ~/dockerfile]#vi 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 1

结果:

[root@jacson ~/dockerfile]#docker build --no-cache -t demo:v1.3 .
Sending build context to Docker daemon  2.048kB
Step 1/8 : FROM alpine
 ---> c059bfaa849c
Step 2/8 : LABEL maintainer=leifengyang xx=aa
 ---> Running in 5778621b2514
Removing intermediate container 5778621b2514
 ---> c946185cf348
Step 3/8 : ENV msg=' hello atguigu itdachang'
 ---> Running in 4966417054cc
Removing intermediate container 4966417054cc
 ---> 8eda0af71085
Step 4/8 : RUN echo $msg
 ---> Running in ca82ebaa1a59
hello atguigu itdachang
Removing intermediate container ca82ebaa1a59
 ---> 3d3ccffcae7b
Step 5/8 : RUN ["echo","$msg"]
 ---> Running in 0b1c3e8484c4
$msg
Removing intermediate container 0b1c3e8484c4
 ---> e0ed1c40af06
Step 6/8 : RUN /bin/sh -c 'echo $msg'
 ---> Running in 18365bcfffc7
hello atguigu itdachang
Removing intermediate container 18365bcfffc7
 ---> 4cacf20a152b
Step 7/8 : RUN ["/bin/sh","-c" ,"echo $msg"]
 ---> Running in 11bd0b3ee190
hello atguigu itdachang
Removing intermediate container 11bd0b3ee190
 ---> dd34682d9434
Step 8/8 : CMD sleep 1
 ---> Running in be4a71c9632b
Removing intermediate container be4a71c9632b
 ---> 0e3b241d6831
Successfully built 0e3b241d6831
Successfully tagged demo:v1.3

总结:

Step 5/8 : RUN ["echo","$msg"]
 ---> Running in 0b1c3e8484c4
$msg
结; 由于 [ ] 不是 shell 形式, 所以不能输出变量信息, 而是输出 $msg 。 其他任何 /bin/sh -c 的形式都 可以输出变量信息

什么是 shell 和 exec 形式 ?
1 . shell 是 /bin/sh -c 的方式。
2. exec [ " /bin/sh", " -c" , command] 的方式 == shell 方式 也就是 exec 默认方式不会进行变量替换。

3、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 形式 )

测试案例

FROM alpine
LABEL maintainer=jacson

CMD ["111"]
CMD ["222"]
ENTRYPOINT ["echo"]

结果

[root@jacson ~/dockerfile]#docker build -t demo:v1.3 -f Dock
[root@jacson ~/dockerfile]#docker run -it demo:v1.3 
222
#参数覆盖
[root@jacson ~/dockerfile]#docker run -it demo:v1.3 666
666

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

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

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

区别CMD和ENTRYPOINT

ENTRYPOINT作用和CMD一样,都是在指定容器启动程序以及参数。

当指定了ENTRYPOINT之后,CMD指令的语义就有了变化,而是把CMD的内容当作参数传递给ENTRYPOINT指令。

4、ARG和ENV

ARG声明的变量只在构建期有效,在运行期无效,只能在构建期修改

ENV声明的变量在构建期和运行期都生效,且在运行期可以修改

ARG:

ARG 指令定义了一个变量, 用户可以在构建时使用 --build-arg = 传递, docker build 命令会将其传递 给构建器。

–build-arg 指定参数会覆盖 Dockerfile 中指定的同名参数 。

如果用户指定了 未在 Dockerfile 中定义的构建参数 , 则构建会输出 警告 。

ARG 只在构建期有效, 运行期无效 。

不建议使用构建时变量来传递诸如 github 密钥, 用户凭据等机密。 因为构建时变量值使用 docker history 是可见的。

ARG 变量定义从 Dockerfile 中定义的行开始生效。 使用 ENV 指令定义的环境变量始终会覆盖同名的 ARG 指令。

ENV:
在构建阶段中所有后续指令的环境中使用, 并且在许多情况下也可以内联替换。

引号和反斜杠可用于在值中包含空格。

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

测试案例:

FROM alpine
ARG arg1=11111
ENV arg2=22222
ENV runcmd=$arg1
RUN echo $arg1 $arg2 $runcmd
CMD echo $arg1 $arg2 $runcmd
[root@jacson ~/dockerfile]#docker build -t demo:v1.4 --build-arg arg1=333 -f Dockerfile3 .
Sending build context to Docker daemon  4.096kB
Step 1/6 : FROM alpine
 ---> c059bfaa849c
Step 2/6 : ARG arg1=11111
 ---> Running in e81ed34363d2
Removing intermediate container e81ed34363d2
 ---> 3c945615f4a6
Step 3/6 : ENV arg2=22222
 ---> Running in 3bb6442da3ae
Removing intermediate container 3bb6442da3ae
 ---> a2325c70f74d
Step 4/6 : ENV runcmd=$arg1
 ---> Running in 1a6c9039d224
Removing intermediate container 1a6c9039d224
 ---> 7c08f3545e00
Step 5/6 : RUN echo $arg1 $arg2 $runcmd
 ---> Running in 8b42632e046c
333 22222 333
Removing intermediate container 8b42632e046c
 ---> 4d8e10a1c2e1
Step 6/6 : CMD echo $arg1 $arg2 $runcmd
 ---> Running in a4638a31f065
Removing intermediate container a4638a31f065
 ---> 59abffeb767d
Successfully built 59abffeb767d
Successfully tagged demo:v1.4
[root@jacson ~/dockerfile]#docker run -it -e arg1=456 demo:v1.4 
456 22222 333

总结:
1、改变arg1的值,runcmd不会跟着改变
2、ENV 在 image 阶段就会被解析并持久化

5、ADD和COPY

COPY

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 是目录, 则将复制目录的整个内容, 包括文件系统元数据。

6、WORKDIR 和 VOLUME

WORKDIR

WORKDIR 指令为 Dockerfile 中跟随它的所有 RUN , CMD , ENTRYPOINT , COPY , ADD 指令设置工作目 录。 如果 WORKDIR 不存在, 即使以后的 Dockerfile 指令中未使用它也将被创建。

WORKDIR 指令可在 Dockerfile 中多次使用。 如果提供了相对路径, 则它将相对于上一个 WORKDIR 指 令的路径。 例如:

WORKDIR /a 
WORKDIR b 
WORKDIR c 
RUN pwd 
# 结果 /a/b/c

也可以用到环境变量

ENV DIRPATH=/path 
WORKDIR $DIRPATH/$DIRNAME 
RUN pwd 
# 结果 /path/$DIRNAME

VOLUME

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

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

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

7、USER

写法:

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

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

8、EXPOSE

EXPOSE 指令通知 Docker 容器在运行时在指定的网络端口上进行侦听。 可以指定端口是侦听 TCP 还 是 UDP , 如果未指定协议, 则默认值为 TCP 。

EXPOSE 指令实际上不会发布端口。 它充当构建映像的人员和运行容器的人员之间的一种文档, 即 有关打算发布哪些端口的信息。 要在运行容器时实际发布端口, 请在 docker run 上使用 -p 标志发布 并映射一个或多个端口, 或使用 - P 标志发布所有公开的端口并将其映射到高阶端口。

EXPOSE <port> [ <port>/<protocol>. . . ] 
EXPOSE [ 80, 443] 
EXPOSE 80/tcp 
EXPOSE 80/udp

9、multi-stage builds(多阶段构建)

案例

# 以下所有前提 保证 Dockerfile 和项目在同一个文件夹 
# 第一阶段: 环境构建 ; 用这个也可以 
FROM maven:3.5.0-jdk-8-alpine AS builder 
WORKDIR /app 
ADD . / /app 
RUN mvn clean package -Dmaven.test.skip=true 
# 第二阶段, 最小运行时环境, 只需要 jre ; 第二阶段并不会有第一阶段哪些没用的层 
# 基础镜像没有 j map ; jdk springboot-actutor ( jdk ) 
FROM openjdk:8-jre-alpine LABEL maintainer="534096094@qq.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="" 
# 运行 j ar 包 
ENTRYPOINT ["sh","-c","java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar /app.jar $PARAMS" ]
##### 小细节 
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo ' Asia/Shanghai' >/etc/timezone 
或者 
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo ' Asia/Shanghai' >/etc/timezone 可以让镜像时间同步。 
## 容器同步系统时间 CST ( China Shanghai Timezone ) 
-v /etc/localtime:/etc/localtime:ro # 已经不同步的如何同步? 
docker cp /etc/localtime 容器 id : /etc/

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

FROM maven:3.6.1-jdk-8-alpine AS buildapp 
# 第二阶段, 把克隆到的项目源码拿过来 
# COPY --from=gitclone * /app/ 
WORKDIR /app COPY pom.xml . 
COPY src . 
RUN mvn clean package -Dmaven.test.skip=true 
# /app 下面有 target 
RUN pwd && ls -l 
RUN cp /app/target/*.jar /app.jar 
RUN ls -l 

### 以上第一阶段结束, 我们得到了一个 app.jar 
## 只要一个 JRE 
# FROM openjdk:8-jre-alpine 
FROM openjdk:8u282-slim 
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo ' Asia/Shanghai' >/etc/timezone LABEL maintainer="534096094@qq.com" 
# 把上一个阶段的东西复制过来 
COPY --from=buildapp /app.jar /app.jar 
# docker run -e JAVA_OPTS="-Xmx512m -Xms33 - " -e PARAMS="--spring.profiles=dev - -server.port=8080" - jar /app/app.jar 
# 启动 j ava 的命令
ENV JAVA_OPTS="" 
ENV PARAMS="" 
ENTRYPOINT ["sh","-c"," java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar /app.jar $PARAMS" ]

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

学习链接:链接

Dockerfile实例

1、创建myfile文件
mkdir /myfile
2、在该文件夹中导入jdk.tar.gz
3、vim Dockerfile,拷贝粘贴(
FROM centos:7
MAINTAINER zzyy<zzyybs@126.com>
 
ENV MYPATH /usr/local
WORKDIR $MYPATH
 
#安装vim编辑器
RUN yum -y install vim
#安装ifconfig命令查看网络IP
RUN yum -y install net-tools
#安装java8及lib库
RUN yum -y install glibc.i686
RUN mkdir /usr/local/java
#ADD 是相对路径jar,把jdk-8u171-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
ADD jdk-8u171-linux-x64.tar.gz /usr/local/java/
#配置java环境变量
ENV JAVA_HOME /usr/local/java/jdk1.8.0_171
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH
 
EXPOSE 80
 
CMD echo $MYPATH
CMD echo "success--------------ok"
CMD /bin/bash)

4、运行docker build -t centosjava8:1.5 .
5、运行docker run -it 容器id