Docker
使用 docker build
命令来构建镜像,而构建镜像的具体指令则保存在 Dockerfile
中。
当执行
docker build
命令后,无论Dockerfile
文件处于何处,当前目录
都是构建上下文,是build
命令的工作目录,也是COPY
等指令的起始目录
理解镜像生成 ( commit
命令 )
- 从镜像到容器
镜像是多层存储,每一层都是在前一层的基础上进行的修改,而容器同样也是多层存储,它是在镜像的基础上添加一层作为容器运行存储层。容器的所有修改数据都被保存在该层。一旦容器被删除,那也只是它在镜像上添加的那层运行存储层被删除(除非使用数据卷等数据持久化操作)
- 从容器到镜像
当我们在容器中进行了一系列操作,想把它作为镜像保存下来时,可以使用 docker commit
命令,将当前容器的运行存储层保存成一个新的镜像,这个镜像运行后的初始环境与当前容器一致
docker commit
命令
# 从容器创建一个新的镜像
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
-a --author string : 提交的镜像作者
-c --change list : 使用Dockerfile指令来创建镜像
-m --message string : 提交时的说明文字
-p --pause : 在commit时,将容器暂停
CONTAINER: 容器ID
REPOSITORY[:TAG] REPOSITORY: 镜像名称 TAG:镜像标签
docker commit -a "runoob.com" -m "my apache" a404c6c174a2 mymysql:v1
docker images mymysql:v1
# REPOSITORY TAG IMAGE ID CREATED SIZE
# mymysql v1 37af1236adef 15 seconds ago 329 MB
-
尽量不要使用
commit
命令- 容器在运行时可能会生成一些后续镜像用不到的文件,导致镜像极为臃肿
- 黑箱镜像极难维护
- 使用
commit
从容器中生成镜像的操作对其他人而且是不可知的(即黑箱操作),而且就算是开发者也不一定会记住所有操作(无法保证每次操作容器内容是一致的),这会导致镜像的后续修改与维护十分困难 - 如果是对容器生成后的镜像再次运行容器修改,再
commit
生成镜像,则会导致一层又一层的臃肿代码
- 使用
构建镜像命令 build
创建镜像可以使用 docker build
命令根据 Dockerfile
创建一个新的镜像
docker build [OPTIONS] [NAME] PATH | URL | -
NAME: 镜像名称(有时需要加上tag等镜像信息)
PATH | URL | -:Dockerfile文件的地址以及工作目录设置,默认是同一个目录(可以是远端git repo或本地文件)
--build-arg=[] :设置镜像创建时的变量
--cpu-shares :设置 cpu 使用权重
--cpu-period :限制 CPU CFS周期
--cpu-quota :限制 CPU CFS配额
--cpuset-cpus :指定使用的CPU id
--cpuset-mems :指定使用的内存 id
--disable-content-trust :忽略校验,默认开启
-f :指定要使用的Dockerfile路径
--force-rm :设置镜像过程中删除中间容器
--isolation :使用容器隔离技术
--label=[] :设置镜像使用的元数据
-m :设置内存最大值
--memory-swap :设置Swap的最大值为内存+swap,"-1"表示不限swap
--no-cache :创建镜像的过程不使用缓存
--pull :尝试去更新镜像的新版本
--quiet, -q :安静模式,成功后只输出镜像 ID
--rm :设置镜像成功后删除中间容器
--shm-size :设置/dev/shm的大小,默认值是64M
--ulimit :Ulimit配置
--tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签
--network: 默认 default。在构建期间设置RUN指令的网络模式
构建镜像,设置镜像标签等信息
.
当前目录下的Dockerfile文件 此时上下文为当前文件夹
docker build -t runoob/ubuntu:v1 .
根据 GitHub
上文件创建镜像
当使用
git repo
构建镜像时,docker
会自己与git clone
一个文件夹,然后进入其中开始构建
此时的上下文为git clone
后的文件内部,Dockerfile
也在文件内
docker build github.com/creack/docker-firefox
-f Dockerfile
指定文件的位置
最后的
.
此时只作为工作目录定义
,Dockerfile
的路径会被-f
的参数覆盖
# 此时没有指定名称等标签,ps查看的镜像标签全为<none>,应尽量避免出现这种情况
docker build -f /path/to/a/Dockerfile .
从标准输入中读取 Dockerfile 进行构建
docker
会将标准输入的文本作为Dockerfile
,此时没有上下文,也不能执行COPY
等操作上下文文件的指令
docker build - < Dockerfile
cat Dockerfile | docker build -
从标准输入中读取上下文压缩包进行构建
如果上一项中,输入的内容是压缩文件(只支持
gzip、bzip、xz
格式),docker
会将其展开,将文件内容视作上下文,Dockerfile
文件也从内部读取
docker build - < context.tar.gz
.dockerignore
文件
如果上下文中的文件过多,可能会导致构建的镜像非常庞大、臃肿,尤其是其中患有很多我们不需要的文件,那么这就需要用到 .dockerignore
文件了.
.dockerignore
是一个类似 .gitignore
的文件,用于在构建上下文时忽略某些文件,它支持正则和通配符,它的规则定义如下:
# 语法
# 注释
* 匹配0或多个非/的字符
? 匹配1个非/的字符
** 0个或多个目录
! 除...外,需结合上下文语义
# 除README*.md外,所有其他md文件(包括README-secret.md)都被docker忽
*.md
!README*.md
README-secret.md
Dockerfile
语法
镜像构建的实质就是定制每一层所添加的配置、文件. 如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决
这个脚本就是 Dockerfile
,它 是一个包含了一条条构建镜像所需的指令和说明的文本文件
# Dockerfile 常用指令集
FROM ubuntu:18.04
# 构建的镜像都是基于FROM指定的镜像
COPY . /app
# 复制当前工作目录的文件到容器(此时workdir尚未生效,执行后才生效)
# ADD . /app
# ADD 与 COPY功能一致,但ADD会将压缩文件解压
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL com.example.version.is-production=""
# 设置镜像标签
ENV file=docker
# 设置环境变量
ARG app=test
# 声明仅在build生效的环境变量
VOLUME /foo
# 数据卷默认挂载目录(容器数据持久化使用)
RUN echo '构建ubuntu18.04镜像'
# 运行RUN后的命令行命令
CMD echo cmd
# 指定在容器中运行的命令
ENTRYPOINT echo 12121
# run 时默认启动命令
WORKDIR /mydir
# 指定工作目录,在后续指令生效
EXPOSE 5000
# 声明端口
USER patrick
# 指定执行后续命令的用户和用户组
ONBUILD RUN echo '当前基础镜像为ubuntu18.04'
# 使用当前文件构建的镜像为基础镜像时执行
FROM 指令
FROM
用于指定用于构建新镜像的基础镜像
CPOY
指令
源路径
:源文件或者源目录,这里可以是通配符表达式目标路径
:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建
COPY [--chown=<user>:<group>] <源路径1>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
[--chown=<user>:<group>]:可选参数,用户改变复制到容器内文件的拥有者和属组。
# 示例
COPY hom* /mydir/
COPY hom?.txt /mydir/
ADD
指令
ADD
的使用方法与 COPY
一致,功能也类似。但 ADD
指令在源文件为压缩文件(gzip、bzip2、xz
)时,会自动解压缩文件,对需解压缩的文件友好,但无法传输不需压缩的文件,因此在非特殊情况下,推荐使用 CPOY
RUN
指令
执行后面的命令行命令,支持两种写法
在构建镜像
docker build
时执行
shell
命令写法:RUN <命令行命令>
等同于,在终端操作的shell
命令。exec
格式:RUN [可执行文件, arg1, arg2, ...]
CMD
指令
CMD
是在docker
运行镜像(docker run
)时执行,是默认的容器启动命令
, 指令与 RUN
类似,都是运行命令行命令,支持 shell
与 exec
写法 ,但 CMD
多了一种
CMD [arg1, arg2, arg3 ...]
该写法主要是为ENTRYPOINT
指令提供参数
- 在运行镜像
docker run
时执行CMD
会被run
命令的容器启动后执行命令给覆盖CMD
只能执行一次,如果有多个CMD
ENTRYPOINT
指令
ENTRYPOINT
指令也是在镜像运行 docker run
时运行(也是默认容器启动命令的一种),但它不会被 run
指定的命令覆盖,run
的指令也将作为 ENTRYPOINT
命令的参数(CMD
指定的参数会被覆盖)。它也支持 shell
和 exec
写法
run
命令设置--entrypoint
时可以用命令行参数覆盖该指令
FROM nginx
ENTRYPOINT ["nginx", "-c"] # 定参
CMD ["/etc/nginx/nginx.conf"] # CMD传参
- 使用
CMD
参数
docker run nginx:test
容器运行:nginx -c /etc/nginx/nginx.conf
run
命令传参
docker run nginx:test -c /config/new.conf
容器运行:nginx -c /config/new.conf
CMD
与 ENTRYPOINT
指令的优先级
当两个指令同时设置命令行命令时,ENTRYPOINT
如果是 shell
则直接覆盖 CMD
,如果是 exec
则将 CMD
作为参数使用
No ENTRYPOINT | ENTRYPOINT exec_entry p1_entry | ENTRYPOINT [“exec_entry”, “p1_entry”] | |
---|---|---|---|
No CMD | error, not allowed | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry |
CMD [“exec_cmd”, “p1_cmd”] | exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry exec_cmd p1_cmd |
CMD [“p1_cmd”, “p2_cmd”] | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd |
CMD exec_cmd p1_cmd | /bin/sh -c exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |
LABEL
指令
给镜像添加标签
LABEL <key>=<value> <key>=<value> <key>=<value> ...
EXPOSE
指令
仅仅只是声明端口,在运行时使用随机端口映射时,也就是 docker run -P
时,会自动随机映射 EXPOSE
的端口。
EXPOSE <端口 1> [<端口 2>...]
ENV
指令
定义环境变量,该指令定义的变量在后续的指令以及运行的容器中都可以获取
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2> ...
# 如:设置version=0.1.0
ENV version=0.1.0
RUN echo $version
容器中:echo $version
ARG
指令
ARG
指令也是用于设置环境变量,与 ENV
指令作用一致,但 ARG
指令设置的变量只能在 build
时(即 Dockerfile
文件中)使用,作用域不及 ENV
指令
VOLUME
指令
定义默认数据卷挂载目录,在没有设置挂载目录时,会默认挂载到该目录下,避免数据丢失
在启动容器
docker run
的时候,我们可以通过-v
参数修改挂载点
VOLUME ["<路径 1>", "<路径 2>"...]
VOLUME <路径>
USER
指令
用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。
WORKDIR
指令
指定工作目录(执行上下文,build
的执行目录)。用 WORKDIR
指定的工作目录,会在构建镜像的每一层中都存在。(WORKDIR
指定的工作目录,必须是提前创建好的)
docker build
构建镜像过程中的,每一个RUN
命令都是新建的一层。只有通过WORKDIR
创建的目录才会一直存在。
ONBUILD
指令
用于延迟构建命令的执行。简单的说,就是 Dockerfile
里用 ONBUILD
指定的命令,在本次构建镜像的过程中不会执行(假设镜像为 test-build
)。当有新的 Dockerfile
使用了之前构建的镜像 FROM test-build
,这是执行新镜像的 Dockerfile
构建时候,会执行 test-build
的 Dockerfile
里的 ONBUILD
指定的命令
STOPSIGNAL
指令
该 STOPSIGNAL
指令设置将被发送到容器退出的系统调用信号。该信号可以是与内核 syscall
表中的位置匹配的有效无符号数字(例如 9),也可以是格式为 SIGNAME
的信号名称 (例如 SIGKILL
)
STOPSIGNAL signal
HEALTHCHECK
指令
用于指定某个程序或者指令来监控 docker
容器服务的运行状态
HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令
HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK [选项] CMD <命令> : 这边 CMD 后面跟随的命令使用,可以参考 CMD 的用法
--interval=DURATION(默认值:30s)
--timeout=DURATION(默认值:30s)
--start-period=DURATION(默认值:0s)
--retries=N(默认值:3)
SHELL
指令
设置容器运行的默认 shell
SHELL ["executable", "parameters"]
# 使用shell(linux默认)
SHELL ["/bin/sh", "-c"]
# 使用powershell
SHELL ["powershell", "-command"]
# 使用cmd(Windows默认)
SHELL ["cmd", "/S", "/C"]
Dockerfile
注意事项
多阶段构建
在构建镜像时如果全部内容都在一个 Dockerfile 内容,最后生成的镜像可能会非常庞大,但如果分散到多个 Dockerfile 文件,管理与部署又会变得非常麻烦,在 docker 17.05 之后可以使用多阶段构建来处理这种情况
多阶段构建是通过 FROM
指令实现的,您可以在 Dockerfile
中使用多个 FROM
语句。每个 FROM
指令可以使用不同的基础,并且每个指令都开始一个新的构建。您可以选择性地将工件从一个阶段复制到另一个阶段,从而在最终 image
中只留下您想要的内容
docker build --target builder # 可以使用--target 指定构建某一阶段的镜像
# 通过多次使用FROM,将镜像构建分为两个阶段
FROM golang:1.11-alpine AS build
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
RUN dep ensure -vendor-only
COPY . /go/src/project/
RUN go build -o /bin/project
# 通过 COPY --from=xxxx 指令将第一阶段的镜像复制到第二阶段,但只留下需要的部分
FROM scratch
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["--help"]
避免无意义的层数
Dockerfile
的指令每执行一次都会在 docker
上新建一层。所以过多无意义的层,会造成镜像膨胀过大
通过 RUN
FROM centos
RUN yum install wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN tar -xvf redis.tar.gz
# 以上执行会创建 3 层镜像。可简化为以下格式:
# 为了增加可读性,尽量使用 \ && 分隔多行
FROM centos
RUN yum install wget \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& tar -xvf redis.tar.gz
其他优化设置
- 设置
.dockerignore
文件,避免无用的大文件影响 - 尽量使用
COPY
而非ADD
尽管 ADD
和 COPY
在功能上相似,但 COPY
比 ADD
简单。COPY 仅支持将本地文件基本复制到容器中,而 ADD
具有一些并非必要的功能(如解压 tar 文件和支持 url 获取),因此,除非一些需要解压缩文件等特殊情况外,尽量使用 ADD
- 缓存设置
docker
在按行执行 Dockerfile
指令时,如果存在缓存会直接使用缓存的结果,如果不需要缓存可以在 build
时设置 --no-cache=true
,避免缓存过多减慢速度
- 合理调整
COPY
与RUN
的顺序
一但 COPY
和 ADD
执行,那么之前的缓存就会失效,该指令之后构建的镜像也会因此不再使用缓存,因此,类似 COPY WORKDIR ENV LABEL
等命令可以往后移,将缓存变化较少的指令前移
- 添加
HEALTHCHECK
健康检查