深入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