前面我们使用 docker commit
制作了一个自己的镜像;
但是这个主要是用于学习以及一些特殊的用途;
实际应用中一般不会通过这种方式制作镜像;
因为 docker commit
只是把变动的文件制作成了镜像;
比如之前文章中创建 test 镜像的时候;
记录的是创建的 /test
这个文件夹;
但是变动的过程无法记录;
没有办法追踪到我是通过 mkdir /test
这个命令创建的 /test
目录;
这样的话就非常不利于追踪历史和维护;
如果要是涉及到编译安装软件等等过程;
那就更加令人崩溃了;
其实对我们来说更重要的是想记录 mkdir /test
这条命令;
因为只要有这条命令;
我们肯定是可以得到 /test
目录这个结果;
因此 Docker 推出了 Dockerfile
来解救猿类;
Dockerfile
就是一个名叫 Dockerfile 的普通的文本文件;
用来记录制作镜像的命令;
我们找个空白文件夹建一个 Dockerfile 文件;
我们之前制作镜像的时候分为 3 步;
docker pull ubuntu
那在 Dockerfile
文件中则是通过 FROM
关键字;
FROM ubuntu
mkdir /test
命令;Dockerfile
文件中则是通过 RUN
关键字;FROM ubuntu
RUN mkdir /test
docker commit
制作镜像Dockerfile
中了;docker build
命令;docker build -t baijunyao/test:v2 .
-t baijunyao/test:v2
: 定义镜像的名称和版本
.
: build 的上下文环境目录
build 完成后 push 镜像就跟之前一样;
这里就略过不讲了;
敲黑板划重点注意我标注的箭头了;
接着我要拿它来讲解上面说的让人不太理解的上下文环境目录;
Docker 跟 MySQL 之类的一样也分为客户端和服务端;
干活的是服务端;
客户端主要起调用的作用;
在执行 docker build
的时候是要把文件都发给服务端引擎来 dockerd 处理;
所以这个 .
就是指定上下文目录的;
意思是把当前目录的所有文件都发送给 dockerd ;
为了验证上面所说;
我们把 /bin/bash
文件复制到当前目录再运行看看;
cp /bin/bash .
docker build -t baijunyao/test:v2 .
可以看到发送的数据明显变多了;
但是其实 build 镜像的时候并不需要在这个 bash 文件;
这种发送全部文件的行为明显是不合理的;
要解决这个问题可以借助 .dockerignore
;
看名字就明显一股浓浓的山寨 .gitignore
的气息;
这正是 Docker 的高明之处;
什么 pull
、push
、commit
等等通通使用我们已经熟知的 Git 概念;
极大的减少了我们的学习阻碍;
接着创建 .dockerignore 文件并加入 bash ;
/bash
再次执行 build 命令;
那 1M 的 bash 文件已经不再发送给 dockerd 了;
另外针对上面的 RUN 顺便提一嘴;
如果要 RUN 多个命令可以用 &&
连接;
比如说常见的 apt 安装;
apt update && apt install busybox
CMD
关键字用来指定容器启动后执行的 默认
命令;
修改下 Dockerfile 定义一个 CMD 输出 "Dockerfile CMD Commmand" ;
Dockerfile
FROM ubuntu
RUN mkdir /test
CMD echo "Dockerfile CMD Commmand"
build 镜像;
docker build -t baijunyao/test:v3 .
启动容器;
docker run baijunyao/test:v3
可以看到在 run 完后会输出这句话;
但是注意上面我说过是默认命令;
既然叫默认值那一般是可以替换的;
如果我们在启动容器的时候执行了其他命令;
那就不会再执行 CMD 默认命令了;
docker run baijunyao/test:v3 /bin/echo "test"
可以看到 CMD 的默认命令没有执行;
不同于 RUN 可以定义多个;
CMD 只可以定义一个;
如果定义多个那么后面的会覆盖前面的 CMD;
那如果想定义一个不会被覆盖的命令就可以使用 ENTRYPOINT
;
Dockerfile
FROM ubuntu
RUN mkdir /test
CMD echo "Dockerfile CMD Commmand"
ENTRYPOINT echo "Dockerfile ENTRYPOINT Commmand"
我们来重新 build ;
docker build -t baijunyao/test:v4 .
运行容器;
docker run baijunyao/test:v4
截图会出乎你的意料;
"Dockerfile CMD Commmand" 或者说 "test" 都没有输出;
"test" 会覆盖掉 "Dockerfile CMD Commmand" 是可以想到的;
但是连 "test" 都不输出这就有点说不过去了;
这里其实是模式的不同;
RUN 、 CMD、ENTRYPOINT 三种关键字都有两种模式;
['/bin/echo', 'test']
结论来了在 shell 模式下 ENTRYPOINT
会直接覆盖掉 CMD
的命令;
那我们用 exec 模式改写下;
Dockerfile
FROM ubuntu
RUN mkdir /test
CMD ["/bin/echo", "Dockerfile CMD Commmand"]
ENTRYPOINT ["/bin/echo", "Dockerfile ENTRYPOINT Commmand"]
再次 build 运行;
docker build -t baijunyao/test:v5 .
docker run baijunyao/test:v5
结果再次出乎意料;
CMD
的命令并没有正常输出;
原因是在 exec 模式下;
CMD
命令会被作为 ENTERPOINT
的参数;
因此上面的命令等于下面这样;
ENTRYPOINT ["/bin/echo", "Dockerfile ENTRYPOINT Commmand", "/bin/echo test"]
这真是为难人了;
那有木有一种方案可以让我正常的符合直觉的同时使用 CMD
和 ENTERPOINT
;
有;但是需要我先把下个关键字讲了;
COPY
关键字用来将宿主机的文件 copy 到镜像中;
我们上面的 ENTERPOINT
代码转移到文件中;
enterpoint.sh
#!/bin/bash
echo "Dockerfile ENTRYPOINT Commmand"
exec "$@"
注意这里多加了一行 exec "$@"
;
它 的作用是执行接到的参数;
给与写权限;
chmod +x enterpoint.sh
接着改写下 Dockerfile 文件;
Dockerfile
FROM ubuntu
RUN mkdir /test
COPY enterpoint.sh /root/docker/
CMD echo "Dockerfile CMD Commmand"
ENTRYPOINT ["/root/docker/enterpoint.sh"]
上面这几条命令也很容易理解了;
先把宿主机上的 enterpoint.sh 文件复制到容器的 /root/docker/ 目录下;
再次 build 运行;
docker build -t baijunyao/test:v6 .
docker run baijunyao/test:v6
终于如愿以偿;
这里需要再次强调上下文环境;
我们 build 命令使用的是 .
;
也就是说我们不能获取当前目录外面的内容;
我们来试着把 /bin/bash
文件复制到镜像中;
Dockerfile
FROM ubuntu
RUN mkdir /test
COPY enterpoint.sh /root/docker/
COPY /bin/bash /root/docker/
CMD echo "Dockerfile CMD Commmand"
ENTRYPOINT ["/root/docker/enterpoint.sh"]
果然是报错的;
ADD 和 COPY 差不多;
比较常见的场景是可以用来解压缩文件;
除了需要解压文件;
这个关键字就记住一句话就行了;
官方推荐用 COPY ;
并不建议用 ADD;
ENV 用来定义变量;
用空格或者 =
定义;
如果值中有空格就加引号;
比如:
ENV PATH /root/baijunyao
ENV PATH=/root/baijunyao
ENV NAME "Junyao Bai"
ENV NAME="Junyao Bai"
使用变量的时候加上 $
即可;
FROM ubuntu
ENV DOCKER_PATH=/root/docker
RUN mkdir $DOCKER_PATH
COPY enterpoint.sh $DOCKER_PATH/
CMD echo "Dockerfile CMD Commmand"
ENTRYPOINT ["/root/docker/enterpoint.sh"]
另外需要注意的是有几个系统的变量不能作为 key;
比如说 PATH
、 HOME
;
这类变量可以直接在 Dockerfile 中使用;
EXPOSE 的主要作用就是告诉使用者镜像的守护端口;
比如说 MySQL 的 Dockerfile 就会定义 EXPOSE 为 3306 来告诉用户端口号;
本文为白俊遥原创文章,转载无需和我联系,但请注明来自白俊遥博客https://baijunyao.com 欢迎捐赠赞赏加入组织创建QQ群及捐赠渠道
最新评论