DockerFile常用命令
阅读原文时间:2023年07月09日阅读:1
COPY 复制文件
COPY [--chown=<user>:<group>] <源路径>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
COPY  指令将从构建上下文目录中  <源路径>  的文件/目录复制到新的一层的镜像内的  <目标路径>  位置。
COPY package.json /usr/src/app/  这个是把【上下文】路径下的package.json文件复制到【容器】的/usr/src/app/下
源路径可以是多个,并且支持通配。
COPY hom* /mydir/      COPY hom?.txt /mydir/
<目标路径>  可以是容器内的【绝对路径】,也可以是相对于【工作目录】的相对路径(工作目录可以用  WORKDIR  指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
PS:使用  COPY  指令,源文件的各种元数据都会保留 可以加上--chown=<user>:<group> 来确定用户和用户组。

ADD高级复制命令
ADD  指令和  COPY  的格式和性质基本一致。但是在  COPY  基础上增加了一些功能。
1.<源路径>可以是url,下载后权限600.
2.支持tar压缩包,会自动解压。

CMD 容器启动命令

CMD  指令的格式和  RUN  相似,也是两种格式:
    shell  格式: CMD <命令>
    exec  格式: CMD ["可执行文件", "参数1", "参数2"...]
    参数列表格式: CMD ["参数1", "参数2"...]  。在指定了  ENTRYPOINT  指令后,用  CMD  指定具体的参数。
    CMD  指令就是用于指定默认的容器主进程的启动命令的。

    比如直接执行  docker run -it ubuntu 其实是启了bash,因为这个镜像默认的CMD是/bin/bash,当然也可以再启动的时候指定运行程序,比如这样:
    docker run -it ubuntu cat /etc/os-release,这会直接输出版本信息的
    书中是建议使用exec格式,这类格式在解析时会被解析为JSON数组(因此一定用双引号不要用单引号)。
    如果使用shell格式的话,实际的命令会被包装为 sh -c 的参数形式进行执行。比如:
    CMD echo $HOME 在实际执行中,会将其变更为:CMD["sh", "-c", "echo $HOME"],也因为这个特点,才可以使用环境变量,因为shell会进行解析环境变量。

容器中应用在前台执行和后台执行的问题
    Docker不是虚拟机,容器中的应用都应该在前台执行,而不像是虚拟机、物理机那样,用systemd去启动后台服务,容器内没有后台服务的概念。
    比如 执行 CMD service nginx start 这个应该是想要后台启动nginx当服务用,但是真这么执行的时候,容器会立刻退出的。对于容器而言,其启动程序就是容器应用进程,容器就是为了住进程而存在的,主进程退出,容器就是去了存在的意义,从而退出,其他辅助进程不是它需要关心的东西。使用 service nginx start 是希望upstart来以后台守护进程形式起的弄nginx服务。其实这个会被解析成
    CMD["sh","-c","service nginx start"] ,因此主进程实际上是sh。执行完service nginx start后,sh就没事了,也就是主进程没事了,主进程没事了的话,容器就没事了。所以就退出了。
    所以可以直接,前台运行nginx  CMD["nginx","-g","daemon off;"]

ENTRYPOINT 入口点
    ENTRYPOINT格式也是分为exec和shell。
    ENTRYPOINT的目的和CMD一样,都是指定容器启动及其参数,但是他有单独的用处。
    如果指定了ENTRYPOINT的话,CMD的内容将会变成参数:<ENTRYPOINT> "<CMD>"
    看下实际用途:
    创建一个镜像,功能是获取本地ip,Dockerfile里这么写
FROM ubuntu:18.04
RUN apt-get update \
    && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*
CMD ["curl","-s","https://ip.cn"]

    然后做一个镜像 docker build -t myip .   [记得上面创建的Dockerfile放在当前路径下]
    启动下看看 docker run myip
    我的输出结果是 {"ip": "221.217.52.233", "country": "北京市", "city": "联通"}
    OK目前看没啥问题,默认执行的是CMD里面设置好的东西,但是如果现在想继续加参数会咋样,比如说加上 -i参数
    我们其实想实现这样的效果 docker run myip curl -s https://ip.cn -i
    然后尝试的这么执行的 docker run myip -i
    得到的结果是这个:docker: Error response from daemon: OCI runtime create failed: container_linux.go:346: starting container process caused "exec: \"-i\": executable file not found in $PATH": unknown.
    和想象的不一样,原因是:跟在镜像名后面的是  command  ,运行时会替换  CMD  的默认值。因此这里的  -i  替换了原来的  CMD  ,而不是添加在原来的  curl -s

    所以引入了ENTRYPOINT
FROM ubuntu:18.04
RUN apt-get update \
    && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["curl","-s","https://ip.cn"]
    创建新的镜像 docker build -t myip2 .
   执行docker run myip2 -i
   结果是:
HTTP/2 200
date: Thu, 28 Nov 2019 15:23:01 GMT
content-type: application/json; charset=UTF-8
set-cookie: __cfduid=d2f01f73f37cc1d1bbf92e970d887583a1574954581; expires=Sat, 28-Dec-19 15:23:01 GMT; path=/; domain=.ip.cn; HttpOnly
cf-cache-status: DYNAMIC
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 53cd57349d3ae79c-LAX
{"ip": "221.217.52.233", "country": "北京市", "city": "联通"}
所以说成功了。

    ENTRYPOINT还有一个应用场景,就是运行前的准备工作:可以写一个脚本,然后放入  ENTRYPOINT  中去执行,而这个脚本会将接到的参数(也就是  <CMD>  )作为命令,在脚本最后执行。比如官方镜像  redis  中就是这么做的:

FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD [ "redis-server" ]

    其中为了 redis 服务创建了 redis 用户,并在最后指定了  ENTRYPOINT为  docker-entrypoint.sh  脚本
#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
chown -R redis .
exec su-exec redis "$0" "$@"
fi
exec "$@"
    该脚本的内容就是根据  CMD  的内容来判断,如果是  redis-server  的话,则切换到  redis  用户身份启动服务器,否则依旧使用  root  身份执行

ENV 设置环境变量
    ENV <key> <value>
    ENV <key1>=<value1> <key2>=<value2>...
    无论是后面的其它指令,如  RUN  ,还是运行时的应用,都可以直接使用这里定义的环境变量。
例如可以这么写[注意换行符和 空格字符串的处理]
ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"
到时候引用的时候就可以在下面直接这样
XXXXX " xxxx$VERSION xxxxxx"

ARG 构建参数
格式: ARG <参数名>[=<默认值>]

   构建参数和  ENV  的效果一样,都是设置环境变量。所不同的是, ARG  所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用  ARG  保存密码之类的信息,因为  docker history  还是可以看到所有值的。Dockerfile  中的  ARG  指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令  docker build  中用  --build-arg <参数名>=<值>  来覆盖。

VOLUME 定义匿名卷
格式为:
    VOLUME ["<路径1>", "<路径2>"...]
    VOLUME <路径>
    容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,在  Dockerfile  中,可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
    VOLUME /data
    这里的  /data  目录就会在运行时自动挂载为匿名卷,任何向  /data  中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。
    也可以执行   docker run -d -v mydata:/data xxxx
    在这行命令中,就使用了  mydata  这个命名卷挂载到了  /data  这个位置,替代了  Dockerfile  中定义的匿名卷的挂载配置。
ps:如果是mac的话注意一个问题,就是mac的docker不是直接跑在本地的,是跑在本地虚拟机里的,所以说如果你是在设置完卷或者是查找完卷之后没有在本地找到路径是对的。需要登陆到虚拟机里面去,在虚拟机里面找对应的路径 进入本地虚拟机输入 screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty
然后回车就行了,还有就是遇到一个问题,不要强行关闭到shell窗口,这样再打开shell窗口然后重新登陆到本地虚拟机会又问题,登不进去。

EXPOSE 声明端口
    格式为  EXPOSE <端口1> [<端口2>...]
    EXPOSE  指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是  docker run -P时,会自动随机映射  EXPOSE  的端口。
    要将  EXPOSE  和在运行时使用  -p <宿主端口>:<容器端口>  区分开来。 -p  ,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而  EXPOSE  仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。

WORKDIR 指定工作目录
    格式为  WORKDIR <工作目录路径>  。
    使用  WORKDIR  指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在, WORKDIR  会帮你建立目录。    

USER 指定当前用户
    格式: USER <用户名>[:<用户组>]
例子:
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]

