本文仅为个人学习记录. 教程来源Docker--从入门到实践
FROM 指定基础镜像
FROM nginx
FROM 有一个比较特殊的镜像(空镜像)
FROM scratch
一般 golang 程序会直接使用FROM scratch.
RUN 执行命令
run指令有两种格式
- shell格式
RUN echo 'hello docker'
- exec格式
RUN apt-get update
RUN apt-get install -y gcc
注意:Dockerfile每一个指令都会构建一层,不得超过127层
所以很多时候我们会使用 &&
来减少构建的层数
例如上例可改写为
RUN apt-get update && apt-get install -y gcc
Dockerfile nginx 实例
FROM nginx
RUN echo '<h1>Hello,Docker!!</h1>' > /usr/share/nginx/html/index.html
构建命令
docker build -t nginx:v3 .
运行命令
docker run --name webserver -d -p 80:80 nginx:v3
COPY 复制文件
COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。
COPY [--chown=<user>:<group>]<源路径> ... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
例子:
COPY package.json /usr/src/app/
这里源路径可以使用通配符,其通配符规则要满足 go 的 filepath.Match规则
例如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
ADD 更高级的复制文件 (虽然很高级但不建议使用)
ADD [--chown=<user>:<group>]<源路径> ... <目标路径>
ADD [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
ADD
指令和 COPY
的格式和性质基本一致。但是在 COPY
基础上增加了一些功能
比如 <源路径> 可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩。所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用
注意 ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。
CMD 容器启动命令
Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程的启动命令的
CMD指令的格式和RUN相似,也是两种格式:
#shell格式
CMD <命令>
#exec格式
CMD ["可执行文件","参数1","参数2"...]
在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号。
如果使用 shell 格式的话, 实际的命令会被包装为 sh -c 的参数的形式进行执行.
CMD echo $HOME
在实际执行中, 会将其变更为:
CMD [ "sh", "-c", "echo $HOME" ]
这就是为什么我们可以使用环境变量的原因, 因为这些环境变量会被shell 进行解析处理, 提到CMD
就不得不提容器中应用在前台执行和后台执行的问题. 这是初学者常出现的一个混淆.
Docker不是虚拟机, 容器中的应用都应该以前台执行,而不是像虚拟机/物理机那样,用upstart/systemd去启动后台服务, 容器内没有后台服务的概念
一些初学者将CMD写为
CMD service nginx start
然后发现容器执行后就立即退出了,甚至在容器内去使用systemctl 命令,结果发现根本执行不了.这就是因为没搞明白前台,后台的概念, 没有区分容器和虚拟机的差异,依旧用传统虚拟机的角度去理解容器
对于容器而言, 其启动程序就是容器应用进程, 容器就是为了主进程而存在的, 主进程退出 , 容器就失去了存在的意义,从而退出, 其他辅助进程不是他需要关心的东西
而使用 service nginx start命令 则是希望upstart来以"后台守护进程"的形式启动nginx服务, 但刚才说了 CMD service nginx start
会被理解为 CMD [ "sh", "-c", "echo $HOME" ]
,因此主进程实际上是sh
.那么当service nginx start命令结束后 , sh
也就结束了,sh
作为主进程退出了, 自然就会让容器也跟着退出
正确的做法是 直接执行nginx可执行文件, 并且要求以前台的形式运行.
比如:
CMD ["nginx", "-g", "deamon off"]
个人理解:
推荐使用 exec
格式的用意是为了避免类似sh -c
的命令干扰
ENTRYPOINT (入口点)
ENTRYPOINT
的格式和RUN
指令格式一样, 分为 exec
和shell
ENTRYPOINT
的目的和CMD
一样都是在指定容器启动程序及参数. ENTRYPOINT
在运行是也可以替代,不过比CMD
略显繁琐, 需要通过docker run
的参数--entrypoint
来指定
当指定了ENTRYPOINT
后, CMD
的含义就发生了改变, 不再是直接的运行其命令, 而是将CMD
的内容当作参数传给ENTRYPOINT
指令.可以理解为:
<ENTRYPOINT> "<CMD>"
场景一: 让镜像变成像命令一样使用
假设我们需要一个得知自己当前公网 IP 的镜像,那么可以先用 CMD
来实现:
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 .
来构建镜像的话, 如果我们需要查询当前公网IP, 只需要执行
docker run myip
这里遇到一个问题, 如果我们想用curl -i
的话就会出现问题
所以这里就引申出了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"]
ENV 设置环境变量
格式有两种
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2> ...
这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如 RUN
,还是运行时的应用,都可以直接使用这里定义的环境变量。
.
有疑问加站长微信联系(非本文作者)