镜像制作及原因
镜像制作是因为某种需求,官方的镜像无法满足需求,需要我们通过一定手段来自定义镜像来满足要求。制作镜像往往因为以下原因:
- 编写的代码如何打包到镜像中直接跟随镜像发布
- 第三方制作的内容安全性未知,如含有安全漏洞
- 特定的需求或者功能无法满足,如需要给数据库添加审计功能
- 公司内部要求基于公司内部的系统制作镜像,如公司内部要求使用自己的操作系统作为基础镜像
Docker 镜像制作方式
制作容器镜像,主要有两种方法:
- 制作快照方式获得镜像(偶尔制作的镜像):在基础镜像上(比如 Ubuntu),先登录容器中,然后安装镜像需要的所有软件,最后使用 docker commit 整体制作快照。
- Dockerfile 方式构建镜像(经常更新的镜像):将软件安装的流程写成 Dockerfile,使用 docker build 构建成容器镜像。
快照方式制作镜像:docker commit
功能:从容器创建一个新的镜像。
语法
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
参数
- -a :提交的镜像作者。
- -c :使用 Dockerfile 指令来创建镜像,可以修改启动指令。
- -m :提交时的说明文字。
- -p :在 commit 时将容器暂停。
样例
docker commit mybusybox mybusybox:v1
Dockerfile 制作镜像
Dockerfile 是什么
镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,
这个脚本就是 Dockerfile
。
Dockerfile
是一个文本文件,其内
包含了一条条的指令
(Instruction)
,
每一条指令
构建一层,因此每一条指令的内容,就是描述该层应当如何构建
。
Dockerfile 格式
# Comment
INSTRUCTION arguments
该指令不区分大小写。然而约定它们是大写的,以便更容易地将它们与参数区分开来。Docker 按顺序运行指令
Dockerfile,Docker 将以
开头
的行视为
#
注释,行中其他任何地方的标记
#都被视为参数。
为什么需要 Dockerfile
- 可以按照需求自定义镜像:和 docker commit 一样能够自定义镜像,官方的镜像可以说很少能直接满足我们应用的,都需要我们自己打包自己的代码进去然后做成对应的应用镜像对外使用。
- 很方便的自动化构建,重复执行:通过 dockerfile 可以自动化的完成镜像构建,而不是像 docker commit 一样,手动一个命令一个命令执行,而且可以重复执行,docker commit 的话很容易忘记执行了哪个命令,哪个命令没有执行。
- 维护修改方便,不再是黑箱操作:使用 docker commit 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为黑箱镜像,dockerfile 很容易二次开发。
- 更加标准化,体积可以做的更小:docker 容器启动后,系统运行会生成很多运行时的文件,如果使用 commit 会导致这些文件也存储到镜像里面,而且 commit 的时候安装了很多的依赖文件,没有有效的清理机制的话会导致镜像非常的臃肿。使用 Dockerfile 则会更加标准化,而且提供多级构建,将编译和构建分开,不会有运行时的多余文件,更加的标准化。
制作命令 docker build
功能:
docker build
命令用于使用
Dockerfile
创建镜像。
语法
docker build [OPTIONS] PATH | URL | -
关键参数
- --build-arg=[] :设置镜像创建时的变量;
- -f :指定要使用的 Dockerfile 路径;
- --label=[] :设置镜像使用的元数据;
- --no-cache :创建镜像的过程不使用缓存;
- --pull :尝试去更新镜像的新版本;
- --quiet, -q :安静模式,成功后只输出镜像 ID;
- --tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式,可以在一次构建中为一个镜像设置多个标签。
- --network: 默认 default。在构建期间设置 RUN 指令的网络模式。
样例
docker build -t web:v1 .
Dockerfile 指令
指令清单
指令 |
功能
|
FROM |
构建镜像基于哪个镜像,也就是基础镜像
|
LABEL |
为镜像添加元数据
|
COPY
|
拷贝文件或目录到镜像中,跟
ADD
类似,但不具备自动下载或解压的功能
|
ADD |
拷贝文件或目录到镜像中,如果是
URL
或压缩包便会自动下载或自动解压
|
WORKDIR
| 指定工作目录 |
RUN
|
指定
docker build
过程中运行的程序
|
VOLUME
| 指定容器挂载点 |
EXPOSE
|
声明容器的服务端口(仅仅是声明)
|
ENV
|
设置环境变量
|
CMD | 运行容器时执行的命令 |
ENTRYPOINT |
运行容器时程序入口
|
ARG |
指定构建时的参数
|
SHELL
|
指定采用哪个
shell
|
USER
| 指定当前用户 |
HEALTHCHECK |
健康检测指令
|
ONBUILD
| 在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。 |
FROM
功能:FROM 指令用于
为镜像文件构建过程指定基础镜像
,后续的指令运行于此基础镜像所提供的运行环境。
注意事项
- FROM 指令必须是 Dockerfile 中非注释行或者 ARG 之后的第一个指令。
- 实践中,基准镜像可以是任何可用镜像文件,默认情况下,docker build 会在 docker 主机上查找指定的镜像文件,在其不存在时,则会自动从 Docker 的公共库 pull 镜像下来。如果找不到指定的镜像文件,docker build 会返回一个错误信息。
- FROM 可以在一个 Dockerfile 中出现多次,如果有需求在一个 Dockerfile 中创建多个镜像,或将一个构建阶段作为另一个的依赖。
- 如果 FROM 语句没有指定镜像标签,则默认使用 latest 标签。
语法
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
参数
- <platform>:构建的 cpu 架构,如 linux/amd64, linux/arm64, windows/amd64;
- <image>:指定作为 base image 的名称;
- <tag>:base image 的标签,省略时默认 latest;
- <digest>:是镜像的哈希码;
- AS <name>: 指定构建步骤的名称,配合 COPY --from=<name>可以完成多级构建
样例
建一个 Dokcerfile 文件,并向文件中填下以下语句
From ubuntu:24.04 as buildbase
后使用以下语句创建镜像文件
docker build -t web:0.1 .
然后可以尝试启动一个容器运行该镜像文件
docker run -it --name web1 --rm web:0.1 cat /etc/*release*
LABEL
功能:为镜像添加元数据,元数据是 kv
对形式,之后就可以在镜像文件的信息中能查看到添加的LABEL信息。
语法
LABEL <key>=<value> <key>=<value> <key>=<value> ...
样例
FROM ubuntu:24.04 as buildbase
LABEL author="lbk" app="nginx"
COPY
功能:用于从 docker
主机复制新文件或者目录至创建的新镜像指定路径中 。
语法
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
参数
- <src>:要复制的源文件或目录,支持使用通配符;
- <dest>:目标路径,即正在创建的 image 的文件系统路径;建议<dest>使用绝对路径,否则,COPY 指定以 WORKDIR 为当前路径,在路径中有空白字符时,通常使用第 2 种格式;
- --chown:修改用户和组
-
--from <name>可选项:可以从之前构建的步骤中拷贝内容,结合 FROM .. AS <name>往往用作多级构建,后续我们有实战课专门完成多级构建
注意事项
- 如果<src>是目录,则其内部文件或子目录会被递归复制,但<src>目录自身不会被复制;
- 如果指定了多个<src>,或在<src>中使用了通配符,则<dest>必须是一个目录,且必须以 / 结尾;
- 如果<dest>事先不存在,它将会被自动创建,这包括父目录路径。
样例
在本目录下创建一个 index.html 文件,内容如下
<html>
<h1>Hello,My Home Page!!</h1>
</html>
然后在 Dockerfile 中编辑如下内容,从主机复制该 index.html 文件到镜像中
FROM ubuntu:24.04 as buildbase
LABEL author="lbk" app="nginx"
COPY ./index.html /data/web
ENV
功能:用于为镜像定义所需的环境变量
,并可被
Dockerfile
文件中
位于其后的
其它指令(
如
ENV
、
ADD
、
COPY
等
)
所调用,调用格式为$variable_name
或
${variable_name}
语法
ENV <key>=<value> ...
样例
目录后面可能复用,我们将目录的位置提取为变量,通过
ENV
来设置,我们再次编辑 Dockerfile
FROM ubuntu:24.04 as buildbase
LABEL author="lbk" app="nginx"
ENV WEB_ROOT=/data/web
COPY ./index.html ${WEB_ROOT}
WORKDIR
功能:为 Dockerfile
中所有的
RUN
、
CMD
、
ENTRYPOINT
、
COPY
和
AD
D 指定设定工作目录
语法
WORKDIR /path/to/workdir
注意事项
- 默认的工作目录是/
- 如果提供了相对路径,它将相对于前一条 WORKDIR 指令的路径。
- WORKDIR 指令可以解析先前使用设置的环境变量 ENV
ADD
功能:ADD 指令类似于
COPY
指令,
ADD
支持使用
TAR
文件和
URL
路径
,
会自动完成解压和下载。
语法
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
参数
- <src>:要复制的源文件或目录,支持使用通配符;
- <dest>:目标路径,即正在创建的 image 的文件系统路径;建议<dest>使用绝对路径,否则,ADD 指定以 WORKDIR 为其实路径;在路径中有空白字符时,通常使用第 2 种格式;
- --chown:修改用户和组;
实战
我们登录
nginx
官网,找到最新版本的 nginx 的下载地址 https://nginx.org/en/download.html,可以看到最新版本为 1.28.0,复制该链接
https://nginx.org/download/nginx-1.28.0.tar.gz.asc

有了链接地址后,我们发现这是一个
URL
地址,我们通过
ADD
命令下载,因为 nginx 的未来版本还会变,所以我们可以提取
ningx
的版本为环境变量,我们再次编辑我们的 Dockerfile
FROM ubuntu:24.04 as buildbase
LABEL author="lbk" app="nginx"
ENV WEB_ROOT=/data/web
ENV NGINX_VERSION="nginx-1.28.0"
COPY ./index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
RUN
功能:用于指定 docker build
过程中运行的程序,其可以是任何命令
语法
#shell form
RUN <command>
#exec form
RUN ["executable", "param1", "param2"]
参数
- 第一种格式中,<command>通常是一个 shell 命令
- 第二种语法格式中的参数是一个 JSON 格式的数组,其中<executable>为要运行的命令,后面的 <paramN>为传递给命令的选项或参数
样例
接上,因为我们
nginx
是源码所以我们需要先解压
src
文件,通过
RUN
命令可以完成 nginx
的解压。
FROM ubuntu:24.04 as buildbase
LABEL author="lbk" app="nginx"
ENV WEB_ROOT=/data/web
ENV NGINX_VERSION="nginx-1.28.0"
COPY ./index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
#解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
因为是源码安装所以我们要编译安装
nginx
,需要下载编译工具,已经依赖库信息,并通过 make
来完成编译,我们再次修改
dockerfile。
FROM ubuntu:24.04 as buildbase
LABEL author="lbk" app="nginx"
ENV WEB_ROOT=/data/web
ENV NGINX_VERSION="nginx-1.28.0"
COPY ./index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
#解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
#1.安装 build-essential 构建工具
#2.安装依赖包 libpcre3 libpcre3-dev zlib1g-dev 依赖库
#3.进入 nginx 目录
#4.执行编译和构建
RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
CMD
功能:类似于 RUN
指令
,
CMD
指令也可用于运行任何命令或应用程序,不过,二者的运行时间点不同,
RUN
指令运行于映像文件
构建
过程
中,而
CMD
指令运行于基于
Dockerfile
构建出的新映像文件
启动
一个容器时。
CMD 指令的首要
目的在于为启动的容器指定默认要运行的程序
,且其运行结束后,容器也将终止;不过,CMD
指定的命令其可以被
docker run 的命令行选项所覆盖,在 Dockerfile 中可以存在
多个
CMD 指令,但仅最后一个会生效
语法
CMD ["executable","param1","param2"] (exec form, this is the preferred form)
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
CMD command param1 param2 (shell form)
注意事项
- 第二种则用于为 ENTRYPOINT 指令提供默认参数
- json 数组中,要使用双引号,单引号会出错
样例
接上,此时因为
docker
是需要一个长时间后台运行的,
我们让
nginx
进入前台运行,这就需要我们的 CMD
或者
EntryPoint
来完成,我们先用
CMD
来配置,修改 Dockerfile 如下
FROM ubuntu:24.04 as buildbase
LABEL author="lbk" app="nginx"
ENV WEB_ROOT=/data/web
ENV NGINX_VERSION="nginx-1.28.0"
COPY ./index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
#解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
#1.安装 build-essential 构建工具
#2.安装依赖包 libpcre3 libpcre3-dev zlib1g-dev 依赖库
#3.进入 nginx 目录
#4.执行编译和构建
RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
CMD ["/usr/local/nginx/sbin/nginx","-g daemon off;"]
EXPOSE
功能:用于为容器
声明
打开指定要监听的端口以实现与外部通信,该 EXPOSE
指令实际上并不发布端口。它充当构建镜像的人和运行容器的人之间的一种文档,关于要发布哪些端口。要在运行容器时实际发布端口,使用-p
参数发布和映射一个或多个端口,或者使用-P
flag
发布所有暴露的端口并将它们映射宿主机端口。
语法
EXPOSE <port> [<port>/<protocol>...]
参数
- <protocol>:tcp/udp 协议
- <port>:端口
样例
我们通过
EXPOSE
暴露端口,注意暴露的端口仅仅是声明,调整
Dockerfile
如下
FROM ubuntu:24.04 as buildbase
LABEL author="lbk" app="nginx"
ENV WEB_ROOT=/data/web
ENV NGINX_VERSION="nginx-1.28.0"
COPY ./index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
#解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
#1.安装 build-essential 构建工具
#2.安装依赖包 libpcre3 libpcre3-dev zlib1g-dev 依赖库
#3.进入 nginx 目录
#4.执行编译和构建
RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
EXPOSE 80/tcp
CMD ["/usr/local/nginx/sbin/nginx","-g daemon off;"]
ENTRYPOINT
功能:用于指定容器的启动入口,功能与CMD类似
语法
#exec from
ENTRYPOINT ["executable", "param1", "param2"]
# shell form
ENTRYPOINT command param1 param2
json 数组中,要使用双引号
,单引号会出错。
样例
我们将 CMD
调整为
EntryPoint
重新测试下,此时
Dockerfile
如下
FROM ubuntu:24.04 as buildbase
LABEL author="lbk" app="nginx"
ENV WEB_ROOT=/data/web
ENV NGINX_VERSION="nginx-1.28.0"
COPY ./index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
#解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
#1.安装 build-essential 构建工具
#2.安装依赖包 libpcre3 libpcre3-dev zlib1g-dev 依赖库
#3.进入 nginx 目录
#4.执行编译和构建
RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
EXPOSE 80/tcp
#CMD ["/usr/local/nginx/sbin/nginx","-g daemon off;"]
ENTRYPOINT ["/usr/local/nginx/sbin/nginx","-g daemon off;"]
ARG
功能:ARG 指令类似
ENV
,定义了一个变量,区别于 ENV的是用户可以在构建时 docker build --build-arg <varname> = <value> 对变量进行修改,ENV 不可以,如果用户指定了未在 Dockerfile
中定义的构建参数,那么构建输出警告。
语法
ARG <name>[=<default value>]
注意事项
- Dockerfile 可以包含一个或多个 ARG 指令。
- ARG 支持指定默认值。
- 使用范围:定义之后才能使用,定义之前为空。
- ENV 和 ARG 同时存在,ENV 会覆盖 ARG。
样例
突然有一天要把基础镜像升级到
22.10
吧,这个时候我们的
ARG
就排上用场了,我们定义一个 ARG 变量指定操作系统版本,修改后的 Dockerfile
如下
ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION} as buildbase
此时可以这样构建镜像文件
docker build --build-arg UBUNTU_VERSION=22.10 -t web:v0.2 .
VOLUME
功能:用于在 image
中创建一个挂载点目录,通过 VOLUME
指令创建的挂载点,无法指定主机上对应的目录,是自动生成的。
语法
VOLUME <mountpoint>
VOLUME ["<mountpoint>"]
参数:mountpoint 挂载点目录
注意事项
- 如果挂载点目录路径下此前有文件存在,docker run 命令会在卷挂载完成后将此前的所有文件复制到新挂载的卷中。
- 其实 VOLUME 指令只是起到了声明了容器中的目录作为匿名卷,但是并没有将匿名卷绑定到宿主机指定目录的功能。
- volume 只是指定了一个目录,用以在用户忘记启动时指定-v 参数也可以保证容器的正常运行。比如 mysql,你不能说用户启动时没有指定-v,然后删了容器,就把 mysql 的数据文件都删了,那样生产上是会出大事故的,所以 mysql 的 dockerfile 里面就需要配置 volume,这样即使用户没有指定-v,容器被删后也不会导致数据文件都不在了。还是可以恢复的。
- volume 与-v 指令一样,容器被删除以后映射在主机上的文件不会被删除。如果-v 和 volume 指定了同一个位置,会以-v 设定的目录为准,其实 volume 指令的设定的目的就是为了避免用户忘记指定-v 的时候导致的数据丢失,那么如果用户指定了-v,自然而然就不需要 volume 指定的位置了。
样例
ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION} as buildbase
LABEL author="lbk" app="nginx"
ENV WEB_ROOT=/data/web
ENV NGINX_VERSION="nginx-1.28.0"
COPY ./index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
#解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
#1.安装 build-essential 构建工具
#2.安装依赖包 libpcre3 libpcre3-dev zlib1g-dev 依赖库
#3.进入 nginx 目录
#4.执行编译和构建
RUN apt-get update -y && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
EXPOSE 80/tcp
#CMD ["/usr/local/nginx/sbin/nginx","-g daemon off;"]
ENTRYPOINT ["/usr/local/nginx/sbin/nginx","-g daemon off;"]
#创建挂载目录
RUN mkdir -p /data
VOLUME ["/data"]
Dockerfile 编写优秀实践
- 善用.dockerignore 文件:使用它可以标记在执行 docker build 时忽略的路径和文件,避免发送不必要的数据内容,从而加快整个镜像创建过程。
- 镜像的多阶段构建:通过多步骤创建,可以将编译和运行等过程分开,保证最终生成的镜像只包括运行应用所需要的最小化环境。当然,用户也可以通过分别构造编译镜像和运行镜像来达到类似的结果,但这种方式需要维护多个 Dockerfile。
- 合理使用缓存: 如合理使用 cache,减少内容目录下的文件,内容不变的指令尽量放在前面,这样可以尽量复用。
- 基础镜像尽量使用官方镜像,并选择体积较小镜像:容器的核心是应用,大的平台微服务可能几十上百个。选择过大的父镜像(如 Ubuntu 系统镜像)会造成最终生成应用镜像的臃肿,推荐选用瘦身过的应用镜像(如 node:slim),或者较为小巧的系统镜像(如 alpine、busybox 或debian)。
- 减少镜像层数:如果希望所生成镜像的层数尽量少,则要尽量合并 RUN、ADD 和 COPY 指令写到一行。
- 精简镜像用途:尽量让每个镜像的用途都比较集中单一,避免构造大而复杂、多功能的镜像。
- 减少外部源的干扰:如果确实要从外部引入数据,需要指定持久的地址,并带版本信息等,让他人可以复用而不出错。
- 减少不必要的包安装:只安装需要的包,不要安装无用的包,减少镜像体积。