在前面两节我们学习了如何安装以及简单的运行管理docker容器,在本节我们将会更多的探讨关于docker镜像的知识问题。
1、什么是Docker镜像
Docker镜像是由文件系统叠加而成。 最底端是一个引导文件系统, 即bootfs,这很像典型的Linux/Unix的引导文件系统。 Docker用户几乎永远不会和引导文件系统有什么交互。 实际上, 当一个容器启动后, 它将会被移到内存中, 而引导文件系统则会被卸载(unmount) , 以留出更多的内存供initrd磁盘镜像使用。
Docker镜像的第二层是root文件系统rootfs, 它位于引导文件系统之上。 rootfs可以是一种或多种操作系统(如Debian或者Ubuntu文件系统)。在Docker里, root文件系统永远只能是只读状态, 并且Docker利用联合加载(union mount) 技术又会在root文件系统层上加载更多的只读文件系统。 联合加载指的是一次同时加载多个文件系统, 但是在外面看起来只能看到一个文件系统。 联合加载会将各层文件系统叠加到一起, 这样最终的文件系统会包含所有底层的文件和目录。
Docker将这样的文件系统称为镜像。 一个镜像可以放到另一个镜像的顶部。 位于下面的镜像称为父镜像(parent image),可以依次类推,直到镜像栈的最底部,最底部的镜像称为基础镜像(base image)。最后,当从一个镜像启动容器时, Docker会在该镜像的最顶层加载一个读写文
件系统。 我们想在Docker中运行的程序就是在这个读写层中执行的。
当Docker第一次启动一个容器时, 初始的读写层是空的。 当文件系统发生变化时, 这些变化都会应用到这一层上。 比如, 如果想修改一个文件,这个文件首先会从该读写层下面的只读层复制到该读写层。 该文件的只读版本依然存在, 但是已经被读写层中的该文件副本所隐藏。
通常这种机制被称为写时复制(copy on write) , 这也是使Docker如此强大的技术之一。 每个只读镜像层都是只读的, 并且以后永远不会变化。 当创建一个新容器时, Docker会构建出一个镜像栈,并在栈的最顶端添加一个读写层。 这个读写层再加上其下面的镜像层以及一些配置数据,就构成了一个容器。
2、查看docker镜像
使用docker images
可以查看本地已有的镜像:
[01:52 shexuan@hulab ~]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/ubuntu latest ea4c82dcd15a 2 weeks ago 85.8 MB
docker.io/ubuntu 12.04 5b117edd0b76 19 months ago 104 MB
上面的结果中第一列是镜像的来源仓库,可以看到两个镜像都来自于同一个仓库,只是版本不同。其中第二列为镜像标签,每个标签对组成特定镜像的一些镜像层进行标记(比如, 标签12.04就是对所有Ubuntu 12.04镜像的层的标记)。这种机制使得在同一个仓库中可以存储多个镜像。
我们可以通过在仓库名后面加上一个冒号和标签名来指定该仓库中的某一镜像:
[02:07 shexuan@hulab ~]$ docker run --rm -it ubuntu:12.04 /bin/bash
root@229010a0aed7:/#
在构建容器时指定仓库的标签也是一个很好的习惯。 这样便可以准确地指定容器来源于哪里。 不同标签的镜像会有不同, 比如Ubutnu 12.04和14.04就不一样, 指定镜像的标签会让我们确切知道自己使用的哪一个镜像来构建容器的。
Docker Hub中有两种类型的仓库: 用户仓库(user repository) 和顶层仓库(top-level repository) 。 用户仓库的镜像都是由Docker用户创建的,而顶层仓库则是由Docker内部的人来管理的。用户仓库的命名由用户名和仓库名两部分组成,如 jamtur01/puppet
, 用户名: jamtur01, 仓库名: puppet。
3、拉取镜像
用docker run命令从镜像启动一个容器时, 如果该镜像不在本地,Docker会先从Docker Hub下载该镜像。 如果没有指定具体的镜像标签,那么Docker会自动下载latest标签的镜像。
此外,还可以通过docker pull
命令先发制人地将该镜像拉取到本地。
[02:32 shexuan@hulab /data/docker]$ docker pull fedora:20
20: Pulling from library/fedora
4abd98c7489c: Pull complete
Digest: sha256:5d5a02b873d298da9bca4b84440c5cd698b0832560c850d92cf389cef58bc549
Status: Downloaded newer image for fedora:20
[02:33 shexuan@hulab /data/docker]$ docker pull fedora:21
21: Pulling from library/fedora
fced6e4a4c06: Pull complete
Digest: sha256:a268e5e12257c7770eb44c24041baf5e728fba2eed1a84f007b81845ded0a485
Status: Downloaded newer image for fedora:21
[02:36 shexuan@hulab /data/docker]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest ea4c82dcd15a 2 weeks ago 85.8MB
ubuntu 12.04 5b117edd0b76 19 months ago 104MB
fedora 20 ba74bddb630e 2 years ago 291MB
fedora 21 ba6369d667d1 2 years ago 241MB
除了拉取指定的镜像外,我们还可以使用docker search
在命令行中搜索指定的镜像:
[02:39 shexuan@hulab ~]$ docker search puppet
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
puppet/puppetserver A Docker Image for running Puppet Server. Wi… 71
alekzonder/puppeteer GoogleChrome/puppeteer image and screenshots… 40 [OK]
devopsil/puppet Dockerfile for a container with puppet insta… 27 [OK]
macadmins/puppetmaster Simple puppetmaster based on CentOS 6 26 [OK]
... ...
上面的命令在Docker Hub上查找了所有带有puppet的镜像。 这条命令会完成镜像查找工作, 并返回如下信息:
- 仓库名;
- 镜像描述;
- 用户评价(Stars) —反应出一个镜像的受欢迎程度;
- 是否官方(Official) —由上游开发者管理的镜像(如fedora镜像由Fedora团队管理) ;
- 自动构建(Automated) —表示这个镜像是由Docker Hub的自动构建(Automated Build) 流程创建的。
除了在命令行中搜索镜像外,还可以在Docker Hub官网搜索镜像。
4、构建镜像
构建Docker镜像有以下两种方法:
- 使用docker commit命令;
- 使用docker build命令和Dockerfile文件。
但是不推荐使用docker commit命令, 而应该使用更灵活、 更强大的Dockerfile来构建Docker镜像。
4.5.1 创建Docker Hub账号
可以从https://hub.docker.com/account/signup/加入创建账号Docker Hub。
创建完账号就可以在本地命令行进行登陆了。
[02:53 shexuan@hulab ~]$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.comto create one.
Username: shexuan
Password:
WARNING! Your password will be stored unencrypted in /home/shexuan/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
4.5.2 用Docker的commit命令创建镜像
创建Docker镜像的第一种方法是使用docker commit命令。 可以将此想象为我们是在往版本控制系统里提交变更。 我们先创建一个容器, 并在容器里做出修改, 就像修改代码一样, 最后再将修改提交为一个新镜像。
下面在ubuntu基础镜像中安装一个vim软件然后提交构建一个新的镜像:
[02:54 shexuan@hulab ~]$ docker run -it ubuntu:latest /bin/bash
root@30e6af0c5b03:/# apt-get -yqq update
root@30e6af0c5b03:/# apt-get -y install vim
... ...
root@30e6af0c5b03:/# exit
# 使用commit构建镜像
[03:00 shexuan@hulab ~]$ docker commit -m "ubuntu with vim" -a "shexuan" 30e6af0c5b03 shexuan/ubuntu:vim
sha256:5520623782ff5157e5abc5273d8b6dadb1ea2d44dc27d9af390abcb860334fa9
#查看镜像
[03:02 shexuan@hulab ~]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
shexuan/ubuntu vim 5520623782ff 30 seconds ago 169MB
ubuntu latest ea4c82dcd15a 2 weeks ago 85.8MB
ubuntu latest ea4c82dcd15a 2 weeks ago 85.8MB
ubuntu 12.04 5b117edd0b76 19 months ago 104MB
fedora 20 ba74bddb630e 2 years ago 291MB
fedora 21 ba6369d667d1 2 years ago 241MB
-m
选项用来指定新创建的镜像的提交信息。 同时还指定了-a
选项, 用来列出该镜像的作者信息。 接着指定了想要提交的容器的ID。 最后的shexuan/ubuntu:vim
指定了镜像的用户名和仓库名, 并为该镜像增加了一个vim
标签。
可以用docker inspect命令来查看新创建的镜像的详细信息:
[03:05 shexuan@hulab ~]$ docker inspect shexuan/ubuntu:vim
[
{
"Id": "sha256:5520623782ff5157e5abc5273d8b6dadb1ea2d44dc27d9af390abcb860334fa9",
"RepoTags": [
"shexuan/ubuntu:vim"
],
... ...
4.5.3 用Dockerfile构建镜像
推荐使用Dockerfile方法来代替docker commit,因为通过前者来构建镜像更具备可重复性、透明性以及幂等性。
一旦有了Dockerfile, 我们就可以使用docker build命令基于该Dockerfile中的指令构建一个新的镜像。
第一个Dockerfile
[03:11 shexuan@hulab ~]$ mkdir static_web
[03:11 shexuan@hulab ~]$ cd static_web/
[03:11 shexuan@hulab ~/static_web]$ touch Dockerfile
我们创建了一个名为static_web的目录用来保存Dockerfile, 这个目录就是我们的构建环境(build environment) , Docker则称此环境为上下文(context) 或者构建上下文(build context) 。 Docker会在构建镜像时将构建上下文和该上下文中的文件和目录上传到Docker守护进程。 这样Docker守护进程就能直接访问用户想在镜像中存储的任何代码、 文件或者其他数据。
我们还创建了一个空Dockerfile, 下面就通过一个例子来看看如何通过Dockerfile构建一个能作为Web服务器的Docker镜像:
# Dockerfile 内容
[03:17 shexuan@hulab ~/static_web]$ cat Dockerfile
# Version: 0.0.1
# 基础镜像为 ubuntu:14.04
FROM ubuntu:14.04
# 创建者信息
MAINTAINER shexuan "[email protected]"
# 运行命令,安装 nginx
RUN apt-get update && apt-get install -y nginx
RUN echo 'Hi, I am in your container' \
>/usr/share/nginx/html/index.html
# 指定Docker该容器内的应用程序将会使用容器的指定端口
EXPOSE 80
该Dockerfile由一系列指令和参数组成。 每条指令, 如FROM, 都必须为大写字母, 且后面要跟随一个参数。Dockerfile中的指令会按顺序从上到下执行, 所以应该根据需要合理安排指令的顺序。
每条指令都会创建一个新的镜像层并对镜像进行提交。 Docker大体上按照如下流程执行Dockerfile中的指令:
- Docker从基础镜像运行一个容器。执行一条指令, 对容器做出修改;
- 执行类似docker commit的操作, 提交一个新的镜像层;
- Docker再基于刚提交的镜像运行一个新容器;
- 执行Dockerfile中的下一条指令, 直到所有指令都执行完毕。
默认情况下, RUN指令会在shell里使用命令包装器/bin/sh -c
来执行。如果是在一个不支持shell的平台上运行或者不希望在shell中运行(比如避免shell字符串篡改) , 也可以使用exec格式的RUN指令,在这种方式中, 我们使用一个数组来指定要运行的命令和传递给该命令的每个参数:
RUN [ "apt-get", " install", "-y", "nginx" ]
基于Dockerfile构建新镜像
执行docker build
命令时, Dockerfile中的所有指令都会被执行并且提交, 并且在该命令成功结束后返回一个新镜像。 下面就来看看如何构建一个新镜像:
[03:17 shexuan@hulab ~/static_web]$ docker build -t="shexuan/static_web:v1" .
Sending build context to Docker daemon 2.048kB # 这里可以看到构建环境上下文已经上传到了docker 守护进程中
Step 1/5 : FROM ubuntu:14.04
14.04: Pulling from library/ubuntu
027274c8e111: Pull complete
d3f9339a1359: Pull complete
872f75707cf4: Pull complete
dd5eed9f50d5: Pull complete
Digest: sha256:e6e808ab8c62f1d9181817aea804ae4ba0897b8bd3661d36dbc329b5851b5637
Status: Downloaded newer image for ubuntu:14.04
---> f216cfb59484
Step 2/5 : MAINTAINER shexuan "[email protected]"
---> Running in b2c3d41d5dc2
Removing intermediate container b2c3d41d5dc2
---> 4b5254d5a9c8
Step 3/5 : RUN apt-get update && apt-get install -y nginx
---> Running in d455cbb97664
... ...
Setting up nginx (1.4.6-1ubuntu3.8) ...
Processing triggers for libc-bin (2.19-0ubuntu6.14) ...
Processing triggers for sgml-base (1.26+nmu4ubuntu1) ...
Removing intermediate container d455cbb97664
---> 04d107396cb1
Step 4/5 : RUN echo 'Hi, I am in your container' >/usr/share/nginx/html/index.html
---> Running in ad6c71d10114
Removing intermediate container ad6c71d10114
---> 021ff3c49752
Step 5/5 : EXPOSE 80
---> Running in e128310bee23
Removing intermediate container e128310bee23
---> aba837fa8cf1
Successfully built aba837fa8cf1
Successfully tagged shexuan/static_web:v1
通过指定-t
选项可以为新镜像设置了仓库和名称以及标签。
可以看到Dockerfile中的每条指令会被顺序执行, 而且作为构建过程的最终结果, 返回了新镜像的ID, 即aba837fa8cf1。 构建的每一步及其对应指令都会独立运行, 并且在输出最终镜像ID之前, Docker会提交每步的构建结果。
指令失败时候会怎样?
下面来看一个例子: 假设我们在第4步中将软件包的名字弄错了, 比如写成了ngin,再来运行一遍构建过程并看看当指令失败时会怎样:
[03:32 shexuan@hulab ~/static_web]$ docker build -t="shexuan/static_web:v2" .
Sending build context to Docker daemon 2.048kB
Step 1/5 : FROM ubuntu:14.04
---> f216cfb59484
Step 2/5 : MAINTAINER shexuan "[email protected]"
---> Using cache
---> 4b5254d5a9c8
Step 3/5 : RUN apt-get update && apt-get install -y ngin
---> Running in ba6d6583e2cb
... ...
Reading state information...
E: Unable to locate package ngin
The command '/bin/sh -c apt-get update && apt-get install -y ngin' returned a non-zero code: 100
从上面可以看到镜像构建到第三步时出现了错误。
这时我们可以直接使用前面一个镜像的ID,也即第二步是生成的中间镜像4b5254d5a9c8
进入交互模式进行调试直到找到错误原因更改Dockerfile后再来构建镜像。
[03:35 shexuan@hulab ~/static_web]$ docker run -i -t 4b5254d5a9c8 /bin/bash
root@f253488eff83:/#
在第二次构建镜像的过程中我们可以发现Step 2/5
那里并没有像第一次运行时出现running的情况而是直接使用的cache,这是由于每一步的构建过程都会将结果提交为镜像,它会将之前的镜像层看作缓存。在我们的调试例子里, 我们不需要在第1步到第2步之间进行任何修改, 因此Docker会将之前构建时创建的镜像当做缓存并作为新的开始点。 实际上, 当再次进行构建时, Docker会直接从第3步开始。
然而, 有些时候需要确保构建过程不会使用缓存。 比如, 如果已经缓存了前面的第3步, 即apt-get update
, 那么Docker将不会再次刷新APT包的缓存。 这时用户可能需要取得每个包的最新版本。 要想略过缓存功能, 可以使用docker build的--no-cache
标志。
一个避免忘记添加--no-cache
的方法是在Dockerfile文件起始的地方添加镜像构建时间环境变量,每次构建时候更改时间便能避免使用缓存构建镜像了:
FROM fedora:20
MAINTAINER James Turnbull "[email protected]"
ENV REFRESHED_AT 2014-07-01
RUN yum -q makecache
最后查看一下我们构建的镜像:
# 使用改正后的Dockerfile构建镜像,并使用不同的标签
[03:46 shexuan@hulab ~/static_web]$ docker build -t="shexuan/static_web:v2".
Sending build context to Docker daemon 2.048kB
Step 1/5 : FROM ubuntu:14.04
---> f216cfb59484
Step 2/5 : MAINTAINER shexuan "[email protected]"
---> Using cache
---> 4b5254d5a9c8
Step 3/5 : RUN apt-get update && apt-get install -y nginx
---> Using cache
---> 04d107396cb1
Step 4/5 : RUN echo 'Hi, I am in your container' >/usr/share/nginx/html/index.html
---> Using cache
---> 021ff3c49752
Step 5/5 : EXPOSE 80
---> Using cache
---> aba837fa8cf1
Successfully built aba837fa8cf1
Successfully tagged shexuan/static_web:v2
# 查看构建的镜像
[03:46 shexuan@hulab ~/static_web]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
shexuan/static_web v1 aba837fa8cf1 20 minutes ago 222MB
shexuan/static_web v2 aba837fa8cf1 20 minutes ago 222MB
shexuan/ubuntu vim 5520623782ff 45 minutes ago 169MB
ubuntu 14.04 f216cfb59484 2 weeks ago 188MB
ubuntu latest ea4c82dcd15a 2 weeks ago 85.8MB
ubuntu latest ea4c82dcd15a 2 weeks ago 85.8MB
ubuntu 12.04 5b117edd0b76 19 months ago 104MB
fedora 20 ba74bddb630e 2 years ago 291MB
fedora 21 ba6369d667d1 2 years ago 241MB
#查看镜像构建过程
[03:48 shexuan@hulab ~/static_web]$ docker history shexuan/static_web:v1
IMAGE CREATED CREATED BY SIZE COMMENT
aba837fa8cf1 26 minutes ago /bin/sh -c #(nop) EXPOSE 80 0B
021ff3c49752 26 minutes ago /bin/sh -c echo 'Hi, I am in your container'… 27B
04d107396cb1 26 minutes ago /bin/sh -c apt-get update && apt-get install… 34.1MB
4b5254d5a9c8 27 minutes ago /bin/sh -c #(nop) MAINTAINER shexuan "66666… 0B
f216cfb59484 2 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
2 weeks ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
2 weeks ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0B
2 weeks ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 195kB
2 weeks ago /bin/sh -c #(nop) ADD file:ecefeeae93e44cb42… 188MB
运行一下试试:
[03:58 shexuan@hulab ~/static_web]$ docker run -d -p 127.0.0.1::80 --name static_web shexuan/static_web:v1 nginx -g "daemon off;"
2444bce449dc41403be0c3234024d94ab3eb9b9fdb9a6920745da3f637c45fab
[04:01 shexuan@hulab ~/static_web]$ docker port static_web
80/tcp -> 127.0.0.1:32768
[04:01 shexuan@hulab ~/static_web]$ curl localhost:32768
Hi, I am in your container
4.3.4 Dockerfile指令
我们已经看过了一些Dockerfile中可用的指令, 如RUN和EXPOSE。 但是, 实际上还可以在Dockerfile中放入很多其他指令, 这些指令包括CMD、 ENTRYPOINT、 ADD、 COPY、 VOLUME、 WORKDIR、 USER、 ONBUILD、和ENV等。
CMD
CMD指令用于指定一个容器启动时要运行的命令。 这有点儿类似于RUN指令, 只是RUN指令是指定镜像被构建时要运行的命令, 而CMD是指定容器被启动时要运行的命令。 这和使用docker run命令启动容器时指定要运行的命令非常类似。
此外,需要注意的是使用docker run命令可以覆盖CMD指令。 如果我们在Dockerfile里指定了CMD指令, 而同时在docker run命令行中也指定了要运行的命令, 命令行中指定的命令会覆盖Dockerfile中的CMD指令。
ENTRYPOINT
ENTRYPOINT指令与CMD指令非常类似, 也很容易和CMD指令弄混。正如我们已经了解到的那样, 我们可以在docker run
命令行中覆盖CMD指令。 有时候, 我们希望容器会按照我们想象的那样去工作, 这时候CMD就不太合适了。 而ENTRYPOINT指令提供的命令则不容易在启动容器时被覆盖。实际上,docker run
命令行中指定的任何参数都会被当做参数再次传递给ENTRYPOINT指令中指定的命令。
我们也可以组合使用ENTRYPOINT和CMD指令来完成一些巧妙的工作。 比如, 我们可能想在Dockerfile里指定如下代码:
ENTRYPOINT ["/usr/sbin/nginx"]
CMD ["-h"]
此时当我们启动一个容器时, 任何在命令行中指定的参数都会被传递给Nginx守护进程。 比如, 我们可以指定-g "daemon off";参数让Nginx守护进程以前台方式运行。 如果在启动容器时不指定任何参数, 则在CMD指令中指定的-h参数会被传递给Nginx守护进程, 即Nginx服务器会以/usr/sbin/nginx -h的方式启动, 该命令用来显示Nginx的帮助信息。
这使我们可以构建一个镜像, 该镜像既可以运行一个默认的命令, 同时它也支持通过docker run命令行为该命令指定可覆盖的选项或者标志。
如果确实需要, 用户也可以在运行时通过docker run的--entrypoint标志覆盖ENTRYPOINT指令。
WORKDIR
WORKDIR指令用来在从镜像创建一个新容器时, 在容器内部设置一个工作目录, ENTRYPOINT和/或CMD指定的程序会在这个目录下执行。
我们可以使用该指令为Dockerfile中后续的一系列指令设置工作目录, 也可以为最终的容器设置工作目录。
WORKDIR /opt/webapp/db
RUN bundle install
WORKDIR /opt/webapp
ENTRYPOINT [ "rackup" ]
也可以通过-w
标志在运行时覆盖工作目录。
ENV
ENV指令用来在镜像构建过程中设置环境变量。这些环境变量也会被持久保存到从我们的镜像创建的任何容器中。
ENV TARGET_DIR /opt/app
WORKDIR $TARGET_DIR
也可以使用docker run命令行的-e标志来传递环境变量。 这些变量将只会在运行时有效:
$ sudo docker run -ti -e "WEB_PORT=8080" ubuntu env
USER
USER指令用来指定该镜像会以什么样的用户去运行。
我们可以指定用户名或UID以及组或GID, 甚至是两者的组合。
USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group
也可以在docker run命令中通过-u
标志来覆盖该指令指定的值。
VOLUME
VOLUME指令用来向基于镜像创建的容器添加卷。 一个卷是可以存在于一个或者多个容器内的特定的目录, 这个目录可以绕过联合文件系统,并提供如下共享数据或者对数据进行持久化的功能。
- 卷可以在容器间共享和重用;
- 一个容器可以不是必须和其他容器共享卷;
- 对卷的修改是立时生效的;
- 对卷的修改不会对更新镜像产生影响;
- 卷会一直存在直到没有任何容器再使用它。
卷功能让我们可以将数据(如源代码) 、 数据库或者其他内容添加到镜像中而不是将这些内容提交到镜像中, 并且允许我们在多个容器间共享这些内容。 我们可以利用此功能来测试容器和内部的应用程序代码, 管理日志, 或者处理容器内部的数据库。
VOLUME ["/opt/project"]
这条指令将会为基于此镜像创建的任何容器创建一个名为/opt/project
的挂载点。
ADD
ADD指令用来将构建环境下的文件和目录复制到镜像中。ADD指令需要源文件位置和目的文件位置两个参数:
ADD software.lic /opt/application/software.lic
这里的ADD指令将会将构建目录下的software.lic
文件复制到镜像中的/opt/application/software.lic
。 指向源文件的位置参数可以是一个URL, 或者构建上下文或环境中文件名或者目录。 不能对构建目录或者上下文之外的文件进行ADD操作。
在ADD文件时, Docker通过目的地址参数末尾的字符来判断文件源是目录还是文件。 如果目标地址以/
结尾, 那么Docker就认为源位置指向的是一个目录。如果目的地址不是以/
结尾, 那么Docker就认为源位置指向的是文件。
文件源也可以使用URL的格式:
ADD http://wordpress.org/latest.zip /root/wordpress.zip
最后值得一提的是, ADD在处理本地归档文件(tar archive) 时还有一些小魔法。 如果将一个归档文件(合法的归档文件包括gzip、 bzip2、 xz)指定为源文件, Docker会自动将归档文件解开(unpack)。
ADD latest.tar.gz /var/www/wordpress/
这条命令会将归档文件latest.tar.gz解开到/var/www/wordpress/目录下。
COPY
COPY指令非常类似于ADD, 它们根本的不同是COPY只关心在构建上下文中复制本地文件, 而不会去做文件提取(extraction) 和解压( decompression) 的工作。
文件源路径必须是一个与当前构建环境相对的文件或者目录, 本地文件都放到和Dockerfile同一个目录下。
LABEL
LABEL指令用于为Docker镜像添加元数据。 元数据以键值对的形式展现。
LABEL version="1.0"
LABEL location="New York" type="Data Center" role="Web Server"
LABEL指令以label="value"的形式出现。 可以在每一条指令中指定一个元数据, 或者指定多个元数据, 不同的元数据之间用空格分隔。 推荐将所有的元数据都放到一条LABEL指令中, 以防止不同的元数据指令创建过多镜像层。
STOPSIGNAL
TOPSIGNAL指令用来设置停止容器时发送什么系统调用信号给容器。这个信号必须是内核系统调用表中合法的数, 如9, 或者SIGNAME格式中的信号名称, 如SIGKILL。
ARG
ARG指令用来定义可以在docker build命令运行时传递给构建运行时的变量, 我们只需要在构建时使用--build-arg
标志即可。 用户只能在构建时指定在Dockerfile文件中定义过的参数。
上面例子中第二条ARG指令设置了一个默认值, 如果构建时没有为该参数指定值, 就会使用这个默认值。
$ docker build --build-arg build=1234 -t jamtur01/webapp .
这里构建jamtur01/webapp镜像时, build变量将会设置为1234,而webapp_user变量则会继承设置的默认值user。
ONBUILD
ONBUILD指令能为镜像添加触发器(trigger) 。 当一个镜像被用做其他镜像的基础镜像时(比如用户的镜像需要从某未准备好的位置添加源代码, 或者用户需要执行特定于构建镜像的环境的构建脚本) , 该镜像中的触发器将会被执行。
ONBUILD触发器会按照在父镜像中指定的顺序执行, 并且只能被继承一次(也就是说只能在子镜像中执行, 而不会在孙子镜像中执行)。
5、将镜像推送到Docker Hub
镜像构建完毕之后, 我们也可以将它上传到Docker Hub上面去, 这样其他人就能使用这个镜像了。 比如, 我们可以在组织内共享这个镜像, 或者完全公开这个镜像。
我们可以通过docker push
命令将镜像推送到Docker Hub。
[05:02 shexuan@hulab ~]$ docker push shexuan/static_web:v1
The push refers to repository [docker.io/shexuan/static_web]
04b77fe9aaad: Pushed
65a0411b4c56: Pushed
534823441154: Mounted from library/ubuntu
6e62bd7245ce: Mounted from library/ubuntu
cce4153cfb1c: Mounted from library/ubuntu
adb1f7ffb432: Mounted from library/ubuntu
v1: digest: sha256:ef85171a44b8a7993dbcf7b42179f68f2e1a8258c4b8f44d3680512009e53225 size: 1571
还可以设置github关联自动构建镜像,具体参见官方文档。
6、删除镜像
如果不再需要一个镜像了, 也可以将它删除。 可以使用docker rmi
命令来删除一个或多个镜像。
[05:02 shexuan@hulab ~]$ docker rmi shexuan/static_web:v1
Untagged: shexuan/static_web:v1
正如前面看到的,也可以使用组合命令来一次性删除所有镜像:
$ docker rmi `docker images -a -q`
7、运行自己的Docker Registry
有时候我们可能希望构建和存储包含不想被公开的信息或数据的镜像。 这时候我们有以下两种选择。
- 利用Docker Hub上的私有仓库;
- 在防火墙后面运行你自己的Registry。
从容器运行Registry
从Docker容器安装一个Registry非常简单:
$ docker run -p 5000:5000 registry:2
该命令将会启动一个运行Registry应用2.0版本的容器, 并将5000端口绑定到本地宿主机。
测试新Registry
看看是否能将本地已经存在的镜像jamtur01/static_web上传到我们的新Registry上去。
首先, 我们需要通过docker images命令来找到这个镜像的ID:
$ sudo docker images jamtur01/static_web
REPOSITORY TAG ID CREATED SIZE
jamtur01/static_web latest 22d47c8cb6e5 24 seconds ago 12.29 kB
(virtual 326 MB)
接着, 我们找到镜像ID, 即22d47c8cb6e5, 并使用新的Registry给该镜像打上标签。 为了指定新的Registry目的地址, 需要在镜像名前加上主机名和端口前缀。 在这个例子里, 我们的Registry主机名为docker.example.com。
sudo docker tag 22d47c8cb6e5 docker.example.com:5000/jamtur01/static_web
为镜像打完标签之后, 就能通过docker push命令将它推送到新的Registry中去了。
$ sudo docker push docker.example.com:5000/jamtur01/static_web
这个镜像就被提交到了本地的Registry中, 并且可以将其用于使用docker run
命令构建新容器:
$ sudo docker run -t -i docker.example.com:5000/jamtur01/static_web /bin/bash
这是在防火墙后面部署自己的Docker Registry的最简单的方式。
也有很多其他公司和服务提供定制的Docker Registry服务, 如Quay。