Dockerfile由一行行命令语句组成,并且支持以#开头的注释行。基础的小linux系统。jdk; 一般而言,Dockerfile可以分为四部分
基础镜像信息
维护者信息
镜像操作指令
启动时执行指令
指令 | 说明 |
---|---|
FROM | 指定基础镜像 |
MAINTAINER | 指定维护者信息,已经过时,可以使用LABEL maintainer=xxx 来替代 |
LABEL | 指定维护者信息 maintainer=xxx auth=xueqimiao a=b (语法 k=v ) |
RUN | 运行命令 代表镜像构建过程中运行的命令 |
CMD | 指定启动容器时默认的命令 容器启动时要执行的命令 |
ENTRYPOINT | 指定镜像的默认入口.运行命令 |
EXPOSE | 声明镜像内服务监听的端口 |
ENV | 指定环境变量,可以在docker run的时候使用-e改变 会被固话到image的config里面 |
ADD | 复制指定的src路径下的内容到容器中的dest路径下,src可以为url会自动下载,可以为tar文件,会自动解压 |
COPY | 复制本地主机的src路径下的内容到镜像中的dest路径下,但不会自动解压等 |
LABEL | 指定生成镜像的元数据标签信息 |
VOLUME | 创建数据卷挂载点 |
USER | 指定运行容器时的用户名或UID |
WORKDIR | 配置工作目录,为后续的RUN、CMD、ENTRYPOINT指令配置工作目录 |
ARG | 指定镜像内使用的参数(如版本号信息等),可以在build的时候,使用–build-args改变 |
OBBUILD | 配置当创建的镜像作为其他镜像的基础镜像是,所指定的创建操作指令 |
STOPSIGNAL | 容器退出的信号值 |
HEALTHCHECK | 健康检查 |
SHELL | 指定使用shell时的默认shell类型 |
FROM 指定基础镜像,最好挑一些apline,slim之类的基础小镜像.
scratch镜像是一个空镜像,常用于多阶段构建
如何确定我需要什么要的基础镜像?
# 注释
# 基础镜像
FROM alpine
# 给镜像加标签
LABEL auth = xueqimiao
# 运行的指令 默认是使用id=0的用户 也就是root 是这个基础系统的root用户
# 代表镜像构建过程中运行的命令
RUN echo 111
# 容器启动时要执行的命令
# 镜像启动如果要运行很长、很多命令 可以准备一个sh文件 让镜像启动运行sh文件(大多数镜像这么操作)
CMD sleep 10;echo success
标注镜像的一些说明信息。
LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
# --no-cache 不使用缓存构建
docker build --no-cache -t myalpine:v111 -f D2 .
# 不可以引用多个
FROM alpine
LABEL auth = xueqimiao
# 指定构建参数
ARG aaa=bbb
# 指定环境变量
ENV parm=xue
# shell 形式 bash -c "echo 111 "
RUN echo $parm
# 可以取到 ARG
RUN echo $aaa
# exec 形式 $parm 默认取不到 取不出环境变量【ENV】,ARG也取不到
RUN ["echo","$parm"]
# RUN ["echo","$aaa"]
# 都是可以启动容器的命令
#CMD sleep 1;echo $parm;echo $aaa
# 都是可以启动容器的命令
ENTRYPOINT sleep 1;echo $parm;echo $aaa
RUN ( shell 形式, /bin/sh -c 的方式运行,避免破坏shell字符串)
RUN ["executable", "param1", "param2"] ( exec 形式)
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
#上面等于下面这种写法
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
RUN ["/bin/bash", "-c", "echo hello"]
# 测试案例
FROM alpine
LABEL maintainer=xueqimiao xx=aa
ENV msg='hello xueqimiao'
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 的形式都
可以输出变量信息
总结:什么是shell和exec形式
shell 是 /bin/sh -c 的方式,
exec ["/bin/sh","-c",command] 的方式== shell方式
也就是exec 默认方式不会进行变量替换
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=xueqimiao
CMD ["1111"]
CMD ["2222"]
ENTRYPOINT ["echo"]
#构建出如上镜像后测试
docker run xxxx:效果 echo 1111
如果使用CMD为ENTRYPOINT指令提供默认参数,则CMD和ENTRYPOINT指令均应使用JSON数组格式指定。
ENTRYPOINT ping baidu.com 怎么写都没用,容器启动都是以ENTRYPOINT的完整命令为准
无 ENTRYPOINT | ENTRYPOINT exec_entry p1_entry ENTRYPOINT ping baidu.com |
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 CMD [“ping”,“baidu.com”] |
/bin/sh -c exec_entry p1_entry | exec_entry p1_entryexec_cmd p1_cmd |
CMD[“p1_cmd”,“p2_cmd”] | p1_cmd p2_cmd CMD [ “5”,“baidu.com” ] |
/bin/sh -c exec_entry p1_entry | exec_entry p1_entryp1_cmd p2_cmd |
CMD exec_cmd p1_cmd | /bin/sh -c exec_cmd p1_cmd CMD ["/bin/sh","-c",“ping ${url}”] |
/bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |
FROM alpine
ENV url=baidu.com
# CMD ["ping","baidu.com"]
# CMD ["useradd","-u","1000","-g","2000"]
# CMD ["ping","${url}"] 取不出变量
# CMD ping ${url}
# 官方都是建议使用 []方式
# CMD ["/bin/sh","-c","ping ${url}"]
# ENTRYPOINT ping baidu.com + CMD怎么写都没用,容器启动都是以ENTRYPOINT的完整命令为准
# java -jar xxxx.jar --spring.profile=dev --server.port=8888
# 这两个合在一起不能是错误的命令
#官方推荐的写法,,变化的写CMD,而CMD是提供参数给ENTRYPOINT
# docker run imageName cmd1 一旦传递了cmd1,CMD指定的所有参数都会被覆盖,
# 自定义参数的情况下一定要传完
CMD [ "5","baidu.com" ]
#exec的写法 不变的写 ENTRYPOINT;未来他是容器启动的唯一入口,
ENTRYPOINT [ "ping","-c" ]
# 一个示例
FROM alpine
LABEL maintainer=xueqimiao
CMD [“1111”]
ENTRYPOINT [“echo”]
#构建出如上镜像后测试
docker run xxxx:什么都不传则 echo 1111
docker run xxx arg1:传入arg1 则echo arg1
FROM alpine
# ENTRYPOINT: 入口(真正的门)
# ENTRYPOINT [ "ping" ]
# 命令(进门的时候带口令)
# 最终的用法: CMD是给ENTRYPOINT提供参数的
#CMD可以被修改
#CMD ping baidu.com
ENTRYPOINT ping baidu.com
# ENTRYPOINT + CMD = 容器的完整启动命令
# 这是启动命令
# ENTRYPOINT ping + CMD baidu.com = 错误
# 多个CMD只有最后一次生效
# CMD ping baidu.com
# ["echo","${param}"] 不是bash -c的方式,取不出环境变量信息
# echo $param = ["/bin/sh","-c","多长的命令都写在这里 echo ${param}"]
# ENTRYPOINT或者CMD作为唯一入口,只能写一个,最后一个生效
# ENTRYPOINT ping baidu.com
# RUN,CMD,ENTRYPOINT
# []: ["/bin/sh","-c"] = shell
# shell:
#docker build --no-cache --build-arg version=3.13.4 demo:test -f D2 .
#可以动态传入版本
#可以在任意位置定义 并在以后取值使用
#ARG version=latest
#FROM alpine:$version
FROM alpine
LABEL maintainer = xueqimiao
# 定义以后的剩下环节生效 不包括运行时
# 可以在构建时进行变化,docker build 时
# ARG 不像 env 不能并排写
ARG param=222
ARG msg="hello docker"
# 构建时传入ARG
# docker build --no-cache --build-arg param=333 --build-arg msg=xue -t demo:test -f D2 .
# 构建时期会运行的指令(根据Dockerfile创建一个镜像的整个过程时期)
RUN echo $param
RUN echo $msg
# 运行时期我们会运行的指令(根据之前创建的镜像启动一个容器,容器启动默认运行的命令)
#CMD echo 1111;echo $param
# CMD ENTRYPOINT 都是指定的运行时的指令
CMD ["/bin/sh","-c","echo 1111;echo $param"]
FROM alpine
LABEL maintainer = xueqimiao
ARG param=222
ARG msg="hello docker"
# 构建期 + 运行期都生效 只能在运行期进行修改
# 怎么修改
# 构建期 不能改env的值
# 运行期 docker run -e app=xue_docker2 demo:test
ENV app=xue_docker
RUN echo $param
RUN echo $msg
RUN echo $app
# 运行时期我们会运行的指令(根据之前创建的镜像启动一个容器,容器启动默认运行的命令)
#CMD echo 1111;echo $param
# CMD ENTRYPOINT 都是指定的运行时的指令
CMD ["/bin/sh","-c","echo 1111;echo $param;echo app_${app}"]
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
ENV arg=1111111
ENV runcmd=$arg
RUN echo $runcmd
CMD echo $runcmd
# 改变arg,会不会改变 echo的值,会改变哪些值,如果修改这些值?
FROM alpine
ARG arg1=22222
ENV arg2=1111111
ENV runcmd=$arg1
RUN echo $arg1 $arg2 $runcmd
CMD echo $arg1 $arg2 $runcmd
# ENV的坑
FROM alpine
#ARG msg=hello
# ENV 肯定能引用ARG的参数
#ENV name=${msg}
#RUN echo ${msg}
#RUN echo ${name}
ENV msg1=hello
ENV msg2=$msg1
# 以上构建期间就已经确定好值了
RUN echo ${msg1}
RUN echo ${msg2}
# msg1=msg2 没问题
# 如果运行期修改msg1的值
# docker run -it -e msg1=6666 demo:test
# 结果输出 msg1的值会改变 msg2的值是不会改变的 msg2还是hello
# 原因:docker build 的时候 env环境的信息会固话 直接在镜像配置里面就已经写死
# -e 只能修改当前env本身
# 运行期间能使用ENV 是因为ENV 固化到了镜像的配置
CMD ["/bin/sh","-c","echo ${msg1};echo ${msg1}"]
COPY的两种写法
COPY [--chown=:] ...
COPY [--chown=:] ["",... ""]
COPY hom* /mydir/ #当前上下文,以home开始的所有资源
COPY hom?.txt /mydir/ # ?匹配单个字符
COPY test.txt relativeDir/ # 目标路径如果设置为相对路径,则相对与 WORKDIR 开始
# 把 “test.txt” 添加到 /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/
同COPY用法,不过 ADD拥有自动下载远程文件和解压的功能。
注意:
FROM alpine
# 把上下文指定的内容添加到镜像中 如果是压缩包 自动解压 如果是远程文件 自动下载
# 把当前内容复制到 这个 alpine 小系统里面 /dest
# 如果是远程文件 自动下载 如果下载的还是压缩包 不会自动解压
ADD https://download.redis.io/releases/redis-6.2.4.tar.gz /dest/
RUN ls
# RUN 指令并没有上下文关系 RUN cd /dest RUN ls 这样还是在跟目录下 并不是进入了dest 再ls
RUN cd /dest
# 当前还是列举的根目录
RUN ls
RUN cd /dest && ls -l
FROM alpine
# 本地linux系统的文件添加进去 【宿主机到镜像内】
# wget https://download.redis.io/releases/redis-6.2.4.tar.gz
# 自动解压
ADD *.tar.gz /app/
# COPY *.tar.gz /app/
RUN cd /app && ls -l
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
#结果 /a/b/c
ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd
#结果 /path/$DIRNAME
FROM alpine
RUN pwd && ls -l
# 为以下所有的命令运行指定了基础目录
WORKDIR /app
RUN pwd && ls -l
# 可以为进入容器指定一个默认目录
WORKDIR abc
##比如我们的nginx镜像可以做成这样
#WORKDIR /usr/share/nginx/html
# /app/abc 多个WORKDIR可以嵌套
RUN pwd && ls -l
#复制到当前目录下
COPY *.txt ./
RUN pwd && ls -l
CMD ping baidu.com
FROM nginx
WORKDIR /usr/share/nginx/html
作用:把容器的某些文件夹映射到主机外部
写法:
VOLUME ["/var/log/"] #可以是JSON数组
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的时候生效
# -v 使用 VOLUME和-v挂载出去的目录(外面变,容器里面变)。所有改变也生效了
# 1)、但是 docker commit 提交当前容器的所有变化为镜像的时候,就会丢弃
# 2)、VOLUME [ "/hello","/app" ] 容器以后自动挂载,在Dockerfile中对VOLUME的所有修改都不生效 要想生效 可以在声明挂载之前修改
# 3)、挂载只有一点就是方便在外面修改,或者把外面的东西直接拿过来
# 所以这个写在最后
# JAVA 日志都要挂外面 /app/log
# VOLUME ["/log"]
VOLUME ["/hello","/app"]
# 这两句话没有生效
RUN echo 6666 >> /hello/a.txt
RUN echo 8888 >> /app/b.txt
CMD ping baidu.com
FROM alpine
# 开用户
# 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
USER [:]
USER [:]
EXPOSE [/...]
EXPOSE [80,443]
EXPOSE 80/tcp
EXPOSE 80/udp
FROM alpine
# 暴露 ,这个只是一个声明;给程序员看。docker也能看到
# docker -d -P(随机分配端口,)
EXPOSE 8080
EXPOSE 9090
CMD ping baidu.com
https://docs.docker.com/develop/develop-images/multistage-build/
多阶段构建
解决:如何让一个镜像变得更小; 多阶段构建的典型示例
### 我们如何打包一个Java镜像
FROM maven
WORKDIR /app
COPY . .
RUN mvn clean package
COPY /app/target/*.jar /app/app.jar
ENTRYPOINT java -jar app.jar
## 这样的镜像有多大?
## 我们最小做到多大??
#以下所有前提 保证Dockerfile和项目在同一个文件夹
# 第一阶段:环境构建;
FROM maven:3.5.0-jdk-8-alpine AS builder
WORKDIR /app
ADD ./ /app
RUN mvn clean package -Dmaven.test.skip=true
# 第二阶段,最小运行时环境,只需要jre
FROM openjdk:8-jre-alpine
# 修改时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
LABEL maintainer="[email protected]"
# 从上一个阶段复制内容
COPY --from=builder /app/target/*.jar /app.jar
ENV JAVA_OPTS=""
ENV PARAMS=""
# 运行jar包
ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS
-jar /app.jar $PARAMS" ]
aliyun
Nexus Snapshot Repository
https://maven.aliyun.com/repository/public
default
true
true
aliyun
Nexus Snapshot Repository
https://maven.aliyun.com/repository/public
default
true
true
# 开发期间,逐层验证正确的
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/*
使用 .dockerignore 文件,排除上下文中无需参与构建的资源
# 比如
*.iml
target/*
使用多阶段构建
合理使用构建缓存加速构建。–no-cache
学习更多Dockerfile的写法:https://github.com/docker-library/
FROM openjdk:8-jdk-alpine
LABEL maintainer="[email protected]"
COPY target/*.jar /app.jar
# 时区设置
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime; echo 'Asia/Shanghai' >/etc/timezone && RUN touch /app.jar
# 记录一下最后保存进来的时间
# RUN touch /app.jar
ENV JAVA_OPTS=""
ENV PARAMS=""
ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar /app.jar $PARAMS" ]
# 运行命令 docker run -e JAVA_OPTS="-Xmx512m -Xms33 -" -e PARAMS="--spring.profiles=dev --server.port=8080" -jar /app/app.jar