在前面的文章 图解Docker的整体架构 中,我们知道镜像是Docker的三大组件之一,本文我们将学习一下镜像的构建过程。主要围绕Dockerfile,已经常用的指令!
1. 镜像常用命令
下面罗列一下常用的与镜像有关的命令:
- 列举本地的镜像:
docker images
- 拉取镜像:
docker pull tomcat
- 在仓库中查找镜像:
docker search tomcat
- 根据名称删除镜像:
docker rmi tomcat:alpine
- 根据id删除镜像:
docker rmi 8b8b1eb786b5
- 构建镜像:
docker build -t mytest:v1 .
- 设置标签:
docker tag tomcat:alpine tomcat:alpine-test
相当于只是设置了软链接!下面可以看到Image ID都是一样的!
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat alpine 8b8b1eb786b5 2 years ago 106MB
tomcat alpine-test 8b8b1eb786b5 2 years ago 106MB
2. Dockerfile简介
如果说Docker容器是刚出炉的鱼香肉丝,Docker镜像就是鱼香肉丝这个菜品,那么Dockerfile就是鱼香肉丝这个菜品的菜谱!
菜谱中的每道工序决定了最后炒出来的鱼香肉丝菜的味道。
Dockerfile中的内容,最终也决定了最终运行起来的Docker容器!
前面的文章 Docker入门篇之Apache展示网页 制作了一个Apache的镜像,我们来看看镜像的内容:
Dockerfile就是一个文本文件,里面包含一条条的指令
!(类比成菜谱里面的做菜的每一个步骤)。
构建镜像时,默认会找上下文路径下的Dockerfile文件,所以如果在上下文的目录下的其他文件名,就需要用
-f
指定Dockerfile的位置和名称!
上下文路径
很中亚的docker命令是docker build。我们重点来剖析它!
docker build -t my-test .
-t
:设置镜像名称和标签,默认标签是latest,完整格式my-test:latest
.
:构建上下文路径
在前面的文章分析过,docker是C/S架构,docker build
命令是在客户端执行的,但是真正构建镜像,是在服务器端进行的。
这样就会引出一个问题,我们的本地的一些文件想最终想包含在镜像中,就需要把它们发送到真正构建镜像的服务端!
处理过程:
- 这个上下文路径,存储的就是我们需要在镜像中包含的文件路径。
- 指定了它,docker build请求服务端时就会把它打包
- 真正构建镜像时,我们可以通过COPY指令,把它们复制到镜像中去。
3. Dockerfile指令
上面的Dockerfile涉及几种指令:FROM、RUN、COPY、CMD、EXPOSE,除此之外,还有其他指令,下面我们逐条分析它们!
3.1 FROM:指定基础镜像
指定要基础镜像,类似Java里面的类继承!
Docker Hub仓库里面有各种已经制作好的镜像,我们也可以基于它们制作新的镜像,也可以根据它们创建容器!
如果想马上有一套部署tomcat的环境,直接使用tomcat镜像创建容器!
如果想马上有一套CentOS环境,直接使用CentOS镜像创建容器!
FROM指令是必选的,否则会报错:
failed to solve with frontend dockerfile.v0: failed to create LLB definition: no build stage in current context
FROM中指定的镜像,就是后面指令的构建舞台(build stage)
3.2 COPY:复制文件
COPY指令和我们Linux中的cp命令类似!
最终目的是,从上下文路径
中copy文件到容器的指定路径
!
3.3 RUN:运行命令(构建镜像时)
RUN指令的作用是制作镜像时执行一条命令!支持两种格式:
shell格式:
RUN touch test-1.txt
exec格式:
RUN ["touch", "test-2.txt"]
这样容器运行后,我们可以看到新建的两个文件:
3.4 CMD:运行命令(启动容器时)
CMD和前面的RUN都是运行命令。
区别是运行时机不一样:
- RUN:构建镜像(
docker build
)时运行命令 - CMD:启动容器(
docker run
)时运行命令
指定容器启动时的命令:
FROM alpine
RUN touch test-1.txt
RUN ["touch", "test-2.txt"]
CMD ["echo", "Hello Docker!"]
非常重要的一点:
CMD的作用是给容器指定默认的程序,所以当命令结束时,容器也会退出!
docker run my-test
Hello Docker!
也就是说容器的生命周期随着CMD指令的完成而退出!!!!
当Dockerfile中声明多条CMD指令时,前面的会被最后一条CMD命令覆盖!
FROM alpine
RUN touch test-1.txt
RUN ["touch", "test-2.txt"]
CMD ["echo", "Hello 1"]
CMD ["echo", "Hello 2"]
上面的容器,只会输出Hello 2
另外,CMD指令也会被docker run指定的命令覆盖!
docker run my-test echo "This is Docker"
This is Docker
3.5 ENTRYPOINT:运行命令(启动容器时)
作用类似CMD,同样多条ENTRYPOINT只会保留最后一条。
但是和CMD不同的是,它不会不会被docker run中的命令覆盖!
FROM alpine
RUN touch test-1.txt
RUN ["touch", "test-2.txt"]
CMD ["echo", "Hello 1"]
CMD ["echo", "Hello 2"]
ENTRYPOINT ["echo", "Hello ENTRYPOINT 1"]
ENTRYPOINT ["echo", "Hello ENTRYPOINT 2"]
3.6 ARG:设置变量
相当于局部变量,只是在构建镜像时可用$VERSION_ARG
ARG VERSION_ARG=1001
3.7 ENV:设置环境变量
ARG的加强版,同时设置到环境变量中:
ENV VERSION_ENV=8
ARG VERSION_ARG=1001
3.8 VOLUME:定义默认的数据卷
定义匿名的数据卷,如果docker run是没有挂载数据卷,会用这里默认的数据卷!如果docker run指定-v
已设置的为主!
主要是防止docker run时没有挂载数据卷,容器删除后,容器内的数据会丢失!
测试一下
定义数据卷
FROM alpine
RUN touch nobody.txt
VOLUME test_volume
启动时并不挂载数据卷:
docker run -dit my-test /bin/sh
查看数据卷:
$ docker volume ls
DRIVER VOLUME NAME
local 583336a1717deb1e674fe29934b1b0a9ae7a69d9a8f07c8168d9462c74ca4928
即使容器被删除后,数据卷还在!
3.9 EXPOSE :声明端口
仅仅是声明默认的端口!
比如Apache中声明了80端口:
FROM php:7.0-apache
RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf
COPY ./html/test.html /var/www/html/
EXPOSE 80
有两个作用:
- 相当于注释,告诉修改镜像的人,这些容器端口对外暴露了
- 作为默认端口使用
FROM php:7.0-apache
RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf
COPY ./html/test.html /var/www/html/
EXPOSE 80
启动容器是,自动映射端口:
docker run -d -P apache_test:1.0
可以看到本机的随机端口49153
会映射到容器的80
端口!这样我们可以访问:http://localhost:49153/test.html
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
75f0731ec926 apache_test:1.0 "docker-php" 28 seconds ago Up 27 seconds 0.0.0.0:49153->80/tcp dreamy_rhodes
3.10 WORKDIR:设置工作目录
构建镜像的过程中,每个RUN都会新建一层,所以只有工作目录会一直存在!相当于构建镜像时,层与层之间的共享目录
!
3.11 USER:指定命令执行的用户
切换用户,然后执行接下来的指令
USER root
RUN touch root.txt
USER nobody
RUN touch nobody.txt