如何编写 Dockerfile 文件建立 Docker 镜像

1、前言

承接上篇文章 docker 镜像与容器,本篇来说讲如何建立 Dockerfile 来构建一个镜像。上篇文章有讲到构建一个自定义镜像是手动去构建的,虽然步骤清晰,可是操做比较繁琐,镜像分发起来也不是很方便,因此有必要用一种更好的办法去替换这种模式去建立自定义镜像,因而 Dockerfile 就是最优替代方案。废话少说,如今就来看看如何编写一个 Dockerfile 文件并建立容器镜像的,先说明一个本篇文章的运行环境吧,有看过上篇文章的朋友应该知道,我用的 docker 的镜像加速地址是阿里云的,我以为这是我用 docker 最无痛的环境了。html

2、Dockerfile 示例

# Base images 基础镜像
FROM centos

#MAINTAINER 维护者信息
MAINTAINER lorenwe 

#ENV 设置环境变量
ENV PATH /usr/local/nginx/sbin:$PATH

#ADD 文件放在当前目录下,拷过去会自动解压
ADD nginx-1.13.7.tar.gz /tmp/ 
#RUN 执行如下命令
RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 \ && yum update -y \ && yum install -y vim less wget curl gcc automake autoconf libtool make gcc-c++ zlib zlib-devel openssl openssl-devel perl perl-devel pcre pcre-devel libxslt libxslt-devel \ && yum clean all \ && rm -rf /usr/local/src/* RUN useradd -s /sbin/nologin -M www 
#WORKDIR 至关于cd
WORKDIR /tmp/nginx-1.13.7 
RUN ./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_ssl_module --with-pcre && make && make install 
RUN cd / && rm -rf /tmp/ 
COPY nginx.conf /usr/local/nginx/conf/ 
#EXPOSE 映射端口
EXPOSE 80 443

#ENTRYPOINT 运行如下命令
ENTRYPOINT ["nginx"] 
#CMD 运行如下命令
CMD ["-h"]复制代码

以上代码示例是我编写的一个认为颇有表明性的 dockerfile 文件,涉及到的内容很少,但基本上把全部 dockerfile 指令都用上了,也包含一些细节方面的东西,为了达到示例的效果因此并非最简洁的 dockerfile,创建一个文件夹将以上 dockerfile 放在该文件内,再去 nginx 官网把 nginx 源码包下来放到该文件夹内,以后再在该文件夹内打开命令行窗口,最好是以管理员权限打开命令行窗口,以避免出现一些权限问题的错误,此时的目录结构应该是如下样子的linux

dockerfile catalog
dockerfile catalog

3、指令分析

FROM 表示的是这个 dockerfile 构建镜像的基础镜像是什么,有点像代码里面类的继承那样的关系,基础镜像所拥有的功能在新构建出来的镜像中也是存在的,通常用做于基础镜像都是最干净的没有通过任何三方修改过的,好比我用的就是最基础的 centos,这里有必要说明一下,由于我用的镜像加速源是阿里云的,因此我 pull 下来的 centos 是自带阿里云的 yum 源的镜像,若是你用的不是阿里云的镜像加速源,pull 下来的镜像 yum 源也不同,到时 yum 安装软件的时候可能会遇到不少问题(你懂得)。nginx

MAINTAINER 就是维护者信息了,填本身名字就可了,不用说什么了c++

ENV 设置环境变量,简单点说就是设置这个可以帮助系统找到所须要运行的软件,好比我上面写的是 “ENV PATH /usr/local/nginx/sbin:$PATH”,这句话的意思就是告诉系统若是运行一个没有指定路径的程序时能够从 /usr/local/nginx/sbin 这个路径里面找,只有设置了这个,后面才能够直接使用 ngixn 命令来启动 nginx,否则的话系统会提示找不到应用。git

ADD 顾名思义,就是添加文件的功能了,可是他比普通的添加作的事情多一点,源文件能够是一个文件,或者是一个 URL 都行,若是源文件是一个压缩包,在构建镜像的时候会自动的把压缩包解压开来,示例我写的是 ‘ADD nginx-1.13.7.tar.gz /tmp/’ 其中 nginx-1.13.7.tar.gz 这个压缩包是必需要在 dockefile 文件目录内的,不在 dockerfile 文件目录内的 好比你写完整路径 D:test/nginx-1.13.7.tar.gz 都是会提示找不到文件的。docker

RUN 就是执行命令的意思了,RUN 能够执行多条命令, 用 && 隔开就行,若是命令太长要换行的话在末尾加上 ‘\’ 就能够换行命令,RUN 的含义很是简单,就是执行命令,但其中有一些细节仍是须要注意的,如今就经过上面的示例来看看须要注意的地方有哪些吧。其中 RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 的做用就是导入软件包签名来验证软件包是否被修改过了,为作到安全除了系统要官方的以外软件也要保证是可信的。yum update -y 升级全部包,改变软件设置和系统设置,系统版本内核都升级,咱们知道 linux 的软件存在依赖关系,有时咱们安装新的软件他所依赖的工具软件也须要是最新的,若是没有用这个命令去更新原来的软件包,就很容易形成咱们新安装上的软件出问题,报错提示不明显的状况下咱们更是难找到问题了,为避免此类状况发生咱们仍是先更新一下软件包和系统,虽然这会使 docker 构建镜像时变慢但也是值得的,至于后面的命令天然是安装各类工具库了,接着来看这句 yum clean all ,把全部的 yum 缓存清掉,这能够减小构建出来的镜像大小,rm -rf /usr/local/src/ 清除用户源码文件,都是起到减小构建镜像大小的做用。RUN 指令是能够分步写的,好比上面的 RUN 能够拆成如下这样:shell

# 不推荐
RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 \ RUN yum update -y \ RUN yum install -y vim less wget curl gcc automake autoconf libtool make gcc-c++ zlib zlib-devel openssl openssl-devel perl perl-devel pcre pcre-devel libxslt libxslt-devel \ RUN yum clean all \ RUN rm -rf /usr/local/src/*复制代码

这样也是能够的,可是最好不要这样,由于 dockerfile 构建镜像时每执行一个关键指令都会去建立一个镜像版本,这有点像 git 的版本管理,好比执行完第一个 RUN 命令后在执行第二个 RUN 命令时是会在一个新的镜像版本中执行,这会致使 yum clean all 这个命令失效,没有起到精简镜像的做用,虽然不推荐多写几个 RUN,但也不是说把全部的操做都放在一个 RUN 里面,这里有个原则就是把全部相关的操做都放在同一个 RUN 里面,就好比我把 yum 更新,安装工具库,清除缓存放在一个 RUN 里面,后面的编译安装 nginx 放在另一个 RUN 里面。vim

WORKDIR 表示镜像活动目录变换到指定目录,就至关于 linux 里面 cd 到指定目录同样,其实彻底没有必要使用这个指令的,在须要时能够直接使用 cd 命令就行,由于这里使用了 WORKDIR,因此后面的 RUN 编译安装 nginx 不用切换目录,讲到这里又想起了另一个问题,以下:centos

RUN cd /tmp/nginx-1.13.7 
RUN ./configure复制代码

这样可不能够呢,我想前面看懂的朋友应该知道答案了吧,这里仍是再啰嗦一下,这样是会报找不到 configure 文件错误的,缘由很简单,由于这个两个命令都不是在同一个镜像中执行的,第一个镜像 cd 进入的目录并不表明后面的镜像也进入了。浏览器

COPY 这个指令很简单,就是把文件拷贝到镜像中的某个目录,注意源文件也是须要在 dockerfile 所在目录的,示例的意思是拷贝一份 nginx 配置文件,如今就在 dockerfile 所在目录建立这个文件

user  www;
worker_processes  2;
daemon off;

pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}复制代码

配置很简单,就是对官方的配置文件把注释去掉了,注意里面的 daemon off; 配置,意思是关闭 nginx 后台运行,缘由在上一篇文章中讲过,这里再来絮叨一下,容器默认会把容器内部第一个进程是否活动做为docker容器是否正在运行的依据,若是 docker 容器运行完就退出了,那么docker容器便会直接退出,docker run 的时候把 command 做为容器内部命令,若是使用 nginx,那么 nginx 程序将后台运行,这个时候 nginx 并非第一个执行的程序,而是执行的 bash,这个 bash 执行了 nginx 指令后就挂了,因此容器也就退出了,若是咱们设置了 daemon off 后
启动 nginx 那么 nginx 就会一直占用命令窗口,天然 bash 无法退出了因此容器一直保持活动状态。

EXPOSE 示例注释写的是映射端口,但我以为用暴露端口来形容更合适,由于在使用 dockerfile 建立容器的时候不会映射任何端口,映射端口是在用 docker run 的时候来指定映射的端口,好比我把容器的 80 端口映射到本机的 8080 端口,要映射成功就要先把端口暴露出来,有点相似于防火墙的功能,把部分端口打开。

ENTRYPOINT 和 CMD 要放在一块儿来讲,这二者的功能都相似,但又有相对独特的地方,他们的做用都是让镜像在建立容器时运行里面的命令。固然前提是这个镜像是使用这个 dockerfile 构建的,也就是说在执行 docker run 时 ENTRYPOINT 和 CMD 里面的命令是会执行的,二者是能够单独使用,并不必定要同时存在,固然这二者仍是有区别的。

先从 CMD 说吧,CMD 的一个特色就是可被覆盖,好比把以前的 dockerfile 的 ENTRYPOINT 这一行删除,留下 CMD 填写["nginx"],构建好镜像后直接使用 docker run lorenwe/centos_nginx 命令执行的话经过 docker ps 能够看到容器正常运行了,启动命令也是 “ngixn”,可是咱们使用 docker run lorenwe/centos_nginx bin/bash 来启动的话经过 docker ps 查看到启动命令变成了 bin/bash,这就说明了 dockerfile 的 CMD 指令是可被覆盖的,也能够把他看作是容器启动的一个默认命令,能够手动修改的。

而 ENTRYPOINT 偏偏相反,他是不能被覆盖,也就是说指定了值后再启动容器时无论你后面写的什么 ENTRYPOINT 里面的命令必定会执行,一般 ENTRYPOINT 用法是为某个镜像指定必须运行的应用,例如我这里构建的是一个 centos_nginx 镜像,也就是说这个镜像只运行 ngixn,那么我就能够在 ENTRYPOINT 写上["nginx"],有些人在构建本身的基础镜像时(基础镜像只安装了一些必要的库)就只有 CMD 并写上 ['bin/bash'],当 ENTRYPOINT 和 CMD 都存在时 CMD 中的命令会以 ENTRYPOINT 中命令的参数形式来启动容器,例如上面的示例 dockerfile,在启动容器时会以命令为 nginx -h 来启动容器,遗憾的是这样不能保持容器运行,因此能够这样启动 docker run -it lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf,那么容器启动时运行的命令就是 nginx -c /usr/local/nginx/conf/nginx.conf,是否是颇有意思,能够自定义启动参数了。

固然还有一些没有用到的指令:

ARG,ARG指令用以定义构建时须要的参数,好比能够在 dockerfile中写上这句 ARG a_nother_name=a_default_value,ARG指令定义的参数,在docker build命令中以 --build -arg a_name=a_value 形式赋值,这个用的通常比较少。

VOLUME,VOLUME指令建立一个能够从本地主机或其余容器挂载的挂载点,用法是比较多的,都知道 docker 作应用容器比较方便,其实 docker 也可作数据容器,建立数据容器镜像的 dockerfile 就主要是用 VOLUME 指令,要讲明 VOLUME 用法有必要在开一篇文章,再此就不作介绍了,

USER,USER用来切换运行属主身份的。docker 默认是使用 root 用户,但若不须要,建议切换使用者身分,毕竟 root 权限太大了,使用上有安全的风险。LABEL,定义一个 image 标签。

4、构建演示

dockerfile 构建镜像的命令很简单,在个人示例中个人命令是 "docker build -t lorenwe/centos_nginx . ",注意后面的点不能省略,表示的从当前目录中寻找 dockerfile 来构建镜像

D:\docker\lorenwe>docker build -t lorenwe/centos_nginx .
Sending build context to Docker daemon  995.8kB
Step 1/13 : FROM centos
 ---> d123f4e55e12
Step 2/13 : MAINTAINER lorenwe
 ---> Running in e5c7274f50e8
 ---> 606f7222e69a
Removing intermediate container e5c7274f50e8
Step 3/13 : ENV PATH /usr/local/nginx/sbin:$PATH
 ---> Running in 23716b428809
 ---> 5d8ee1b5a899
         ....
Successfully built eaee6b40b151
Successfully tagged lorenwe/centos_nginx:latest复制代码

看到以上内容就说明成功,构建过程可能须要一点点时间,毕竟要安装一些软件,若是你跟我同样是配置的阿里云的容器源构建时应该不会出现什么问题,由于我以前是有拉取过 centos ,因此在 build 时直接使用本地的 centos,若是你没有拉取过 centos,那么在 build 时还会把 centos 拉取下来

D:\docker\lorenwe>docker images
REPOSITORY               TAG     IMAGE ID       CREATED          SIZE
lorenwe/centos_nginx     latest  eaee6b40b151   7 minutes ago    427MB
lorenwe/centos_net_tools latest  35f8073cede1   6 days ago       277MB
centos                   latest  d123f4e55e12   3 weeks ago      197MB
d4w/nsenter              latest  9e4f13a0901e   14 months ago    83.8kB

D:\docker\lorenwe>docker run -itd --name nginx1 lorenwe/centos_nginx
15d4f108dab7c2f276209ebeb501cac0d3be828e1e81bae22d3fd97c617439eb

D:\docker\lorenwe>docker ps
CONTAINER ID    IMAGE    COMMAND     CREATED    STATUS     PORTS     NAMES

D:\docker\lorenwe>docker ps -a
CONTAINER ID   IMAGE                 COMMAND    CREATED   STATUS   PORTS   NAMES
15d4f108dab7   lorenwe/centos_nginx  "nginx -h"                            nginx1

D:\docker\lorenwe>docker run -itd --name nginx2 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
b6b0e962ca3056d67c24145b08975ffddb9cc050fce5f09f65310fb323ffc1c3

D:\docker\lorenwe>docker ps
CONTAINER ID   IMAGE                 COMMAND        CREATED    STATUS    PORTS     NAMES
b6b0e962ca30   lorenwe/centos_nginx  "nginx -c /usr/loc..."              80/tcp    nginx2

D:\docker\lorenwe>docker run -itd -p 8080:80 --name nginx3 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
2f6997745641e3e3edbbfe5213e6235cab3b5a929f116a2c132df504156090c6

D:\docker\lorenwe>docker ps
CONTAINER ID   IMAGE                 COMMAND    CREATED   STATUS     PORTS                  NAMES
2f6997745641   lorenwe/centos_nginx  "nginx -c /usr/loc..."          0.0.0.0:8080->80/tcp   nginx3
b6b0e962ca30   lorenwe/centos_nginx  "nginx -c /usr/loc..."          80/tcp                 nginx2

D:\docker\lorenwe>docker stop nginx2
nginx2复制代码

其中 “docker run -itd -p 8080:80 --name nginx3 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf” 中的 -p 8080:80 表示把主机的 8080 端口映射到容器的 80 端口,由于以前咱们在 dockerfile 中把 80 端口暴露出来了,作好端口映射后如今就能够在主机中打开浏览器访问 127.0.0.1:8080 就能看到 nginx 的欢迎页面了 (^v^).

D:\docker\lorenwe>docker run -itd -v D:/docker/lorenwe/html:/usr/local/nginx/html  -p 8081:80 --name nginx4 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
cd2d4eb70a39057aed3bfcb64e1f03433e2054d7ff5d50098f49d2e6f2d9e02e复制代码

我再在原来的参数中加入了 -v 参数,其做用就是把一个本地主机的目录挂载到容器内部,这个目录是一个共享的状态,两边均可以进行修改,这就是容器的共享卷,其做用就不言而喻了,如今咱们在 D:\docker\lorenwe 的目录下新建一个叫 html 的文件夹,再在 html 文件夹内新建一个 index.html 随便写上一点内容后再去主机浏览器上访问一下 127.0.0.1:8081 看看是否是你想要看到内容。虽然经过 -v 参数能够知足大部分应用场景,可是 docker 的 VOLUME 还有其余更好用法,欲知后事如何,请看下回分解!

相关文章
相关标签/搜索