HEALTHCHECK 健康检查
  格式:
    HEALTHCHECK [选项] CMD <命令>  :设置检查容器健康状况的命令
    HEALTHCHECK NO
    在没有  HEALTHCHECK  指令前,Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。很多情况下这没问题,但是如果程序进入死锁状态,或者死循环状态,应用进程并不退出,但是该容器已经无法提供服务了。

    当在一个镜像指定了  HEALTHCHECK  指令后,用其启动容器,初始状态会为starting  ,在  HEALTHCHECK  指令检查成功后变为  healthy  ,如果连续一定次数失败,则会变为  unhealthy  。
    HEALTHCHECK  支持下列选项:
    --interval=<间隔>  :两次健康检查的间隔,默认为 30 秒;
    --timeout=<时长>  :健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
    --retries=<次数>  :当连续失败指定次数后,则将容器状态视为unhealthy  ,默认 3 次。和  CMD  ,  ENTRYPOINT  一样, HEALTHCHECK  只可以出现一次,如果写了多个,只有最后一个生效。
例子,web服务的健康体检
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1

ONBUILD 最为基础镜像的时候在执行
格式: ONBUILD <其它指令>  。
ONBUILD  是一个特殊的指令,它后面跟的是其它指令,比如  RUN  ,  COPY  等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。

例子:
FROM node:slim
RUN mkdir /app
WORKDIR /app
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]

ONBUILD前缀的都是这次不执行,下次执行。