Docker 镜像(Image
)是一种分层结构的文件系统,基于Docker Hub中已构建好的镜像后,我们可以快速构建自己的镜像。还可以将自己构建的镜像免费推送到Docker Hub的用户仓库进行管理,然后就可以基于这些镜像创建容器。
1. 构建准备
1.1 创建帐号
构建镜像构建完成后,需要将镜像推送Docker Hub或自已私的有Regitry
中。本文使用Docker Hub,因此开始前需要首先注册一个Docker Hub帐号。可以在Docker Hub官网https://hub.docker.com完成帐号的注册。
注册时需要输入用户名、邮箱、帐号密码,注册后会收到一封激活邮件,需要登录邮箱完成帐号的激活。
注册后,可以通过docker login
命令登录Docker Hub:
$ sudo 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.com to create one. Username: itbilu Password: Login Succeeded
登录成功后,会收到Login Succeeded
提示。登录后,认证信息上会被保存(保存于$HOME/.docker/config.json
文件),以便之后使用。退出登录可以使用docker logout
命令。
1.2 构建方法
基于现有镜像构建新的Docker 镜像可以使用以下两种方式:
- 使用
docker commit
命令 - 使用
Dockerfile
文件和docker build
命令,即:编写Dockerfile
文件后,再通过docker build
命令构建
本文将介绍这两种构建方式。相对来说,第二种方法相对更灵活、功能也更强大,更推荐使用第二种方式来构建Docker 镜像。
2. docker commit
创建镜像
docker commit
可以通过修改容器创建新的镜像。这点类似于git commit
的提交代码更新,我们可以首先创建一个容器,然后对容器进行修改,修改完成后像提交代码一样将修改提交为一个新镜像。
docker commit
命令格式如下:
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
主要选项(OPTIONS
)如下:
-a, --author
- {string}, 作者(如:"John Hannibal Smith") -c, --change
- {list}, 使用Dockerfile指令来创建镜像(默认 [])-m, --message
- {string}, 提交备注信息-p, --pause
- {string}, 提交时暂停容器(默认 true)
创建容器
首先创建一个容器,创建容器的镜像依然使用之前使用的ubuntu
镜像:
$ sudo docker run -i -t --name itbilu_ubuntu ubuntu /bin/bash root@864770ace7fb:/#
安装软件
我们会将这个容器做为一个Web服务器使用,所以需要安装nginx
或apache
。
运行容器后,在容器中安装nginx
:
root@864770ace7fb:/# apt-get update root@864770ace7fb:/# apt-get install nginx
安装完成后,可以将当前状态保存下来,这样就不用每次都创建容器并重新安装软件了。docker commit
提交前,先退出容器:
root@864770ace7fb:/# exit
提交更改
提交时要通过容器名或容器ID指定所要提交的容器,并要指定一个目标仓库和镜像名。docker commit
提交时比较轻量,只会提交创建容器的镜像与容器当前状态之间有差异的部分。
如,提要刚才配置的容器itbilu_ubuntu
,并指定目标仓库和镜像名为itbilu/nginx
:
$ sudo docker commit itbilu_ubuntu itbilu/nginx sha256:548e7912739ad4efefe615baaf1d60c78cb60531fd9cd859332762674e6184dc
提交后,就可以通过docker images
命令看到新创建的容器:
$ sudo docker images itbilu/nginx REPOSITORY TAG IMAGE ID CREATED SIZE itbilu/nginx latest 548e7912739a 10 hours ago 226 MB
提交镜像时,还可以指定一些提交参数和标签等。如:
$ sudo docker commit -m "一个自定义容器" -a "何民三" itbilu_ubuntu itbilu/nginx:webserver
在这条命令中,我们通过-m
参数添加了一些提交备注,通过-a
参数添加了镜像作者,并为新镜像添加了webserver
标签。
查看镜像:
$ sudo docker images itbilu/nginx REPOSITORY TAG IMAGE ID CREATED SIZE itbilu/nginx webserver 0efe6ee86866 8 minutes ago 226 MB itbilu/nginx latest 548e7912739a 11 hours ago 226 MB
每次提交都会创建一个新镜像,在itbilu/nginx
仓库下现在有两个不同ID的镜像。现在使用docker inspect
命令查看新创建镜像的详细信息:
$ sudo docker inspect itbilu/nginx:webserver [{ "Id": "sha256:0efe6ee86866ad59d5ba14ff3c27e039894b010b76d54b2769396eb02bbc2444", … "Comment": "一个自定义容器", "Created": "2017-02-04T12:38:02.01058579Z", "Container": "864770ace7fbbc6b7abe13fc2b96e647d0ce5122de0b8c0de41a71a989959f10", … "DockerVersion": "1.13.0", "Author": "何民三", … }]
使用新镜像
镜像提交会,就可以通过使用提交的镜像来创建容器。
$ sudo docker run -i -t itbilu/nginx:webserver /bin/bash root@5d4f02240a10:/#
3. 使用Dockerfile
构建镜像
使用Dockerfile
和docker build
命令来构建镜像操作更灵活、过程可重复,因此也更推荐使用这种方式来构建镜像。
Dockerfile
基于DSL
(Domain Specific Language)语言构建Docker镜像,Dockerfile
编写完成后,就可以使用docker build
命令来构建一个新镜像。
3.1 创建Dockerfile
文件
首先创建一个目录用于初始化Dockerfile
文件
$ mkdir static_web_server $ cd static_web_server $ touch Dockerfile
如上所示,我们创建static_web_server
目录,并在其中创见了Dockerfile
文件。这个目录就是我们的构建环境,在Docker中,将这个环境称为上下文(content)或者构建上下文(build content)。构建镜像时,Docker会将构建环境中的文件和目录传递给守护进程,这样守护进程就访问到用户想在镜像中存储的任何代码、文件或其它数据。
接下来,编辑刚创建Dockerfile
文件,编写Web服务器的构建代码:
# Version: 0.0.1 FROM ubuntu:16.04 MAINTAINER 何民三 "cn.liuht@gmail.com" RUN apt-get update RUN apt-get install -y nginx RUN echo "Hello World, 我是个容器" \ > /var/www/html/index.html EXPOSE 80
在以上示例中,我们首先通过FROM
指定了一个基础镜像ubuntu:16.04
。在Dockerfile
中,FROM
命令只能有一个,该命令用于指定基础镜像,后续的命令都会基于该镜像进行。接着通过MAINTAINER
命令告诉Docker镜像的作者、联系邮箱。
接下来,通过三条RUN
语句安装软件环境。在这个示例中,首先通过RUN
更新了APT
源,然后安装了nginx
,最后创建一个文件/usr/share/nginx/html/index.html
并在其中添加了一些简单的示例文本。
RUN
语句表示要在镜像中运行的命令。默认情况下,RUN
指令会在/bin/sh -c
。如果不想使用shell
执行,可以exec
来运行RUN
命令,这时需要使用数组来传递指令和参数。如:
RUN ["apt-get", "install", "-y", "nginx"]
在Dockerfile
文件的最后,通过EXPOSE
命令对外开放了80
端口。出于安全考虑,Docker默认不会打开任何端口。EXPOSE
会告诉Docker容器内应用将要使用的端口(可以指定多个),但这并不意味着会自动打开该端口,还需要在docker run
运行容器时,通过--expose
参数来指定要打开的端口。
注意:Dockerfile
支持使用注释,注释以#
开头,如上例中的第一行。
构建镜像时,构建目录下的文件默认都会被传入守护进程,如果有不需要传递守护进程的文件。可以通过.dockerignore
文件指定,该文件类似.gitignore
文件,如果创建后会对每行进行模式匹配并排除符合条件的文件。
关于Dockerfile
文件
Dockerfile
是由一系列命令
和参数
组成的一个文件。其中,每条件命令都要大写(如:FROM
),且其后都要跟一个参数(如:ubuntu:16.04
)。构建镜像时,Dockerfile
中的命令会按顺序从上到下执行,在编写Dockerfile
文件时应注意各条命令的顺序安排。Dockerfile
文件中的每条命令,都会创建一个新的镜像层并会提交镜像。
Docker使用Dockerfile
构建镜像流程大致如下:
- 从基础镜像运行一个容器
- 执行一条命令,对容器进行修改
- 执行类似
docker commit
操作,提交一个新的镜像层 - 基于刚创建的镜像运行一个新容器
- 继续执行下一条命令,直到所有命令执行完
3.2 docker build
构建新镜像
Dockerfile
文件创建完成后,就可以通过docker build
命令来构建新镜像。执行docker build
命令时,Dockerfile
中的命令都会被执行和提交,且每次提交都会创建一个新镜像。
如,在上例中的构建过程如下:
$ sudo docker build -t="itbilu/static_web_server" ./ Sending build context to Docker daemon 2.048 kB Step 1/6 : FROM ubuntu:16.04 ---> f49eec89601e Step 2/6 : MAINTAINER 何民三 "cn.liuht@gmail.com" ---> Running in 5bab087b741a ---> 3f62a1fdbb79 Removing intermediate container 5bab087b741a Step 3/6 : RUN apt-get update ---> Running in 0efa222098eb ... Reading package lists... ---> d426a15eb005 Removing intermediate container 0efa222098eb Step 4/6 : RUN apt-get install -y nginx ---> Running in 322ec20871fc Reading package lists... ... Processing triggers for systemd (229-4ubuntu13) ... ---> 03df6f8f43d0 Removing intermediate container 322ec20871fc Step 5/6 : RUN echo "Hello World, 我是个容器" > /usr/share/nginx/html/index.html ---> Running in bc9f189a89d2 ---> 184542d2035e Removing intermediate container bc9f189a89d2 Step 6/6 : EXPOSE 80 ---> Running in 80d82f35cfea ---> a404b6967a02 Removing intermediate container 80d82f35cfea Successfully built a404b6967a02
在使用docker build
构建镜像时,我们通过-t
参数指定itbilu/static_web_server
做为镜像名,其中itbilu
表示仓库,static_web_server
表示镜像名。
构建镜像时,还可以为镜像设置标签,设置格式为镜像名:标签
。如:
$ sudo docker build -t="itbilu/static_web_server:v0.0.1" .
在构建时我们可以看到,构建上下文被传给了Docker的守护进程。在构建过程中,每执行一条命令都会有一次镜像创建提交,和使用上一步生成的镜像运行新容器的过程。如:
# 构建上下文发送至守护进程 Sending build context to Docker daemon 2.048 kB ... Step 3/6 : RUN apt-get update # 使用上一步创建的镜像运行新容器 ---> Running in 0efa222098eb ... Reading package lists... # 提交新镜像 ---> d426a15eb005
在命令的最后,通过.
(同./
)告诉Docker从本地当前工作目录查找Dockerfile
文件。除指定本地文件还,还可以使用Git
源代码仓库或一个文件URL。类似如下:
$ sudo docker build -t="itbilu/static_web_server:v0.0.1" \ git@github.com:itbilu/static_web_server
注意:以上git
仓库并不存在。当使用Git
地址需要Dockerfile
文件位于仓库的根目录下。
另外,Dockerfile
文件是docker build
默认查找的文件,如果不使用这个文件名,可以通过-f
参数指定文件路径。如:
$ sudo docker build -t="itbilu/static_web_server:v0.0.1" -f path/to/file
构建失败情况
构建过程中,每一步都会提交且生成新镜像,如果某一步出错,其上一步生成的镜像还是会被保留且可用。
如,假设安装nginx
时出错:
---> d426a15eb005 Step 4/6 : RUN apt-get install -y ngin ---> Running in fa9a54ab1fa3 Reading package lists... Building dependency tree... Reading state information... E: Unable to locate package ngin The command '/bin/sh -c apt-get install -y ngin' returned a non-zero code: 100
这时上一步生成的镜像d426a15eb005
依然可用,可使用上一步生成的镜像进行调试:
$ sudo docker run -t -t d426a15eb005 /bin/bash root@953676e604fb:/#
出错后,可使用上一步生成的镜像运行容器。然后在容器内运行出错的步骤进行调试,找出错误原因后,退出容器,改成Dockerfile
文件重新构建即可。
构建缓存
由于构建过程中的每一步都会将结果提交为镜像,Docker 会将这些镜像做为缓存使用。重新构建时,Docker会对比每一步生成的镜像,如果没有变化就不会重新生成镜像,以节约构建时间。如,前面构建出错的情况,重新构建时,Docker并不是从头开始执行,而是直接从上次出错的位置开始。
如果不希望使用缓存,可以为docker build
命令指定--no-cache
参数。如:
$ sudo docker build --no-cache -t="itbilu/static_web_server:v0.0.1" .
3.3 使用新镜像
查看新镜像
使用docker images
命令查看刚构建的镜像。
$ sudo docker images itbilu/static_web_server REPOSITORY TAG IMAGE ID CREATED SIZE itbilu/static_web_server latest a404b6967a02 4 hours ago 226 MB itbilu/static_web_server v0.0.1 a404b6967a02 4 hours ago 226 MB
在前面示例中,我们分别构建完成了itbilu/static_web_server
镜像的两个版本v0.0.1
和latest
。
如果想查看镜像的构建过程,可以使用docker history
命令查看:
$ sudo docker history itbilu/static_web_server IMAGE CREATED CREATED BY SIZE COMMENT a404b6967a02 4 hours ago /bin/sh -c #(nop) EXPOSE 80/tcp 0 B 184542d2035e 4 hours ago /bin/sh -c echo "Hello World, 我是个容器" > ... 29 B 03df6f8f43d0 4 hours ago /bin/sh -c apt-get install -y nginx 56.8 MB d426a15eb005 4 hours ago /bin/sh -c apt-get update 39.7 MB 3f62a1fdbb79 4 hours ago /bin/sh -c #(nop) MAINTAINER 何民三 "cn.liuh... 0 B f49eec89601e 2 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B <missing> 2 weeks ago /bin/sh -c mkdir -p /run/systemd && echo '... 7 B <missing> 2 weeks ago /bin/sh -c sed -i 's/^#\s*\(deb.*universe\... 1.9 kB <missing> 2 weeks ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0 B <missing> 2 weeks ago /bin/sh -c set -xe && echo '#!/bin/sh' >... 745 B <missing> 2 weeks ago /bin/sh -c #(nop) ADD file:68f83d996c38a09... 129 MB
通过docker history
命令可以查看镜像的每一层,及创建这些层的Dockerfile
命令。
使用
可以基于这些新构建的镜像启动一个新容器,以检查构建是否成功:
$ sudo docker run -d -p 80 --name static_web_server itbilu/static_web_server \ > nginx -g "daemon off;" 1aa73f0160b5951d07adb9ff6e0ddf4cb6730a127ad79a2a3ab52d4ceaa7203b
在这个示例中,我们通过-d
参数告诉Docker在后台运行容器,还通过--name
参数为容器指定了名称。而nginx -g "daemon off"
是要在容器中运行的命令,表示要以前台的方式启动Nginx。
还指定了一个-p
参数,这个是告诉Docker应该对宿主机开放的端口。端口开放给宿主机后,宿主机还需要分配一个端口映射到容器。可以通过以下两种方式分配:
- 随机分配 - Docker 会在宿主机上随机选择一个位于
32768~61000
之间的端口映射到容器 - 指定端口 - 在启动容器时直接指定映射端口
在前面启动容器时,我们并没有指定宿主机的映射端口,因此docker run
会在宿主机上随机分配一个端口,并映射到容器的80
端口。这时,可以通过docker ps
查看分配的端口:
$ sudo docker ps -l CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1129bdbb25de itbilu/static_web_server "nginx -g 'daemon ..." 6 seconds ago Up 5 seconds 0.0.0.0:32769->80/tcp static_web_server
还可以通过docker port
命令查看容器的端口映射情况(可以通过容器名或容器ID查看):
$ sudo docker port static_web_server 80/tcp -> 0.0.0.0:32769
如果不想使用随机端口,也可以docker run
启动容器时,通过-p
参数将容器端口直接映射到Docker宿主机的端口上:
$ sudo docker run -d -p 8080:80 --name static_web_server itbilu/static_web_server \ > nginx -g "daemon off;"
这样,就会将容器内的80
端口映射到宿主机的8080
端口上。
除一端口绑定外,-p
参数还可以绑定宿主机的IP,如:
$ sudo docker run -d -p 127.0.0.1:80:80 --name static_web_server itbilu/static_web_server \ > nginx -g "daemon off;"
这样,我们就将容器的80
端口绑定到了宿主机127.0.0.1
IP的80
端口上。绑定IP时,也支持绑定宿主机的随机端口如:
$ sudo docker run -d -p 127.0.0.1::80 --name static_web_server itbilu/static_web_server \ > nginx -g "daemon off;"
我们在前面创建Dockerfile
文件时,曾经通过EXPORT
向外公开了80
端口。这样,我们可以在运行容器时通过简单的-P
参数开放EXPORT
导出的所有端口:
$ sudo docker run -d -P --name static_web_server itbilu/static_web_server \ > nginx -g "daemon off;"
使用docker prot
或docker ps
获取宿主机的映射端口后,就可以在浏览器或使用curl
命令通过127.0.0.1
或localhost
及端口号,连接到容器中的Web服务器了:
$ sudo docker port static_web_server 80/tcp -> 0.0.0.0:32772 $ sudo curl 127.0.0.1:32772 Hello World, 我是个容器
更多关于Dockerfile
介绍:
4. 新镜像推送到Docker Hub
新镜像构建完成后,可以将其推送到Docker Hub,这样就可以在需要的时候轻松获取和使用镜像,其它人也可以使用你构建的镜像。如果不希望镜像被无关人员看到,可以将其推送到私有仓库。
推送镜像使用docker push
命令。
如,将前面创建的static_web_server
推送到Docker Hub:
$ sudo docker push itbilu/static_web_server The push refers to a repository [docker.io/itbilu/static_web_server] 182a2662aedc: Pushed 38a8e3073a9e: Pushed 11b2dd25d9ed: Pushed 5eb5bd4c5014: Pushed d195a7a18c70: Mounted from library/ubuntu af605e724c5a: Mounted from library/ubuntu 59f161c3069d: Mounted from library/ubuntu 4f03495a4d7d: Mounted from library/ubuntu latest: digest: sha256:afbb364ca968e36ffe7ed5784f5412ff2e6cd22f717ef95d35fd9242b95350f9 size: 1988 2b3fd068f542: Pushed 38a8e3073a9e: Layer already exists 11b2dd25d9ed: Layer already exists 5eb5bd4c5014: Layer already exists d195a7a18c70: Layer already exists af605e724c5a: Layer already exists 59f161c3069d: Layer already exists 4f03495a4d7d: Layer already exists v0.0.1: digest: sha256:64c80ac28791d32769122e1a640bedb8be8d4608d23c77a6c4fe6f6be52e5154 size: 1988
如果指定的镜像仓库存在,镜像会被添加到仓库中;如果不存在,则会创建仓库,并将镜像推送到仓库中。在前面我们在前面总共创建了两个static_web_server
镜像版本,因此会有两个镜像被推送到镜像仓库。而static_web_server
仓库,在Docker Hub中并不存在,所以会首先在itbilu
用户下创建这个仓库。
可以在Docker Hub看到上传的镜像,如下图:
注意:推送镜像时,一定要使用用户ID/仓库名
的形式。如果仅使用仓库名
,Docker会认为这是一个root
仓库,会推送失败。
从上面的推送过程可以看出,镜像是一种分层结构的文件系统。镜像推送到镜像仓库时,这些层都会被推送到仓库中。使用docker rmi
删除镜像时,这些层也都会被删除。
5. 自动构建
Docker Hub还提供了自动构建功能,这样我们就可以不必手工建立镜像再推送,只需要将包含Dockerfile
文件的GitHub或Bitbucket地址关联到Docker Hub,Docker Hub就能自动完成构建过程。
使用自动构建前,首先要要将GitHub或Bitbucket帐号连接到Docker Hub。
登录Docker Hub后,点击Create - Create Automated Build
,如下:
创建自动构建还,还需要关联Link Github
或Link Bitbucket
帐号、选项/配置构建项目等。诸君可自行尝试,本篇不再过多介绍。