构建镜像的核心文件 Dockerfile
点击关注公众号,Java干货及时送达
Java技术迷 | 出品
Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。
通过Dockerfile我们能够将项目构建成一个镜像,这样的好处是可以将一个复杂的项目直接打包成一个镜像存储和运行。
Dockerfile提供了非常多的指令供我们操作,下面例举一些常用的命令:
指令 | 作用 |
FROM | 指定当前镜像是基于哪个镜像的 |
RUN | 构建镜像时需要运行的指令 |
EXPOSE | 当前容器对外暴露的端口号 |
WORKDIR | 指定在创建容器后终端默认登录进来的工作目录 |
ENV | 用于在构建镜像过程中设置环境变量 |
ADD | 将宿主机目录下的文件拷贝到镜像 |
COPY | 拷贝文件和目录到镜像 |
VOLUME | 容器数据卷,用于数据持久化 |
CMD | 指定一个容器启动时要运行的命令 |
ENTRYPOINT | 指定一个容器启动时要运行的命令 |
FROM
该指令用于指定当前镜像是基于哪个镜像进行构建的,因为镜像的构建是一层一层进行的,Docker Server会先基于某个基础镜像将项目打包成一个新镜像,再基于这个新镜像继续打包,以此类推,不断打包构建,最终生成一个处理完成的镜像。
下面就来简单地使用一下FROM指令,在/opt目录下新建一个test目录,并创建Dockerfile文件,编写如下内容:
FROM centos
文件内容非常简单,此时我们就可以根据Dockerfile进行镜像构建了,执行指令:
docker build -t mycentos_test01 .
这里我们指定FROM为centos,Docker Server将基于centos镜像进行构建,当Docker中没有该镜像时,还会先将centos镜像拉取下来再进行构建, .
表示Dockerfile文件所在的位置为当前目录:
[root@localhost test]# docker build -t mycentos_test01 .
Sending build context to Docker daemon 2.048kB
Step 1/1 : FROM centos
latest: Pulling from library/centos
7a0437f04f83: Pull complete
Digest: sha256:5528e8b1b1719d34604c87e11dcd1c0a20bedf46e83b5632cdeac91b8c04efc1
Status: Downloaded newer image for centos:latest
---> 300e315adb2f
Successfully built 300e315adb2f
Successfully tagged mycentos_test01:latest
当指令执行成功后,就会生成一个新的镜像,可以使用 docker images
指令进行查看:
[root@localhost test]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 300e315adb2f 7 months ago 209MB
mycentos_test01 latest 300e315adb2f 7 months ago 209MB
此时我们就可以启动这个镜像,同时进入交互终端:
docker run -it mycentos_test01
[root@31c51ab4d120 /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
可以看到这就是一个新的centos,这是因为我们只使用了FROM指令基于centos镜像构建,所以得到的镜像仍然还是一个基础的centos镜像。
需要注意的是Dockerfile文件中的第一条指令必须是 FROM
。
RUN
该指令会将在当前镜像之上的新层中执行任何命令并提交结果,生成的镜像将用于下一步。
修改Dockerfile文件,内容如下:
FROM centos
RUN yum install -y vim
我们知道在没加RUN命令之前构建出来的镜像就是一个centos,但现在,构建出来的镜像就是一个包含vim编辑器的centos系统,马上来构建它:
docker build -t mycentos_test02 .
构建成功后就可以进行启动:
docker run -it mycentos_test02
可以检查一下镜像中是否真的已经包含了vim编辑器:
[root@46992a057ea4 /]# rpm -qa|grep vim
vim-minimal-8.0.1763-15.el8.x86_64
vim-common-8.0.1763-15.el8.x86_64
vim-enhanced-8.0.1763-15.el8.x86_64
vim-filesystem-8.0.1763-15.el8.noarch
RUN指令还支持以数组的方式传递需要执行的命令,比如:
RUN ["yum","install","-y","vim"]
EXPOSE
该指令用于指定构建的镜像在运行时为对外暴露的端口,只有向外暴露了端口,外部才能够访问到这个进镜像提供的服务。
修改Dockerfile文件:
FROM centos
RUN ["yum","install","-y","vim"]
EXPOSE 80
但事实上centos镜像添加端口并没有作用,因为它不像tomcat、mysql、redis那样会向外部主机提供某项服务,所以可以将基础镜像指定为tomcat:
FROM tomcat:8.0
此时构建镜像然后启动:
docker run -p 80:80 mytomcat_test01
CMD
该指令用于为启动的容器指定需要执行的命令,修改Dockerfile:
FROM centos
RUN ["yum","install","-y","vim"]
EXPOSE 80
CMD ["echo","hello"]
构建镜像:
docker build -t mycentos_test03
此时启动镜像:
docker run -it mycentos_test03
因为CMD指令的作用,所以在启动这个镜像的时候就会立马执行CMD中的命令,从而输出 hello
字符串:
[root@localhost test]# docker run -it mycentos_test03
hello
需要注意的是Dockerfile中只能有一条CMD命令,如果写出了多条CMD,则以最后一条的内容为准。
ENTRYPOINT
该指令与CMD类似,也是用于指定容器启动时需要执行的命令,修改Dockerfile文件:
FROM centos
RUN ["yum","install","-y","vim"]
EXPOSE 80
ENTRYPOINT ["echo","hello"]
构建镜像:
docker build -t mycentos_test04 .
启动镜像:
[root@localhost test]# docker run -it mycentos_test04
hello
可以看到ENTRYPOINT和CMD两个指令的效果是一样的,那么它俩有什么区别呢?其区别在于命令的覆盖方式,对于CMD指令,我们可以在启动镜像的时候直接拼写命令来覆盖它,比如:
[root@localhost test]# docker run -it mycentos_test03 echo hello centos
hello centos
此时在Dockerfile中配置的CMD命令将会被这里的 echo hello centos
覆盖掉,最终输出 hello cnetos
。
然而对于ENTRYPOINT,它是无法通过直接追加命令来覆盖的,而是需要用到一个参数:
[root@localhost test]# docker run --entrypoint="echo hello centos" mycentos_test04
hello centos
由此可见,ENTRYPOINT指令对于命令的覆盖是比CMD指令更加复杂的,为此,可以将那些基本固定不变的命令配置到ENTRYPOINT中,而将需要修改的命令配置到CMD中,比如:
FROM centos
RUN ["yum","install","-y","vim"]
EXPOSE 80
ENTRYPOINT ["echo"]
CMD ["hello centos"]
WORKDIR
该指令用于指定镜像的落脚点,即:启动镜像后初始进入的目录,若是没有配置,则默认是 /
,比如:
[root@localhost test]# docker run -it mycentos_test02
[root@9c958bd3830f /]#
启动镜像后首先进入的便是 /
目录,但如果配置了WORKDIR:
FROM centos
WORKDIR /opt/work
构建镜像:
docker build -t mycentos_test05 .
启动镜像:
[root@localhost test]# docker run -it mycentos_test05
[root@37f177c1e546 work]# pwd
/opt/work
需要注意,只要配置了WORKDIR,无论你有没有在后续的指令中使用到它,WORKDIR配置的目录是一定会在镜像中被创建的。
ENV
该指令用于为镜像设置环境变量,比如:
FROM centos
ENV name centos
WORKDIR /$name
在这里配置了一个环境变量name,值为centos,并在WORKDIR指令中引用了该变量,所以通过该文件构建出来的镜像在启动时就会直接进入/centos目录,验证一下:
[root@localhost test]# docker build -t mycentos_test06 .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM centos
---> 300e315adb2f
Step 2/3 : ENV name centos
---> Running in 4c272c71fc62
Removing intermediate container 4c272c71fc62
---> 0c00112be488
Step 3/3 : WORKDIR /$name
---> Running in 19bb2ad9d3cb
Removing intermediate container 19bb2ad9d3cb
---> d7405fa45cb0
Successfully built d7405fa45cb0
Successfully tagged mycentos_test06:latest
[root@localhost test]# docker run -it mycentos_test06
[root@0a505138f024 centos]# pwd
/centos
VOLUME
该指令用于定义容器运行时可以挂载到宿主机的目录,修改一下Dockerfile:
FROM centos
ENV name centos
WORKDIR /$name
VOLUME /$name
此时配置了VOLUME,值为 /$name
,则容器中的/centos目录就允许被外部挂载。
构建镜像:
docker build -t mycentos_test07 .
然后在启动镜像的同时挂载一下数据卷:
docker run -it -v /opt/centos:/centos mycentos_test07
这样两个目录就实现了共享,此时在/opt/centos目录下的操作都会被同步到容器中的/centos目录:
[root@localhost test]# cd /opt/centos/
[root@localhost centos]# touch test
[root@localhost centos]# docker run -it -v /opt/centos:/centos mycentos_test07
[root@37450c713b08 centos]# ls
test
ADD
该指令用于将context目录中指定的文件复制到镜像的指令目录中去,那么首先我们就要知道何为context目录。
context目录意为上下文目录,指的是Dockerfile文件所在的目录,比如:
[root@localhost opt]# ll
total 262144
drwx--x--x. 4 root root 28 Jun 18 11:27 containerd
-rw-r--r--. 1 root root 268435456 Jun 23 15:46 disk.bin
-rw-r--r--. 1 root root 0 Jul 23 07:08 Dockerfile
drwxr-xr-x. 3 root root 25 Jul 22 12:45 javaproject
drwxr-xr-x. 8 10143 10143 273 Apr 7 19:26 jdk1.8.0_291
drwxr-xr-x. 2 root root 24 Jul 23 07:04 test
drwxr-xr-x. 8 root root 160 Jul 5 12:52 zookeeper-3.5.7
若是此时Dockerfile文件处在/opt目录下,则context目录则为/opt目录,因为DockerServer会将context目录中的所有文件,包括子目录及其子文件进行打包构建,所以这也是为什么我们一开始创建一个空的文件夹,并将Dockerfile放在其中,因为文件过多会导致构建速度变慢。
修改Dockerfile:
FROM centos
ENV name centos
WORKDIR /$name
VOLUME /$name
ADD test.txt /
在Dockerfile所在目录下有一个test.txt,现在要将其复制到容器中的 /
目录下,构建镜像:
docker build -t mycentos_test08 .
启动镜像:
[root@localhost test]# docker run -it mycentos_test08
[root@184a8824b583 centos]# cd /
[root@184a8824b583 /]# ls -l | grep test
-rw-r--r--. 1 root root 0 Jul 23 07:25 test.txt
若是想修改添加到容器后的文件名,则直接指定名字即可:
ADD test.txt /ntest.txt
ADD不仅能够添加文件、目录,还能够添加一个url,比如:
ADD http://www.baidu.com /test.html
则启动构建后的镜像时,便会在 /
目录下创建test.html文件,并将访问http://www.baidu.com所得到的响应结果写入该文件。
构建一个SpringBoot应用的镜像
大概了解Dockerfile中的一些指令之后,我们就可以通过它来构建一个SpringBoot应用的镜像了,所以首先来编写一个非常简单的SpringBoot应用:
@RestController
public class TestController {
@GetMapping("/testMethod")
public String testMethod(){
return "success";
}
}
创建一个控制器用于测试即可,然后将应用打成一个jar包,使用maven插件即可进行打包,指令如下:
mvn clean package
将得到的jar包上传至服务器:
[root@localhost ~]# cd /opt/test/
[root@localhost test]# ls -l | grep .jar
-rw-r--r--. 1 root root 16568766 Jul 23 07:56 SpringBootDemo-0.0.1-SNAPSHOT.jar
编写Dockerfile:
FROM openjdk:8 # 指定基础镜像
WORKDIR /test # 指定工作目录
ADD SpringBootDemo-0.0.1-SNAPSHOT.jar /test # 将jar包复制到容器中
EXPOSE 8080 # 向外暴露8080端口
ENTRYPOINT ["java","-jar"]
CMD ["SpringBootDemo-0.0.1-SNAPSHOT.jar"]
因为运行jar包需要JDK环境,所以直接使用openjdk8作为基础镜像即可,然后指定工作目录,接着将jar包复制到容器里的工作目录中,并向外暴露8080端口,最后使用ENTRYPOINT和CMD指令联合组成jar包的运行命令。
这样就可以构建镜像了:
docker build -t springbootdemo .
运行镜像:
docker run -p 8080:8080 springbootdemo
镜像启动后会自动运行jar包,此时外部主机就可以访问到pringBoot应用了:
上传镜像到服务器
这里以阿里云为例,我们将刚才构建的镜像上传至阿里云的镜像服务器中,首先登录阿里云,搜索 容器镜像服务
并开通:
开通后进入管理控制台,点击左侧的命名空间,创建一个命名空间:
然后点击左侧的镜像仓库,创建一个镜像仓库:
选择刚刚创建的命名空间,并填写仓库名称:
点击下一步后选择本地仓库:
点击创建镜像仓库,完成后可以看到阿里云提供的操作指南,我们照着操作指南进行操作即可。
首先登录到阿里云:
docker login --username=取个名字好困难a registry.cn-hangzhou.aliyuncs.com
然后会要求输入密码,密码在开通容器镜像服务的时候就会让你设置,如果没有设置密码,也可以在页面右侧的新手引导中点击访问凭证进行设置:
然后为镜像设置tag:
docker tag 8d4d7be1d926 registry.cn-hangzhou.aliyuncs.com/test_respository/springbootdemo:1.0.0
这里有两处地方需要设置(ImageId和版本号):
docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/test_respository/springbootdemo:[镜像版本号]
其中ImageId为镜像的id,通过 docker images
即可查询到,镜像版本号可以随意填写。
最后将镜像推送到服务器上:
docker push registry.cn-hangzhou.aliyuncs.com/test_respository/springbootdemo:1.0.0
此时点击镜像版本查看一下镜像是否成功上传了:
接下来测试一下拉取功能,首先删除掉之前构建的镜像:
docker rmi springbootdemo
执行指令进行拉取:
docker push registry.cn-hangzhou.aliyuncs.com/test_respository/springbootdemo:1.0.0
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.cn-hangzhou.aliyuncs.com/test_respository/springbootdemo 1.0.0 8d4d7be1d926 41 minutes ago 531MB
openjdk 8 f67a59e543c1 19 hours ago 514MB
centos latest 300e315adb2f 7 months ago 209MB
mycentos_test01 latest 300e315adb2f 7 months ago 209MB
tomcat 8.0 ef6a7c98d192 2 years ago 356MB
拉取下来的镜像就可以启动了,只不过镜像的名字变得比较长了:
docker run -p 8080:8080 registry.cn-hangzhou.aliyuncs.com/test_respository/springbootdemo:1.0.0
本文作者:汪伟俊 为Java技术迷专栏作者 投稿,未经允许请勿转载!