我们知道,镜像是多层存储,每一层都是在前一层的基础上进行的修改;容器以镜像为基础,同样是多层存储!
一、docker commit
1)启动一个容器并命名为webserver
docker run -it --name webserver -p 80:80 nginx /bin/bash
同时映射了80端口,这样可以使用浏览器去访问这个nginx服务器。由于我在本机运行的docker,直接通过http://localhost访问,浏览器显示的画面如下。如果在虚拟机上运行docker,则需要将localhost换成虚拟机的IP
2)修改文件
使用'<h1> hello,my name is docker !</h1>' 替换/usr/share/nginx/html/index.html内的内容
echo '<h1> hello,my name is docker !</h1>' > /usr/share/nginx/html/index.html
3)刷新之后看到如下页面
4)查看对容器进行了哪些修改
exit
docekr diff webserver
结果如下:
可以发现除了对应的html文件之外,还有很多其他文件也被修改了!
5)将修改后的容器保存为镜像
docker commit \
--author "Tao Wang <twang2218@gmail.com>" \
--message "修改了默认网页" \
webserver \
nginx:v2
其中--author是指定修改的作者,--message记录本次修改的内容,这些都可以省略。
可以使用docker image ls查看新定制的镜像
使用docker history nginx:v2查看镜像内的历史记录
运行这个镜像,并访问
docker run --name web2 -d -p 81:80 nginx:v2
这里将新的容器命名为web2,且映射到了81端口,访问http://localhost:81可以看到和之前一样的页面。
缺点
使用docker commit可以很方便的制作镜像,但是这种方式对于其他使用者是完全未知的,别人并不知道我们对镜像做了哪些修改,这种修改是永久性的。如果新的使用者使用该镜像创建容器修改之后继续使用docker commit,将会导致越来越臃肿。
二、dockerfile制作镜像
如果可以把每一层的修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来制作镜像,上述问题将不再存在。这个脚本就是dockerfile
Dockerfile是一个文本文件,其中包含了一条条的指令,每一条指令构建一层,因此每一条指令的内容就是描述当前层应该如何构建。
仍然以定制nginx镜像为例,使用dockerfile方式定制镜像
1)在本地主机新建空白文件夹myDKFile,并创建Dockerfile
mkdir myDKFile
cd myDKFile
touch Dockerfile
2)打开dockerfile并输入以下内容
FROM nginx
RUN echo '<h1>Hello,use dockerfile to build jingxiang!!</h1>' > /usr/share/nginx/html/index.html
其中:FROM指定基础镜像,RUN指定进行了哪些操作
RUN的格式有两种:
格式1:shell格式:RUN <命令> ,这种方式就想之间在命令行中输入的命令一样,如上面的Dockerfile中的RUN 命令
格式2:exec格式:RUN ["可执行文件”,"参数1","参数2"],更像是函数调用中的格式
RUN可以像shell脚本一样执行命令,也可以像shell脚本一样把每个命令对应一个RUN。如
FROM debian:stretch
RUN apt-get update
RUN apt-get install -y gcc libc6-dev make wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install
但是,由于Dockerfile中每一个指令都会建立一层,RUN也不例外,这种方式会创建7层镜像,完全没有意义。而且运行时很多不需要的东西,比如编译环境、更新的软件包等。会产生非常臃肿,多层的镜像,不仅增加了构建部署的时间,也容易出错。
上面的写法可以改成如下:
FROM debian:stretch
RUN set -x; buildDeps='gcc libc6-dev make wget' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
一共14行!之前的命令只有一共目的,就是下载、编译、安装redis。因此可以用一共RUN,并且用&&把不同的指令串联起来,将之前的7层简化为1层,使用\进行换行。Dockerfile支持shell累的行尾部添加\的命令换行方式,以及行首#进行注释的格式。
同时,还添加了清理工作的命令,删除为了编译构建所需要的软件,清理了所有下载、展开的文件,还清理了apt缓存文件。镜像是多层的,每一层的东西并不会在下一层被删除,会一直随着镜像存在,因此构建镜像时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。
第3行set -x的意思,用buildDeps
中间7行和之前的完全一样
第11行
第12、13行删除对应的压缩包和新建的文件夹
第14行删除和运行时无关的软件包
3)在dockerfile文件所在目录执行docker build -t nginx:v4 .
4)查看结果---以nginx:v4镜像操作
依次输入以下指令
docker run --name DKFile -d -p 85:80 nginx:v4
docker exec -it DKFile bash
本地浏览器输入http://localhost:85,结果为:
2.1dockerfile中配置环境变量
理论上你在ubuntu终端输入的所有指令都可以在run后面写入,但对于环境变量如果你希望配置之后使用容器时还有效,则应该使用ENV
如在终端命令 export CC=clang-11, export CXX=clang++-11
在run后的写法为
FROM ubuntu:20.04
RUN export CC=clang-11 \
&& export CXX=clang++-11
这种方式使用docker build生成镜像,然后使用docker run生成容器之后使用export -p查看发现并无对应的变量设置。如果想实现这样的目的,应该改为
FROM ubuntu:20.04
ENV CC=clang-11
ENV CXX=clang++-11
RUN apt-get update
注意:ENV在dockerfile中的位置也很重要,为了保险,可以放在RUN之前