Dockerfile 书写方法(cookbook)
Dockerfile是docker构建镜像的推荐方式,Dockerfile使用基本的基于DSL语法的指令构建Docker镜像,之后使用docker build
命令基于该Dockerfile中的指令构建一个新的镜像。
简单的Dockerfile例子
下面的例子是创建一个nginx为反向代理的web服务器的docker容器。
- 首先创建一个构建环境(文件目录),用来存放Dockerfile等其他构建文件,因此这个环境也被称为上下文(context)或者构建上下文(build context)。
- 其次创建Dockerfile
$ mkdir sample_web
Dockerfile
1 | FROM ubuntu:14.04 |
然后在构建目录下输入下面的docker命令:$ docker build -t="nutshell/sample_web" .
注意命令的最后是一个“.”是告诉docker在当前目录下去寻找Dockerfile
一切正常,完成镜像构建。
下面就需要从新镜像启动容器,还记得启动容器的docker命令是什么嘛?
对就是 docker run <image name>
注意到我们的这个镜像是nginx,需要长时间运行的守护进程,查看docker run --help
的帮助文档后可以使用 -d
这个参数使容器以detach的方式在后台运行
于是我们就有了下面启动容器的docker命令:
1 | $ docker run -d -p 80 --name static_web nutshell/sample_web \ |
运行后没有报错,说明容器启动成功,这时候使用docker ps -l
查看容器的运行情况。
1 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
这里再来解释下命令中的 -p
参数,上面例子中的-p 80
的含义是docker在运行时在宿主机上 随机选择一个大端口号映射并连接到容器的80端口上。
在上面的例子中宿主机的端口是 32770
你会问了,那么我可不可以指定宿主机的端口?
可以,当然可以,具体的格式是 -p <host port>:<container port>
比如上面的例子中我们想将宿主机的8081端口映射到容器的80端口,那么启动的命令就可以这样写:
1 | $ docker run -d -p 8081:80 --name static_web nutshell/sample_web \ |
细心的你可能已经发现了,在dockerfile的最后一行EXPOSE 80
好像在整个过程中并没有什么卵用啊,是的,至少在这个时间点还没有什么卵用。
如果使用 -P
(注意大小写)
这个参数会将dockerfile中EXPOSE的端口绑定到宿主机上的一个随机端口上。
当然如果你对容器的端口映射到宿主机的端口不太清楚,可以使用docker port <container ID> <container port>
查询:
以上面的容器为例:
1 | $ docker port 04a89ad553a8 80 |
好了,可以在宿主机上验证下了
1 | $ curl localhost:32770 |
返回
1 | Hello from docker |
一个最简单的nginx web docker容器就搞定了。
稍微复杂点的例子
还是上面的例子,我们注意到在启动的时候还附加了一个nginx的命令,这个难道不能写到dockerfile里么?
当然可以,使用dockerfile的
CMD
ENTRYPOINT
就可以了。
以ENTRYPOINT
为例,只需在dockerfile的最后添加:
1 | ENTRYPOINT ["nginx","-g","daemon off;"] |
或者
1 | CMD ["nginx","-g","daemon off;"] |
重新build,重新run一个新容器,一切照旧,成功。
到这里你会问,既然一样为什么还要用两个指令?
正解是: CMD指令的命令可以被 “docker run” 后的命令覆盖,而ENTRYPOINT不会。(事实上,如果确实需要,可以在运行docker run 命令时 –entrypoint 来覆盖ENTRYPOINT指令)
是不是像点样子了?
其实还有空间可以提升,如果nginx的默认网页我们希望可以通过外部的文件部署替换,该怎么办?
ADD
COPY
只需将原dockerfile中的这一句
1 | RUN echo 'Hello from docker' /usr/share/nginx/html/index.html |
替换为
1 | ADD project/index.html /usr/share/nginx/html/index.html |
或者
1 | COPY project/index.html /usr/share/nginx/html/index.html |
重新build,重新run一个新容器,一切照旧,成功。
同样的,你会问,既然一样为什么要用两个命令?
正解是:当源文件是压缩文件的时候,目标文件是文件夹时就不一样了,ADD会自动提取,解压; 而COPY仅仅是拷贝
这样是不是就有点样子了?
其实dockerfile还有其他5个指令:
- WORKDIR
指定一个容器的工作目录 - ENV
设置容器的环境变量 - USER
指定用什么样的用户运行 - VOLUME
添加可以在容器间共享数据或者持久化数据的“卷” - ONBUILD
为镜像添加触发器