【Docker进阶-07】Dockerfile
- Dockerfile
-
- 概述
- FROM
- LABEL
- RUN
- CMD和ENTRYPOINT
-
- 都可以作为容器启动入口
- 只能有一个CMD
- CMD为ENTRYPOINT提供默认参数
- 组合最终效果
- docker run启动参数会覆盖CMD内容
- ARG和ENV
-
- ARG
- ENV
- ADD和COPY
-
- COPY
- ADD
- WORKDIR
- VOLUME
- USER
- EXPOSE
- Image瘦身实践
-
- 多阶段构建
- 多阶段构建-例子
- 容器时间同步
Dockerfile
概述
Dockerfile由一行行命令语句组成,并且支持以#开头的注释行。
一般而言,Dockerfile可以分为四部分:基础镜像信息、维护者信息、镜像操作指令、启动时执行指令
https://docs.docker.com/reference/dockerfile/#entrypoint
学习更多Dockerfile的写法:https://github.com/docker-library/

例子:

例子:
# 这是我的Dockerfile
FROM alpine
#给镜像加个标签
LABEL maintainer="leifengyang @ dd" \\
abc=def \\
aaa=bbb cccc=ddd
#运行的指令,安装了软件,修改了文件,默认是用id=0 也就是root,这个基础系统的root用户
#代表镜像构建过程中运行的命令。
RUN echo 11111
#镜像启动如果要运行很长命令才行,容器启动执行的命令
##1、准备一个sh文件,让镜像启动运行sh文件(大多镜像操作)
##2、直接在CMD的位置写即可
CMD sleep 10;echo success
# D:\\lp\\gitcode\\my_private_repo\\demo_project\\itdachanglfy\\day03\\dockerfiles> docker build -t myalpine:v111 -f Dockerfile .
FROM openjdk:17
COPY target/*.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
# 考虑到线上的bug/调优等,建议使用jdk
#FROM openjdk:8-jre-alpine
#LABEL maintainer="lpruoyu@gmail.com"
##COPY之前自己手动编译出jar包
#COPY target/*.jar /app.jar
##touch /app.jar ,在linux中随便touch个文件会发现,touch之后,该文件的时间变了,内容不会变;
##所以touch *.jar的好处是:记录一下最后该jar保存进来的时间,方便以后追溯
#RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone && touch /app.jar
#ENV JAVA_OPTS=""
#ENV PARAMS=""
##每一个启动的Java容器都是一个小的jvm,而且都是进程隔离的,
##在这儿加这俩ENV的作用就是,方便以后修改jvm运行参数,方便调优
#ENTRYPOINT [ "sh","-c","java -Djava.security.egd=file:/dev/./urandom$JAVA_OPTS -jar /app.jar $PARAMS" ]
Dockerfile运行命令:
#传入构建参数
docker build –no-cache –build-arg param="11 22 33" msg="aa bb cc" -t demo:test -f Dockerfile4 .
#进入容器控制台
docker exec -it mydemo1 /bin/sh
# 用完容器就删除
docker run -d -P –rm –name=xxx nginx
FROM
FROM 指定基础镜像,最好挑一些apline,slim之类的基础小镜像
scratch镜像是一个空镜像,常用于多阶段构建
如何确定我需要什么要的基础镜像?
- Java应用当然是java基础镜像(SpringBoot应用)或者Tomcat基础镜像(War应用)
- JS模块化应用一般用nodejs基础镜像
- 其他各种语言用自己的服务器或者基础环境镜像,如python、golang、java、php等
LABEL
标注镜像的一些说明信息。
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指令继续到下一行。


总结:什么是shell和exec形式
1. shell 是 /bin/sh -c <command>的方式
2. exec ["/bin/sh","-c",command] 的方式 == shell方式
也就是exec 默认方式不会进行变量替换
例子:
# 不可以引用多个
FROM alpine
LABEL maintainer="leifengyang @ dd" \\
abc=def \\
aaa=bbb cccc=ddd
#指定构建参数【构建时有效】
ARG aaa=liupengxxx
#指定环境变量【为后面的RUN以及CMD 指定环境变量】
# ENV的内容会被固化保存到docker的镜像配置中 可以使用docker inspect查看
ENV parm=8888888899
# shell 形式; bash -c "echo 11111"
# 不用使用""
RUN echo $parm; echo $aaa
RUN echo ${parm}; echo ${aaa}
# exec 形式。
# 要使用""
RUN ["echo","$parm"]
RUN ["echo","$aaa"]
RUN ["echo","${parm}"]
RUN ["echo","${aaa}"]
RUN ["echo","env parm is $parm"]
RUN ["echo","arg aaa is $aaa"]
RUN ["echo","env parm is ${parm}"]
RUN ["echo","arg aaa is ${aaa}"]
# ENTRYPOINT和CMD不要用ARG,也用不了
CMD sleep 1;echo $parm;echo $aaa;echo ${aaa};echo ${parm}
# ENTRYPOINT sleep 1;echo $parm;echo $aaa;echo ${aaa};echo ${parm}
# docker build -t myalpine:v111 –no-cache -f Dockerfile_run .
# RUN,CMD,ENTRYPOINT下的:
# []: ["/bin/sh","-c"] = shell
# shell:
# 因此
#————————————————–
# FROM alpine
# ENV url=jd.com
# CMD ["ping","baidu.com"]
# CMD ["ping","${url}"] # 取不出变量
# CMD ping ${url}
# 官方都是建议使用这种方式:
# CMD ["/bin/sh","-c","ping ${url}"]
#————————————————–
CMD和ENTRYPOINT
都可以作为容器启动入口


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


FROM alpine
# ENV url=jd.com
# CMD ["ping","baidu.com"]
# CMD ["ping","${url}"] # 取不出变量
# CMD ping ${url}
# 官方都是建议使用这种方式:
# CMD ["/bin/sh","-c","ping ${url}"]
# CMD ["useradd","-u","1000","-g","2000"]
# 官方推荐的写法,变化的写 CMD,不变的写 ENTRYPOINT;CMD是提供参数给ENTRYPOINT;未来ENTRYPOINT是容器启动的唯一入口
# ENTRYPOINT CMD 这两个合在一起不能是错误的命令
# docker run imageName cmd1 一旦传递了cmd1,CMD指定的所有参数都会被覆盖
# 自定义参数的情况下一定要传完
CMD [ "5","baidu.com" ]
ENTRYPOINT [ "ping","-c" ]
FROM alpine
# ENTRYPOINT: 入口(真正的门)
# ENTRYPOINT不能被修改
# ENTRYPOINT ping atguigu.com
# CMD:命令(进门的时候带口令)
# CMD 可以被修改【docker run 时带参数】
# CMD ping baidu.com
# 最终的用法: CMD是给ENTRYPOINT提供参数的
# docker run 可以携带参数覆盖CMD : docker run demo:test jd.com
CMD ["baidu.com"]
ENTRYPOINT ["ping"]
# 总结:不变的写ENTRYPOINT;变化的写CMD
# 这两个合在一起不能是错误的命令
#———————————————-
# 多个CMD或者ENTRYPOINT只有最后一个生效
# CMD ping 666.com
# CMD ping baidu.com
# 或
# ENTRYPOINT ping 666.com
# ENTRYPOINT ping baidu.com
# docker build –no-cache -t demo:test -f .\\DockerfileCMDandENTRYPOINT .
docker run启动参数会覆盖CMD内容

ARG和ENV
ARG

# 可以在任意位置定义ARG,并在以后取值使用
# 运行期不能改 ARG 的值
# 构建时要改ARG:使用–build-arg
# 使用–build-arg version=3.13 改变;以我们传入的为准
# docker build –no-cache -t myalpine:v111 –build-arg version=3.13 -f DockerfileARG .
ARG version=3.13.4
FROM alpine:$version
LABEL maintainer="leifengyang" a=b \\
c=dd
#ENV:构建期+运行期都可以生效;但是只能在运行期进行修改
#构建期不能改 ENV 的值
#运行期:docker run -e app=atguigu 就可以修改
#docker run -e app=liupengxxx myalpine:v111
ENV app=itdachang
RUN echo $app
RUN echo $param
# ARG在定义以后的剩下环节(不包括运行时)能生效
# 可以在构建时进行变化,docker build –build-arg
# ARG不像ENV, ARG不能并排写
ARG param=123456
ARG msg="hello docker"
# 构建时要改ARG:–build-arg
# docker build -t myalpine:v111 –build-arg param="lpx love wb" –build-arg msg=99x -f DockerfileARG .
# RUN:构建时期我们会运行的指令(构建时期:根据Dockerfile创建一个镜像的整个过程时期)
RUN echo 11111
RUN echo $param
RUN echo $msg
# CMD:运行时期我们会运行的指令(运行时期:根据创建的镜像启动一个容器,容器启动默认运行的命令)
#(docker run/docker start)
# CMD和ENTRYPOINT` 都是指定的运行时的指令
CMD ["/bin/sh","-c","echo 1111;echo $param;echo app_${app}"]
ENV


#ENV:构建期+运行期都可以生效;但是只能在运行期进行修改
# env的坑
FROM alpine
# ARG msg=hello
# # ENV肯定能引用ARG
# ENV name=${msg}
# RUN echo ${name}
# RUN echo ${msg}
#———————————–
# ENV只能运行期改掉
ENV msg1=hello
ENV msg2=$msg1
# 以上构建期间就已经确定好值了;
#———————————–
RUN echo ${msg1}
RUN echo ${msg2}
# ENV持久化问题。
# msg1=msg2没问题;如果我运行期修改了msg1的值,请问msg1、msg2输出什么?
# docker run -e msg1=666 myalpine:v111 结果输出: 666 hello;
# 原因:
# 为什么运行期间能用ENV定义的所有值,一定是ENV存在某个地方
# docker build的时候,env环境的信息会固化,直接在镜像配置里面就已经写死了,msg1=hello,msg2=hello
# -e 只能修改当前env本身
# docker run -e msg1=666 -e msg2=777 myalpine:v111
# 输出 666 777
CMD ["/bin/sh","-c","echo ${msg1};echo ${msg2};"]
ADD和COPY
COPY

FROM alpine
USER 2000:2000
COPY –chown=2000:2000 *.txt /b.txt
# COPY *.txt /b.txt
RUN ls -l /
RUN echo liupeng999 >> /b.txt
# 如何测试:
#———–
# 以root身份创建一个.txt文件
# $ ls -l #root才能操作该文件
# -rw-r–r– 1 root root 7 Jan 23 14:57 xxx.txt
#———–
# COPY不加–chown
# COPY加–chown
# 看看能不能写进入
# 可以进入容器内部看看文件的所有者是谁
ADD

FROM alpine
# ADD:把上下文Context指定的内容添加到镜像中
# 如果是远程文件,自动下载;
# 如果是压缩包,自动解压;
# ADD:如果是远程文件,自动下载;
# 把当前内容复制到这个 alpine小系统里面
ADD https://download.redis.io/releases/redis-6.2.1.tar.gz /dest/
#—————————————————–
# RUN指令上下并没有上下文关系;
# RUN cd /dest
# 当前还是列举的根目录
# RUN ls -l
# 要想进入dest目录并查看dest目录下的文件,需要这样写:
RUN cd /dest && ls -l
#—————————————————–
# ADD:如果是压缩包,自动解压;
# 本地linux系统把内容文件添加进去 【宿主机 镜像内】
# docker build –no-cache -t demo:test -f Dockerfile .【. :上下文的文件路径】 .代表上下文环境也就是当前Dockerfile所在目录
# 压缩包位置放在:/root/dockerfiles/ ====> docker build –no-cache -t demo:test -f Dockerfile /root/dockerfiles/
ADD *.tar.gz /app/
RUN cd /app && ls -l
WORKDIR

FROM alpine
#复制到当前目录下
# COPY *.txt ./
# 没指定WORKDIR默认在/目录
# RUN pwd && ls -l
# WORKDIR为以下所有的命令运行指定了基础目录
WORKDIR /app
# WORKDIR abc
# 结果:/app/abc
# 多个WORKDIR可以嵌套
# RUN pwd && ls -l
# 可以使用WORKDIR为进入容器指定目录
# docker exec -it mydemo1 /bin/sh
##比如我们自己的nginx镜像可以做成这样,来方便修改
#WORKDIR /usr/share/nginx/html
#复制到当前目录下
COPY *.txt ./
# RUN pwd && ls -l
CMD ping baidu.com
FROM nginx:1.10
WORKDIR /usr/share/nginx/html
#剩下都是原来 nginx 默认的
# docker build –no-cache -t demo:test -f DockerfileWORKDIRmynginx .
# docker run -d -P –name=mmnginx demo:test
# docker exec -it mmnginx /bin/bash
VOLUME

FROM alpine
RUN mkdir /hello && mkdir /app
RUN echo 1111 > /hello/a.txt
RUN echo 222 > /app/b.txt
# VOLUME:挂载 容器内的指定文件夹,如果不存在就创建。
# 只要指定了 VOLUME ,即使启动容器没有指定 -v 参数,我们也会自动进行匿名卷挂载
# VOLUME 指定的挂载目录
VOLUME [ "/hello","/app" ]
# 这3句话没有生效;
# 也就是VOLUME某个文件(夹)之后,Dockerfile内的改变就不会生效了
# 所以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
# 应用场景:
# JAVA 日志都要挂外面 /app/log
# VOLUME ["/log"]
# …
CMD ping baidu.com
USER

# COPY
# 把上下文Context指定的内容(宿主机的内容)复制到镜像中
# COPY不自动解压和下载
# 容器中的ROOT虽然不是linux宿主机的真实root,但是可以改掉这个镜像的所有东西
# ROOT用户的id是0
# 以后可以给自己的镜像创建一个用户身份来运行,而不是用root(0)
# USER 1000:1000 类似这样,直接使用用户id即可
# 以容器的redis用户的redis组的身份运行:
# COPY –chown=redis:redis *.tar.gz /redis/
# RUN cd /redis && ls -l
# USER user1:group1
# 接下来的所有命令都用这个用户来运行
FROM alpine
USER 1000:1000
# 以后的所有命令会用 1000:1000 身份来执行。
# 有可能没有执行权限
# COPY *.txt /a.txt
# –chown可以把复制来的文件给用户所有权
COPY –chown=1000:1000 *.txt /a.txt
RUN ls -l /
RUN echo 2222 >> /a.txt
# 方便进入容器查看
CMD sleep 1000000;
EXPOSE

Image瘦身实践

如何让一个镜像变得更小
-
选择最小的基础镜像
-
合并RUN环节的所有指令,少生成一些层

-
RUN期间可能安装其他程序会生成临时缓存,要自行删除。
-
使用 .dockerignore 文件,排除上下文中无需参与构建的资源
-
合理使用构建缓存加速构建。[–no-cache]
-
使用多阶段构建
多阶段构建
https://docs.docker.com/develop/develop-images/multistage-build/
多阶段构建-例子
# 多阶段最大的好处就是之前的阶段都跟我打包的镜像没关系,最后一个阶段,不会有前面阶段的那些层!因此镜像就会很小
FROM alpine
# RUN 安装maven
# RUN mvn clean package
# COPY xxx.jar /app.jar
# ENTRYPOINT [ "java","-jar","app.jar" ]
#SpringBoot应用 java -jar xxx.jar
# 运行Java最少环境:jre环境;
# 不嫌麻烦你也可以自己打包,那就不需要上面的maven环境了
# 多阶段构建:
# 一个镜像分为多个大的阶段进行构建,最终的构建结果是最后一个阶段的结果,好处就是能够缩小镜像
# 多阶段构建
# FROM alpine AS build
# xxxxxx
# 上面的FROM是辅助阶段
# FROM jre
# COPY –from=build xxx xxx
# ENTRYPOINT [ "executable" ]
FROM maven:3.6.1-jdk-8-alpine AS buildapp
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
# 启动java的命令
ENV JAVA_OPTS=""
ENV PARAMS=""
ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar /app.jar $PARAMS" ]
# 多阶段构建有个好处就是:缩小镜像
# https://hub.docker.com/_/maven/tags?name=jdk-17
FROM maven:3.8.5-openjdk-17-slim AS buildapp
#FROM maven:3.8.5-openjdk-17-slim
WORKDIR /app
# COPY 是从宿主机上复制到镜像内部
COPY pom.xml .
COPY src ./src
#CMD sleep 10000000
#
RUN mvn clean package
# /app 下面有 target
#RUN pwd && ls -l
#镜像内部拷贝
RUN cp /app/target/*.jar /app.jar
#RUN ls -l
### 以上第一阶段结束,我们得到了一个 app.jar
## Java想要运行只要一个JRE就够了
FROM openjdk:26-ea-17-trixie
# 修改时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
LABEL maintainer="lpruoyu@gmail.com"
# 把上一个阶段的东西复制过来
COPY –from=buildapp /app.jar /app.jar
# docker run -e JAVA_OPTS="-Xmx512m -Xms33 xxx" -e PARAMS="–spring.profiles=dev –server.port=8080 xxx"
# 启动java的命令
ENV JAVA_OPTS=""
ENV PARAMS=""
# EXPOSE 主要是起文档的作用
#EXPOSE 8080
ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar /app.jar $PARAMS" ]
容器时间同步

网硕互联帮助中心






评论前必须登录!
注册