Docker 镜像制作和管理
Docker 镜像说明
Docker 镜像中有没有内核
从镜像大小上面来说,一个比较小的镜像只有1MB多点或几MB,而内核文件需要几十MB, 因此镜像里面是没有内核的,镜像在被启动为容器后将直接使用宿主机的内核,而镜像本身则只提供相应的rootfs,即系统正常运行所必须的用户空间的文件系统,比如: /dev/,/proc,/bin,/etc等目录,容器当中/boot目录是空的,而/boot当中保存的就是与内核相关的文件和目录。
为什么没有内核
由于容器启动和运行过程中是直接使用了宿主机的内核,不会直接调用物理硬件,所以也不会涉及到硬件驱动,因此也无需容器内拥有自已的内核和驱动。而如果使用虚拟机技术,对应每个虚拟机都有自已独立的内核
容器中的程序后台运行会导致此容器启动后立即退出
Docker容器如果希望启动后能持续运行,就必须有一个能前台持续运行的进程,如果在容器中启动传统的服务,如:httpd,php-fpm等均为后台进程模式运行,就导致 docker 在前台没有运行的应用,这样的容器启动后会立即退出。所以一般会将服务程序以前台方式运行,对于有一些可能不知道怎么实现前台运行的程序,只需要在你启动的该程序之后添加类似于 tail ,top 这种可以前台运行的程序即可. 比较常用的方法,如 tail -f /etc/hosts 。
范例:
#httpd
ENTRYPOINT [ "/usr/sbin/apache2" ]
CMD ["-D", "FOREGROUND"]
#nginx
ENTRYPOINT [ "/usr/sbin/nginx", "-g", "daemon off;" ]
#用脚本运行容器
cat run_haproxy.sh
#!/bin/bash
haproxy -f /etc/haproxy/haproxy.cfg
tail -f /etc/hosts
tail -n1 Dockerfile
CMD ["run_haproxy.sh"]
docker 镜像生命周期
制作镜像方式
Docker 镜像制作类似于虚拟机的镜像(模版)制作,即按照公司的实际业务需求将需要安装的软件、相关配置等基础环境配置完成,然后将其做成镜像,最后再批量从镜像批量生成容器实例,这样可以极大的简化相同环境的部署工作.
Docker的镜像制作分为手动制作(基于容器)和自动制作(基于DockerFile),企业通常都是基于Dockerfile制作镜像
docker commit #通过修改现有容器,将之手动构建为镜像
docker build #通过Dockerfile文件,批量构建为镜像
将现有容器通过 docker commit 手动构建镜像
基于容器手动制作镜像步骤
docker commit 格式
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
#选项
-a, --author string Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
-c, --change list Apply Dockerfile instruction to the created image
-m, --message string Commit message
-p, --pause Pause container during commit (default true)
#说明:
制作镜像和CONTAINER状态无关,停止状态也可以制作镜像
如果没有指定[REPOSITORY[:TAG]],REPOSITORY和TAG都为<none>
提交的时候标记TAG号: 生产当中常用,后期可以根据TAG标记创建不同版本的镜像以及创建不同版本的容器
基于容器手动制作镜像步骤具体如下:
- 下载一个系统的官方基础镜像,如: CentOS 或 Ubuntu
- 基于基础镜像启动一个容器,并进入到容器
- 在容器里面做配置操作
- 安装基础命令
- 配置运行环境
- 安装服务和配置服务
- 放业务程序代码
- 提交为一个新镜像
docker commit - 基于自己的的镜像创建容器并测试访问
实战案例: 基于 busybox 制作 httpd 镜像
[root@ubuntu1804 ~]#docker run -it --name b1 busybox
/ # ls
bin dev etc home proc root sys tmp usr var
/ # mkdir /data/html -p
/ # echo httpd website in busybox > /data/html/index.html
/ # httpd --help
BusyBox v1.32.0 (2020-06-27 00:20:57 UTC) multi-call binary
Usage: httpd [-ifv[v]] [-c CONFFILE] [-p [IP:]PORT] [-u USER[:GRP]] [-r REALM] [-h HOME] or httpd -d/-e/-m STRING
Listen for incoming HTTP requests
-i Inetd mode
-f Don't daemonize
-v[v] Verbose 显示访问日志
-p [IP:]PORT Bind to IP:PORT (default *:80)
-u USER[:GRP] Set uid/gid after binding to port
-r REALM Authentication Realm for Basic Authentication
-h HOME Home directory (default .)
-c FILE Configuration file (default {/etc,HOME}/httpd.conf)
-m STRING MD5 crypt STRING
-e STRING HTML encode STRING
-d STRING URL decode STRING
/ # exit
#格式1
[root@ubuntu1804 ~]#docker commit -a "ayaka<root@ayaka.com>" -c 'CMD /bin/httpd -fv -h /data/html' -c "EXPOSE 80" b1 httpd-busybox:v1.0
#格式2
[root@ubuntu1804 ~]#docker commit -a "ayaka<root@ayaka.com>" -c 'CMD ["/bin/httpd", "-f", "-v","-h", "/data/html"]' -c "EXPOSE 80" b1 httpd-busybox:v1.0
[root@ubuntu1804 ~]#docker images
REPOSITORY TAG IMAGE ID CREATED
SIZE
httpd-busybox v1.0 e7883146c119 6 minutes ago
1.22MB
[root@ubuntu1804 ~]#docker run -d -P --name httpd01 httpd-busybox:v1.0
ce95174c6385392b9699d12d1a86d7f81bc4dde1400a071ce17d9c78d905cb12
[root@ubuntu1804 ~]#docker port httpd01
80/tcp -> 0.0.0.0:32783
[root@ubuntu1804 ~]#docker inspect -f "{{.NetworkSettings.Networks.bridge.IPAddress}}" httpd01
172.17.0.2
#对应格式1
[root@ubuntu1804 ~]#docker inspect -f "{{.Config.Cmd}}" httpd01
[/bin/sh -c /bin/httpd -f -h /data/html]
#对应格式2
[root@ubuntu1804 ~]#docker inspect -f "{{.Config.Cmd}}" httpd01
[/bin/httpd -f -h /data/html]
[root@ubuntu1804 ~]#docker exec -it httpd01 sh
/ # pstree -p
httpd(1)
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 /bin/httpd -fv -h /data/html
7 root 0:00 sh
13 root 0:00 ps aux
/ #
[root@ubuntu1804 ~]#curl 172.17.0.2
httpd website in busybox
[root@ubuntu1804 ~]#curl 127.0.0.1:32783
httpd website in busybox
#再次制作镜像v2.0版
[root@ubuntu1804 ~]#docker commit -a "ayaka<root@ayaka.com>" b1 httpd-busybox:v2.0
[root@ubuntu1804 ~]#docker run -d --name web2 -p 81:80 httpd-busybox:v2.0 /bin/httpd -fv -h /data/html
c47bce0de75dcdf88266467accbe0a119190999c23cec32d6af8b8500aed96d4
利用 DockerFile 文件执行 docker build 自动构建镜
Dockfile 使用详解
Dockerfile 介绍
DockerFile 是一种被Docker程序解释执行的脚本,由一条条的命令组成的,每条命令对应linux下面的一条命令,Docker程序将这些DockerFile指令再翻译成真正的linux命令,其有自己的书写方式和支持的命令,Docker程序读取DockerFile并根据指令生成Docker镜像,相比手动制作镜像的方式,DockerFile更能直观的展示镜像是怎么产生的,有了DockerFile,当后期有额外的需求时,只要在之前的DockerFile添加或者修改响应的命令即可重新生成新的Docker镜像,避免了重复手动制作镜像的麻烦,类似与shell脚本一样,可以方便高效的制作镜像
Docker守护程序 Dockerfile 逐一运行指令,如有必要,将每个指令的结果提交到新镜像,然后最终输出新镜像的ID。Docker守护程序将自动清理之前发送的上下文
请注意,每条指令都是独立运行的,并会导致创建新镜像,比如 RUN cd /tmp 对下一条指令不会有任何影响。
Dockerfile 镜像制作和使用流程

Dockerfile文件的制作镜像的分层结构

Docker将尽可能重用中间镜像层(缓存),以显著加速 docker build命令的执行过程,这由 Using cache 控制台输出中的消息指示
范例:
#按照业务类型或系统类型等方式划分创建目录环境,方便后期镜像比较多的时候进行分类
[root@ubuntu1804 ~]#mkdir /data/dockerfile/web/{nginx,apache,tomcat,jdk},system/{centos,ubuntu,alpine,debian}} -p
[root@ubuntu1804 ~]#tree /data/dockerfile/
/data/dockerfile/
├── system
│ ├── alpine
│ ├── centos
│ ├── debian
│ └── ubuntu
└── web
├── apache
├── jdk
├── nginx
└── tomcat
10 directories, 0 files
[root@ubuntu1804 ~]#
Dockerfile 文件格式
Dockerfile 是一个有特定语法格式的文本文件
dockerfile 官方说明: https://docs.docker.com/engine/reference/builder/
帮助: man 5 dockerfile
Dockerfile 文件说明
- 每一行以Dockerfile的指令开头,指令不区分大小写,但是惯例使用大写
- 使用 # 开始作为注释
- 每一行只支持一条指令,每条指令可以携带多个参数
- 指令按文件的顺序从上至下进行执行
- 每个指令的执行会生成一个新的镜像层,为了减少分层和镜像大小,尽可能将多条指令合并成一条指令
- 制作镜像一般可能需要反复多次,每次执行dockfile都按顺序执行,从头开始,已经执行过的指令已经缓存,不需要再执行,如果后续有一行新的指令没执行过,其往后的指令将会重新执行,所以为加速镜像制作,将最常变化的内容放下dockerfile的文件的后面
Dockerfile 相关指令
dockerfile 文件中的常见指令:
ADD
COPY
ENV
EXPOSE
FROM
LABEL
STOPSIGNAL
USER
VOLUME
WORKDIR
| FROM | 它妈妈是谁(基础镜像) |
|---|---|
| MAINTAINER | 告诉别人,你创造了它(维护者信息) |
| RUN | 你想让它干啥(命令前面加上RUN) |
| ADD | 往它肚子里放点文件(COPY文件,会自动解压) |
| WORKDIR | 我是cd 今天刚化了妆(当前工作目录) |
| VOLUME | 给我一个放行李的地方(目录挂载) |
| EXPOSE | 我要打开的门是啥(端口) |
| CMD | 奔跑吧兄弟 (进程要一直运行下去) |
FROM: 指定基础镜像
定制镜像,需要先有一个基础镜像,在这个基础镜像上进行定制。
FROM 就是指定基础镜像,此指令通常必需放在Dockerfile文件第一个非注释行。后续的指令都是运行于此基准镜像所提供的运行环境
基础镜像可以是任何可用镜像文件,默认情况下,docker build会在docker主机上查找指定的镜像文件,在其不存在时,则会从Docker Hub Registry上拉取所需的镜像文件.如果找不到指定的镜像文件docker build会返回一个错误信息
如何选择合适的镜像呢?
对于不同的软件官方都提供了相关的docker镜像,比如: nginx、redis、mysql、httpd、tomcat等服务类的镜像,也有操作系统类,如: centos、ubuntu、debian等。建议使用官方镜像,比较安全。
格式:
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
#说明:
--platform 指定镜像的平台,比如: linux/amd64, linux/arm64, or windows/amd64
tag 和 digest是可选项,如果不指定,默认为latest
说明: 关于scratch 镜像
FROM scratch
参考链接:
https://hub.docker.com/_/scratch?tab=description
https://docs.docker.com/develop/develop-images/baseimages/
该镜像是一个空的镜像,可以用于构建busybox等超小镜像,可以说是真正的从零开始构建属于自己的镜像
该镜像在构建基础镜像(例如debian和busybox)或超最小镜像(仅包含一个二进制文件及其所需内容,例如:hello-world)的上下文中最有用
范例
FROM scratch #所有镜像的起源镜像,相当于JAVA中Object类
FROM ubuntu
FROM ubuntu:bionic
FROM debian:buster-slim
LABEL: 指定镜像元数据
可以指定镜像元数据,如: 镜像作者等
LABEL <key>=<value> <key>=<value> <key>=<value> ...
范例:
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
一个镜像可以有多个label ,还可以写在一行中,即多标签写法,可以减少镜像的的大小
范例: 多标签写法
#一行格式
LABEL multi.label1="value1" multi.label2="value2" other="value3"
#多行格式
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
docker inspect 命令可以查看LABEL
范例
"Labels": {
"com.example.vendor": "ACME Incorporated"
"com.example.label-with-value": "foo",
"version": "1.0",
"description": "This text illustrates that label-values can span multiple
lines.",
"multi.label1": "value1",
"multi.label2": "value2",
"other": "value3"
},
MAINTAINER: 指定维护者信息
此指令已过时,用LABEL代替
MAINTAINER <name>
范例:
MAINTAINER ayaka <root@ayaka.cn>
#用LABEL代替
LABEL maintainer="ayaka <root@ayaka.cn>"
RUN: 执行 shell 命令
RUN 指令用来在构建镜像阶段需要执行 FROM 指定镜像所支持的Shell命令。
通常各种基础镜像一般都支持丰富的shell命令
注意: RUN 可以写多个,每一个RUN指令都会建立一个镜像层,所以尽可能合并成一条指令,比如将多个shell命令通过 && 连接一起成为在一条指令
每个RUN都是独立运行的,和前一个RUN无关
#shell 格式: 相当于 /bin/sh -c <命令> 此种形式支持环境变量
RUN <命令>
#exec 格式: 此种形式不支持环境变量,注意:是双引号,不能是单引号
RUN ["executable","param1","param2"...]
#exec格式可以指定其它shell
RUN ["/bin/bash","-c","echo hello ayaka"]
说明:
shell格式中,<command>通常是一个shell命令,且以"/bin/sh -c”来运行它,这意味着此进程在容器中的PID不为1,不能接收Unix信号,因此,当使用docker stop <container>命令停止容器时,此进程接收不到SIGTERM信号
exec格式中的参数是一个JSON格式的数组,其中<executable>为要运行的命令,后面的<paramN>为传递给命令的选项或参数;然而,此种格式指定的命令不会以"/bin/sh -c"来发起,因此常见的shell操作如变量替换以及通配符(?,*等)替换将不会进行;不过,如果要运行的命令依赖于此shell特性的话,可以将其替换为类似下面的格式。
RUN ["/bin/bash", "-c", "<executable>", "<param1>"]
| 特性 | Shell 格式 (字符串) | Exec 格式 (JSON 数组) |
|---|---|---|
| 底层运行 | /bin/sh -c "命令" | 直接运行二进制文件 |
| 容器内 PID 1 | 是 Shell (sh/bash) | 是你的程序 (推荐) |
| 能否接收信号 | 不能 (容易导致强制退出) | 能 (优雅退出) |
| 支持变量替换 | 支持 (如 $HOME) | 不支持 (除非手动调用 sh) |
| 适用场景 | 简单的临时命令 | 生产环境启动指令 (CMD/ENTRYPOINT) |
范例:
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
RUN ["/bin/bash", "-c", "echo hello world"]
RUN yum -y install epel-release \
&& yum -y install nginx \
&& rm -rf /usr/share/nginx/html/* \
&& echo "<h1> docker test nginx </h1>" > /usr/share/nginx/html/index.html
范例: 多个 前后RUN 命令独立无关和shell命令不同
#world.txt并不存放在/app内
RUN cd /app
RUN echo "hello" > world.txt
ENV: 设置环境变量
ENV 可以定义环境变量和值,会被后续指令(如:ENV,ADD,COPY,RUN等)通过KEY或{KEY}进行引用,并在容器运行时保持
#变量赋值格式1
ENV <key> <value> #此格式只能对一个key赋值,<key>之后的所有内容均会被视作其<value>的组成部分
#变量赋值格式2
ENV <key1>=<value1> <key2>=<value2> \ #此格式可以支持多个key赋值,定义多个变量建议使用,减少镜像层
<key3>=<value3> ...
#如果<value>中包含空格,可以以反斜线\进行转义,也可通过对<value>加引号进行标识;另外,反斜线也可用于续行
#只使用一次变量
RUN <key>=<value> <command>
#引用变量
RUN $key .....
#变量支持高级赋值格式
${key:-word}
${key:+word}
如果运行容器时如果需要修改变量,可以执行下面通过基于 exec 机制实现
注意: 下面方式只影响容器运行时环境,而不影响构建镜像的过程,即只能覆盖docker run时的环境变量,而不会影响docker build时环境变量的值
docker run -e|--env <key>=<value>
#说明
-e, --env list #Set environment variables
--env-file filename #Read in a file of environment variables
示例: 两种格式功能相同
#格式1
ENV myName="John Doe" myDog=Rex\ The\ Dog \
myCat=fluffy
#格式2
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy
范例:
ENV VERSION=1.0 DEBUG=on NAME="Happy Feet"
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
范例:
[root@ubuntu1804 dockerfile]#cat Dockerfile
FROM busybox
LABEL maintainer="ayaka <root@ayaka.com>"
ENV NAME Ayaka Miyazaki kuyashii
RUN touch $NAME.txt
[root@ubuntu1804 dockerfile]#cat build.sh
#!/bin/bash
#
TAG=$1
docker build -t test:$TAG . #点号去当前目录下找 Dockerfile,并将这个目录下的所有文件都发送给 Docker 守护进程,以便在构建时使用(例如执行 COPY 或 ADD 指令)
[root@ubuntu1804 dockerfile]#./build.sh v5.0
[root@ubuntu1804 dockerfile]#docker run --rm --name c1 test:v5.0 env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=d4e1f89aca71
NAME=Ayaka Miyazaki kuyashii
HOME=/root
[root@ubuntu1804 dockerfile]#docker run --rm -e NAME=aika --name c1 test:v5.0
env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=b23500aa100d
NAME=aika
HOME=/root
[root@ubuntu1804 dockerfile]#docker run --rm -e NAME=aika --name c1 test:v5.0
ls -l
total 36
drwxr-xr-x 2 root root 12288 Jun 27 00:21 ayaka
-rw-r--r-- 1 root root 0 Jul 24 10:05 bin
drwxr-xr-x 5 root root 340 Jul 24 10:05 dev
drwxr-xr-x 1 root root 4096 Jul 24 10:05 etc
drwxr-xr-x 2 nobody nogroup 4096 Jun 27 00:21 home
-rw-r--r-- 1 root root 0 Jul 24 10:05 Kuyashii
dr-xr-xr-x 222 root root 0 Jul 24 10:05 proc
drwx------ 2 root root 4096 Jun 27 00:21 root
dr-xr-xr-x 13 root root 0 Jul 24 10:05 sys
drwxrwxrwt 2 root root 4096 Jun 27 00:21 tmp
drwxr-xr-x 3 root root 4096 Jun 27 00:21 usr
drwxr-xr-x 4 root root 4096 Jun 27 00:21 var
-rw-r--r-- 1 root root 0 Jul 24 10:05 Miyazaki
[root@ubuntu1804 dockerfile]#cat env.txt
NAME=ayaka
TITLE=cto
[root@ubuntu1804 dockerfile]#docker run --rm --env-file env.txt --name c1 test:v5.0 env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=88ddf5b73a6b
NAME=ayaka
TITLE=cto
HOME=/root
COPY: 复制文本
复制本地宿主机的文件到容器中的 。
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] #路径中有空白字符时,建议使用此格式
说明
- 可以是多个,可以使用通配符,通配符规则满足Go的filepath.Match 规则
filepath.Match 参考链接: https://golang.org/pkg/path/filepath/#Match - 必须是build上下文中的路径(为 Dockerfile 所在目录的相对路径),不能是其父目录中的文件
- 如果是目录,则其内部文件或子目录会被递归复制,但目录自身不会被复制
- 如果指定了多个, 或在中使用了通配符,则必须是一个目录,且必须以 / 结尾
- 可以是绝对路径或者是 WORKDIR 指定的相对路径
- 使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等
- 如果事先不存在,它将会被自动创建,这包括其父目录路径,即递归创建目录
范例
COPY hom* /mydir/
COPY hom?.txt /mydir/
ADD: 复制和解包文件
该命令可认为是增强版的COPY,不仅支持COPY,还支持自动解压缩。可以将复制指定的文件到容器中去
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
说明:
- 可以是Dockerfile所在目录的一个相对路径;也可是一个 URL;还可是一个 tar 文件(自动解压)
- 可以是绝对路径或者是 WORKDIR 指定的相对路径
- 如果是目录,只复制目录中的内容,而非目录本身
- 如果是一个 URL ,下载后的文件权限自动设置为 600
- 如果为URL且不以/结尾,则指定的文件将被下载并直接被创建为,如果以 / 结尾,则文件名URL指定的文件将被直接下载并保存为/< filename>
- 如果是一个本地文件系统上的打包文件,如: gz, bz2 ,xz ,它将被解包 ,其行为类似于"tar -x"命令,但是通过URL获取到的tar文件将不会自动展开
- 如果有多个,或其间接或直接使用了通配符,则必须是一个以/结尾的目录路径;如果不以/结尾,则其被视作一个普通文件,文件的内容将被直接写入到
范例:
ADD test relativeDir/ # adds "test" to `WORKDIR`/relativeDir/
ADD test /absoluteDir/ # adds "test" to /absoluteDir/
ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
CMD: 容器启动命令

一个容器中需要持续运行的进程一般只有一个,CMD 用来指定启动容器时默认执行的一个命令,且其运行结束后,容器也会停止,所以一般CMD 指定的命令为持续运行且为前台命令.
- 如果docker run没有指定任何的执行命令或者dockerfile里面也没有ENTRYPOINT命令,那么开启容器时就会使用执行CMD指定的默认的命令
- 前面介绍过的 RUN 命令是在构建镜像时执行的命令,注意二者的不同之处
- 每个 Dockerfile 只能有一条 CMD 命令。如指定了多条,只有最后一条被执行
- 如果用户启动容器时用 docker run xxx 指定运行的命令,则会覆盖 CMD 指定的命令
# 使用 exec 执行,推荐方式,第一个参数必须是命令的全路径,此种形式不支持环境变量
CMD ["executable","param1","param2"]
# 在 /bin/sh 中执行,提供给需要交互的应用;此种形式支持环境变量
CMD command param1 param2
# 提供给 ENTRYPOINT 命令的默认参数
CMD ["param1","param2"]
范例:
CMD ["nginx", "-g", "daemon off;"]
范例:
[root@ubuntu1804 dockerfile]#cat Dockerfile
FROM ubuntu:18.04
LABEL maintainer="ayaka <root@ayaka.com>"
RUN apt update \
&& apt -y install curl \
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s","https://ip.cn"]
[root@centos8 ubuntu]#podman run 9b
{"ip": "111.199.187.36", "country": "北京市", "city": "联通"}
#cat /etc/etc/issue覆盖了curl命令
[root@centos8 ubuntu]#podman run 9b cat /etc/issue
Ubuntu 18.04.4 LTS \n \l
范例:
[root@ubuntu1804 dockerfile]#pwd
/data/dockerfile
[root@ubuntu1804 dockerfile]#cat Dockerfile
FROM busybox
LABEL maintainer=ayaka <root@ayaka.com>"
ENV ROOT /data/website
COPY index.html ${ROOT}/index.html
CMD /bin/httpd -f -h ${ROOT}
EXPOSE 80
[root@ubuntu1804 dockerfile]#cat index.html
Website in Dockerfile
[root@ubuntu1804 dockerfile]#cat build.sh
#!/bin/bash
#
TAG=$1
docker build -t test:$TAG .
[root@ubuntu1804 dockerfile]#./build.sh v1.0
[root@ubuntu1804 dockerfile]#docker run -d --rm -P --name c1 test:v1.0
[root@ubuntu1804 ~]#docker port c1
80/tcp -> 0.0.0.0:32781
[root@ubuntu1804 ~]#curl 127.0.0.1:32781
Website in Dockerfile
[root@ubuntu1804 ~]#
范例:
[root@ubuntu1804 dockerfile]#pwd
/data/dockerfile
[root@ubuntu1804 dockerfile]#cat Dockerfile
FROM busybox
LABEL maintainer="ayaka <root@ayaka.com>"
ENV ROOT /data/website
RUN mkdir -p ${ROOT} && echo '<h1> Busybox httpd server in Dockerfile</h1>' > ${ROOT}/index.html
#COPY index.html ${ROOT}/index.html
CMD ["/bin/sh","-c","/bin/httpd -f -h ${ROOT}"]
#CMD /bin/httpd -f -h ${ROOT}
EXPOSE 80
[root@ubuntu1804 dockerfile]#cat build.sh
#!/bin/bash
#
TAG=$1
docker build -t test:$TAG .
[root@ubuntu1804 dockerfile]#ls
build.sh Dockerfile
[root@ubuntu1804 dockerfile]#./build.sh v2.0
Sending build context to Docker daemon 4.096kB
Step 1/5 : FROM busybox
---> c7c37e472d31
Step 2/5 : ENV ROOT /data/website
---> Using cache
---> ad654b3213c7
Step 3/5 : RUN mkdir -p ${ROOT} && echo '<h1> Busybox httpd server in Dockerfile</h1>' > ${ROOT}/index.html
---> Running in c7c2b920ccb0
Removing intermediate container c7c2b920ccb0
---> d40ce17d4e85
Step 4/5 : CMD ["/bin/sh","-c","/bin/httpd -f -h ${ROOT}"]
---> Running in 52be24840eee
Removing intermediate container 52be24840eee
---> d22731d921df
Step 5/5 : EXPOSE 80
---> Running in 4a50d8d9aa83
Removing intermediate container 4a50d8d9aa83
---> 85d6547a3168
Successfully built 85d6547a3168
Successfully tagged test:v2.0
[root@ubuntu1804 dockerfile]#docker run -d --rm -P --name c2 test:v2.0
cf5fea09ce1362f18a90f4fcdffa3000de26b4919927e1cdce0cce6243f5dd24
[root@ubuntu1804 dockerfile]#docker port c2
80/tcp -> 0.0.0.0:32785
[root@ubuntu1804 dockerfile]#curl 127.0.0.1:32785
<h1> Busybox httpd server in Dockerfile</h1>
[root@ubuntu1804 dockerfile]#docker inspect -f "{{.Config}}" test:v2.0
{ false false false map[80/tcp:{}] false false false
[PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ROOT=/data/website] [/bin/sh -c /bin/httpd -f -h ${ROOT}] <nil> false
sha256:d22731d921df65f9da7452e714cf35ef345529ffa1bd5b92c0024d1d1bd8e714 map[] []
false [] map[] <nil> []}
#查看进程关系
[root@ubuntu1804 ~]#docker exec -it c2 sh
/ # ps
PID USER TIME COMMAND
1 root 0:00 /bin/httpd -f -h /data/website
8 root 0:00 sh
13 root 0:00 ps
ENTRYPOINT: 入口点
功能类似于CMD,配置容器启动后执行的命令及参数
# 使用 exec 执行
ENTRYPOINT ["executable", "param1", "param2"...]
# shell中执行
ENTRYPOINT command param1 param2
- ENTRYPOINT 不能被 docker run 提供的参数覆盖,而是追加,即如果docker run 命令有参数,那么参数全部都会作为ENTRYPOINT的参数
- 如果docker run 后面没有额外参数,但是dockerfile中有CMD命令(即上面CMD的第三种用法),即Dockerfile中即有CMD也有ENTRYPOINT,那么CMD的全部内容会作为ENTRYPOINT的参数
- 如果docker run 后面有额外参数,同时Dockerfile中即有CMD也有ENTRYPOINT,那么docker run后面的参数覆盖掉CMD参数内容,最终作为ENTRYPOINT的参数
- 可以通过docker run --entrypoint string 参数在运行时替换,注意string不要加空格
- 使用CMD要在运行时重新写命令本身,然后在后面才能追加运行参数,ENTRYPOINT则可以运行时无需重写命令就可以直接接受新参数
- 每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个生效
- 通常会利用ENTRYPOINT指令配合脚本,可以为CMD指令提供环境配置
范例:
[root@centos8 ~]#cat /data/dockerfile/web/nginx/nginx-1.18/Dockerfile
FROM centos:centos7.9-v10.0
LABEL maintainer="ayaka <root@ayaka.com>"
ENV version=1.18.0
ADD nginx-$version.tar.gz /usr/local/
RUN cd /usr/local/nginx-$version && ./configure --prefix=/apps/nginx && make && make install && rm -rf /usr/local/nginx* && sed -i 's/.*nobody.*/user nginx;/' /apps/nginx/conf/nginx.conf && useradd -r nginx
COPY index.html /apps/nginx/html
VOLUME ["/apps/nginx/html"]
EXPOSE 80 443
CMD ["-g","daemon off;"]
ENTRYPOINT ["/apps/nginx/sbin/nginx"]
#上面两条指令相当于ENTRYPOINT ["/apps/nginx/sbin/nginx","-g","daemon off;"]
HEALTHCHECK --interval=5s --timeout=3s CMD curl -fs http://127.0.0.1/
范例:
[root@ubuntu1804 ~]#docker run -it --entrypoint cat alpine /etc/issue
Welcome to Alpine Linux 3.12
Kernel \r on an \m (\l)
范例:
[root@ubuntu1804 dockerfile]#cat Dockerfile
FROM ubuntu:18.04
RUN apt update \
&& apt -y install curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s","https://ip.cn"]
[root@centos8 dockerfile]#podman run -it --rm f68e006
{"ip": "111.199.187.36", "country": "北京市", "city": "联通"}
#追加-i参数
[root@centos8 dockerfile]#podman run -it --rm f68e006 -i
HTTP/2 200
date: Sun, 23 Feb 2020 08:05:19 GMT
content-type: application/json; charset=UTF-8
set-cookie: __cfduid=d4a22496ea6f3b2861763354f8ca600711582445119; expires=Tue,
24-Mar-20 08:05:19 GMT; path=/; domain=.ip.cn; HttpOnly; SameSite=Lax
cf-cache-status: DYNAMIC
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-
cgi/beacon/expect-ct"
alt-svc: h3-25=":443"; ma=86400, h3-24=":443"; ma=86400, h3-23=":443"; ma=86400
server: cloudflare
cf-ray: 5697b1ac1862eb41-LAX
{"ip": "111.199.187.36", "country": "北京市", "city": "联通"}
范例: 利用脚本实现指定环境变量动态生成配置文件内容
[root@ubuntu1804 ~]#echo 'Nginx Website in Dockerfile' > index.html
[root@ubuntu1804 ~]#cat Dockerfile
FROM nginx:1.16-alpine
LABEL maintainer="ayaka <root@ayaka.com>"
ENV DOC_ROOT='/data/website/'
ADD index.html ${DOC_ROOT}
ADD entrypoint.sh /bin/
EXPOSE 80/tcp 8080
#HEALTHCHECK --start-period=3s CMD wget -0 - -q http://${IP:-0.0.0.0}:{PORT:-80}/
ENTRYPOINT [ "/bin/entrypoint.sh"]
#CMD指令的内容都成为了ENTRYPOINT的参数
CMD ["/usr/sbin/nginx","-g", "daemon off;"]
[root@ubuntu1804 ~]#cat entrypoint.sh
#!/bin/sh
#注意:alpine镜像没有bash,此处使用sh
cat > /etc/nginx/conf.d/www.conf <<EOF
server {
server_name ${HOSTNAME:-"www.ayaka.com"};
listen ${IP:-0.0.0.0}:${PORT:-80};
root ${DOC_ROOT:-/usr/share/nginx/html};
}
EOF
exec "$@" #我已经干完杂活了,现在请把舞台交给用户指定的那个程序,并且让它直接接管 PID 1 进程
[root@ubuntu1804 ~]#chmod +x entrypoint.sh
[root@ubuntu1804 ~]#docker build -t nginx:v1.0 .
[root@ubuntu1804 ~]#docker run --name n1 --rm -P -e "PORT=8080" -e "HOSTNAME=www.ayaka.org" nginx:v1.0
范例:利用脚本实现指定环境变量动态生成配置文件内容
[root@ubuntu2204 nginx-web-entrypint]#cat Dockerfile
FROM nginx-alpine:1.24.0-v4.0
LABEL maintainer="ayaka <root@ayaka.com>"
RUN mkdir -p /data/website
#COPY index.html /data/website
COPY entrypoint.sh /
CMD ["nginx","-g","daemon off;"]
ENTRYPOINT ["/entrypoint.sh"]
[root@ubuntu2204 nginx-web-entrypint]#cat entrypoint.sh
#!/bin/sh
cat > /apps/nginx/conf/conf.d/www.conf <<EOF
server {
server_name ${HOST:-"www.ayaka.com"};
listen ${IP:-0.0.0.0}:${PORT:-80};
root ${DOC_ROOT:-/apps/nginx/html};
}
EOF
echo ${HOST:-"www.ayaka.com"} > ${DOC_ROOT:-/apps/nginx/html}/index.html
#nginx -g "daemon off;"
exec "$@"
ARG: 构建参数
ARG指令在build 阶段指定变量
和ENV不同的是,容器运行时并不存在ARG定义的环境变量
ARG <name>[=<default value>]
如果和ENV同名,ENV覆盖ARG变量
可以用 docker build --build-arg <参数名>=<值> 来覆盖
范例:
[root@ubuntu1804 ~]#cat Dockerfile
FROM busybox
ARG author="ayaka <root@ayaka.com>"
LABEL maintainer="${author}"
[root@ubuntu1804 ~]#docker build --build-arg author="aykaa@qq.com" -t busybox:v1.0 .
说明: ARG 和 FROM
#FROM指令支持ARG指令放在第一个FROM之前声明变量
#示例:
ARG CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD /code/run-app
FROM extras:${CODE_VERSION}
CMD /code/run-extras
#在FROM之前声明的ARG在构建阶段之外,所以它不能在FROM之后的任何指令中使用。 要使用在第一个FROM之前声明的ARG的默认值,请在构建阶段内使用没有值的ARG指令
# 1. 在外面定义默认值
ARG VERSION=18
FROM node:${VERSION}-alpine
# 2. 在构建阶段内“重新登记”一下,不写赋值符号
ARG VERSION
# 3. ✅ 现在可以正常使用了,它会沿用外面的 18
RUN echo "My version is ${VERSION}"
#示例:
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version
VOLUME: 匿名卷
在容器中创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等,默认会将宿主机上的目录挂载至VOLUME 指令指定的容器目录。即使容器后期被删除,此宿主机的目录仍会保留,从而实现容器数据的持久保存。
宿主机目录为
/var/lib/docker/volumes/<volume_id>/_data
语法:
VOLUME <容器内路径>
VOLUME ["<容器内路径1>", "<容器内路径2>"...]
注意:
<容器内路径>如果在容器内不存在,在创建容器时会自动创建
<容器内路径>如果是存在的,同时目录内有内容,将会把此目录的内容复制到宿主机的实际目录
注意:
- Dockerfile中的VOLUME实现的是匿名数据卷,无法指定宿主机路径和容器目录的挂载关系
- 通过docker rm -fv <容器ID> 可以删除容器的同时删除VOLUME指定的卷
范例: 在容器创建两个/data/ ,/data2的挂载点
VOLUME [ "/data1","/data2" ]
范例:
[root@centos8 ~]#cat /data/dockerfile/system/alpine/Dockerfile
FROM alpine:3.11
LABEL maintainer="ayaka <root@ayaka.com>"
COPY repositories /etc/apk/repositories
VOLUME [ "/testdata","/testdata2" ]
[root@centos8 alpine]#podman run -it --rm 8ef61dd3959da3f sh
# df
Filesystem 1K-blocks Used Available Use% Mounted on
overlay 104806400 3656380 101150020 3% /
tmpfs 65536 0 65536 0% /dev
/dev/sda2 104806400 3656380 101150020 3% /testdata2
/dev/sda2 104806400 3656380 101150020 3% /testdata
/ # cp /etc/issue /testdata/f1.txt
/ # cp /etc/issue /testdata2/f2.txt
[root@centos8 ~]#tree /var/lib/containers/storage/volumes/
/var/lib/containers/storage/volumes/
├── 725f0f67921bdbffbe0aaf9b015d663a6e3ddd24674990d492025dfcf878529b
│ └── _data
│ └── f1.txt
└── fbd13e5253deb375e0dea917df832d2322e96b04ab43bae061584dcdbe7e89f2
└── _data
└── f2.txt
4 directories, 2 files
EXPOSE: 暴露端口
指定服务端的容器需要对外暴露(监听)的端口号,以实现容器与外部通信。
EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会真正暴露端口,即不会自动在宿主进行端口映射
因此,在启动容器时需要通过 -P 或 -p ,Docker 主机才会真正分配一个端口转发到指定暴露的端口才可使用
注意: 即使 Dockerfile 没有 EXPOSE 端口指令,也可以通过docker run -p 临时暴露容器内程序真正监听的端口,所以EXPOSE 相当于指定默认的暴露端口,可以通过docker run -P 进行真正暴露
EXPOSE <port>[/ <protocol>] [<port>[/ <protocol>] ..]
#说明
<protocol>用于指定传输层协议,可为tcp或udp二者之一,默认为TCP协议
范例:
EXPOSE 80 443
EXPOSE 11211/udp 11211/tcp
范例:
[root@ubuntu1804 dockerfile]#pwd
/data/dockerfile
[root@ubuntu1804 dockerfile]#echo Website in Dockerfile > index.html
[root@ubuntu1804 dockerfile]#vim Dockerfile
FROM busybox
LABEL maintainer="ayaka <root@ayaka.com>"
COPY index.html /data/website/
EXPOSE 80
[root@ubuntu1804 dockerfile]#cat build.sh
#!/bin/bash
#
TAG=$1
docker build -t test:$TAG .
[root@ubuntu1804 dockerfile]#chmod +x build.sh
[root@ubuntu1804 dockerfile]#./build.sh v1.0
[root@ubuntu1804 dockerfile]#ls
build.sh Dockerfile index.html
[root@ubuntu1804 dockerfile]#docker run --rm -P --name c1 test:v1.0 /bin/httpd -f -h /data/website
[root@ubuntu1804 ~]#docker port c1
80/tcp -> 0.0.0.0:32773
[root@ubuntu1804 ~]#curl 127.0.0.1:32773
Website in Dockerfile
[root@ubuntu1804 ~]#docker kill c1
c1
WORKDIR: 指定工作目录
为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录,当容器运行后,进入容器内WORKDIR指定的默认目录
WORKDIR 指定工作目录(或称当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会自行创建
注意: 在RUN可以生效,在CMD、ENTRYPOINT可能会有问题
WORKDIR /path/to/workdir
范例:
#两次RUN独立运行,不在同一个目录
RUN cd /app
RUN echo "hello" > world.txt
#如果想实现相同目录
RUN cd /app && echo "hello" > world.txt
#可以使用WORKDIR
WORKDIR /app
RUN echo "hello" > world.txt
可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
#则最终路径为 /a/b/c
ONBUILD: 子镜像引用父镜像的指令
可以用来配置当构建当前镜像的子镜像时,会自动触发执行的指令,但在当前镜像构建时,并不会执行,即延迟到子镜像构建时才执行
ONBUILD [INSTRUCTION]
例如,Dockerfile 使用如下的内容创建了镜像 image-A。
...
ONBUILD ADD http://www.aau.com/wp-content/uploads/2017/09/logo.png /data/
ONBUILD RUN rm -rf /*
ONBUILD RUN /usr/local/bin/python-build --dir /app/src...
如果基于 image-A 创建新的镜像image-B时,新的Dockerfile中使用 FROM image-A指定基础镜像时,会自动执行ONBUILD 指令内容,等价于在后面添加了两条指令。
FROM image-A
#Automatically run the following
ADD http://www.aau.com/wp-content/uploads/2017/09/logo.png /data
RUN /usr/local/bin/python-build --dir /app/src
说明:
- 尽管任何指令都可注册成为触发器指令,但ONBUILD不能自我能套,且不会触发FROM和MAINTAINER指令
- 使用
ONBUILD指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild
USER: 指定当前用户
指定运行容器的用户名或 UID,在后续dockerfile中的 RUN ,CMD和ENTRYPOINT指令时使用此用户
当服务不需要管理员权限时,可以通过该命令指定运行用户
这个用户必须是事先建立好的,否则无法切换
如果没有指定 USER,默认是 root 身份执行
USER <user>[:<group>]
USER <UID>[:<GID>]
范例:
RUN groupadd -r mysql && useradd -r -g mysql mysql
USER mysql
HEALTHCHECK: 健康检查
https://docs.docker.com/reference/dockerfile#healthcheck
检查容器的健康性
HEALTHCHECK [选项] CMD <命令> #设置检查容器健康状况的命令,如果命令执行失败,则返回1,即 unhealthy
HEALTHCHECK NONE #如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK 支持下列选项:
--interval=<间隔> #两次健康检查的间隔,默认为 30 秒
--timeout=<时长> #健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒
#检查结果返回值:
0 #success the container is healthy and ready for use
1 #unhealthy the container is not working correctly
2 #reserved do not use this exit code
范例
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s CMD curl -fs http://localhost/
范例:
#如果健康性检查成功,STATUS会显示 (healthy)
[root@centos8 nginx-1.18]#docker ps
CONTAINER ID IMAGE COMMAND CREATED
STATUS PORTS NAMES
fad976015cad nginx-centos7.9:v1.18-11 "/apps/nginx/sbin/ng…" 6
seconds ago Up 5 seconds (healthy) 0.0.0.0:80->80/tcp, 443/tcp n1
#如果健康性检查不通过,STATUS会显示(unhealthy)
[root@centos8 nginx-1.18]#docker ps
CONTAINER ID IMAGE COMMAND CREATED
STATUS PORTS NAMES
fad976015cad nginx-centos7.9:v1.18-11 "/apps/nginx/sbin/ng…" About a
minute ago Up About a minute (unhealthy) 0.0.0.0:80->80/tcp, 443/tcp n1
#查看健康状态
[root@centos8 ~]#docker inspect -f '{{.State.Health.Status}}' n1
unhealthy
#以json格式显示
[root@centos8 ~]#docker inspect -f '{{json .State.Health}}' n1
{"Status":"unhealthy","FailingStreak":120,"Log":[{"Start":"2021-01-
12T23:26:55.897317224+08:00","End":"2021-01-
12T23:26:56.117988749+08:00","ExitCode":22,"Output":""},{"Start":"2021-01-
12T23:27:01.121614252+08:00","End":"2021-01-
12T23:27:01.358263627+08:00","ExitCode":22,"Output":""},{"Start":"2021-01-
12T23:27:06.362034898+08:00","End":"2021-01-
12T23:27:06.596203379+08:00","ExitCode":22,"Output":""},{"Start":"2021-01-
12T23:27:11.597954624+08:00","End":"2021-01-
12T23:27:11.857543947+08:00","ExitCode":22,"Output":""},{"Start":"2021-01-
12T23:27:16.861453605+08:00","End":"2021-01-
12T23:27:17.115885101+08:00","ExitCode":22,"Output":""}]}
[root@centos8 ~]#docker inspect -f '{{json .State.Health}}' n1 | python3 -m json.tool
{
"Status": "unhealthy",
"FailingStreak": 136,
"Log": [
{
"Start": "2021-01-12T23:28:19.950974202+08:00",
"End": "2021-01-12T23:28:20.204912349+08:00",
"ExitCode": 22,
"Output": ""
},
{
"Start": "2021-01-12T23:28:25.208822104+08:00",
"End": "2021-01-12T23:28:25.491441522+08:00",
"ExitCode": 22,
"Output": ""
},
{
"Start": "2021-01-12T23:28:30.495419338+08:00",
"End": "2021-01-12T23:28:30.714133496+08:00",
"ExitCode": 22,
"Output": ""
},
{
"Start": "2021-01-12T23:28:35.718450539+08:00",
"End": "2021-01-12T23:28:35.988073766+08:00",
"ExitCode": 22,
"Output": ""
},
{
"Start": "2021-01-12T23:28:40.993098183+08:00",
"End": "2021-01-12T23:28:41.260442962+08:00",
"ExitCode": 22,
"Output": ""
}
]
}
.dockerignore文件
官方文档: https://docs.docker.com/engine/reference/builder/#dockerignore-file
与.gitignore文件类似,生成构建上下文时Docker客户端应忽略的文件和文件夹指定模式
.dockerignore 使用 Go 的文件路径规则 filepath.Match
参考链接: https://golang.org/pkg/path/filepath/#Match
完整的语法
# #以#开头的行为注释
* #匹配任何非分隔符字符序列
? #匹配任何单个非分隔符
\\ #表示 \
** #匹配任意数量的目录(包括零)例如,**/*.go将排除在所有目录中以.go结尾的所有文件,包括构建上下文的根。
! #表示取反,可用于排除例外情况
| Rule (规则) | Behavior (行为) |
|---|---|
# comment | 被忽略 (Ignored)。 |
*/temp* | 排除根目录下一级子目录中以 temp 开头的文件和目录。例如,普通文件 /somedir/temporary.txt 会被排除,目录 /somedir/temp 也会被排除。 |
*/*/temp* | 排除根目录下两级子目录中以 temp 开头的文件和目录。例如,/somedir/subdir/temporary.txt 会被排除。 |
temp? | 排除根目录下名称为 temp 且带有一个字符后缀的文件和目录。例如,/tempa 和 /tempb 会被排除。 |
范例:
#排除 test 目录下的所有文件
test/*
#排除 md 目录下的 xttblog.md 文件
md/xttblog.md
#排除 xttblog 目录下的所有 .md 的文件
xttblog/*.md
#排除以 xttblog 为前缀的文件和文件夹
xttblog?
#排除所有目录下的 .sql 文件夹
**/*.sql
范例:
#除了README的md不排外,排除所有md文件,但不排除README-secret.md
*.md
!README*.md
README-secret.md
#除了所有README的md文件以外的md都排除
*.md
#README-secret.md
!README*.md
Dockerfile 构建过程和指令总结
Dockerfile 构建过程
- 从基础镜像运行一个容器
- 执行一条指令,对容器做出修改
- 执行类似docker commit的操作,提交一个新的中间镜像层(可以利用中间层镜像创建容器进行调试和排错)
- 再基于刚提交的镜像运行一个新容器
- 执行Dockerfile中的下一条指令,直至所有指令执行完毕
Dockerfile 指令总结
| BUILD (构建阶段) | RUN (运行阶段) | BOTH (两个阶段) |
|---|---|---|
FROM | CMD | WORKDIR |
LABEL | VOLUME | USER |
COPY | EXPOSE | ENV |
ADD | ENTRYPOINT | |
RUN | ||
ONBUILD | ||
.dockerignore |
构建镜像docker build 命令
docker build命令使用Dockerfile文件创建镜像
docker build [OPTIONS] PATH | URL | -
说明:
PATH | URL | - #可以使是本地路径,也可以是URL路径。若设置为 - ,则从标准输入获取Dockerfile的内容
-f, --file string #Dockerfile文件名,默认为 PATH/Dockerfile
--force-rm #总是删除中间层容器,创建镜像失败时,删除临时容器
--no-cache #不使用之前构建中创建的缓存
-q --quiet=false #不显示Dockerfile的RUN运行的输出结果
--rm=true #创建镜像成功时,删除临时容器
-t --tag list #设置注册名称、镜像名称、标签。格式为 <注册名称>/<镜像名称>:<标签>(标签默认为latest)
范例:
docker build .
docker build /usr/local/src/nginx
docker build -f /path/to/a/Dockerfile .
docker build -t shykes/myapp .
docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
docker build -t test/myapp .
docker build -t nginx:v1 /usr/local/src/nginx
查看镜像的构建历史:
docker history 镜像ID
范例:
[root@centos8 ~]#podman history 90201858b1fc
ID CREATED CREATED BY
SIZE COMMENT
90201858b1fc 17 minutes ago /bin/sh -c #(nop) CMD ["tail" ,"-f","/etc/...
0B
<missing> 2 hours ago /bin/sh -c apt-get update && apt-get insta...
14.83MB
<missing> 35 hours ago /bin/sh -c #(nop) CMD ["/bin/bash"]
14.83MB
<missing> 35 hours ago /bin/sh -c mkdir -p /run/systemd && echo '...
3.072kB
<missing> 35 hours ago /bin/sh -c set -xe && echo '#!/bin/sh' > /...
15.87kB
<missing> 35 hours ago /bin/sh -c [ -z "$(apt-get indextargets)" ]
991.2kB
<missing> 35 hours ago /bin/sh -c #(nop) ADD file:91a750fb184711f...
65.58M
范例: 利用Dockerfile构建基于CentOS的nginx镜像
[root@ubuntu1804 ~]#vim /data/Dockerfile
[root@ubuntu1804 ~]#cat /data/Dockerfile
FROM centos
LABEL maintainer="ayaka <root@ayaka.com>"
RUN yum install -y nginx && echo Nginx Website in Docker > /usr/share/nginx/html/index.html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
#ENTRYPOINT ["nginx", "-g", "daemon off;"]
[root@ubuntu1804 ~]#docker build -t nginx_centos8.2:v1.14.1 /data/
Sending build context to Docker daemon 209.2MB
Step 1/6 : FROM centos
---> 831691599b88
Step 2/6 : LABEL maintainer="ayaka <root@ayaka.com>"
---> Running in 3fc4487e80f9
Removing intermediate container 3fc4487e80f9
---> 598318841b8a
Step 3/6 : RUN yum install -y nginx
---> Running in 8a6d9866e4ae
CentOS-8 - AppStream 2.1 MB/s | 5.8 MB 00:02
CentOS-8 - Base 1.8 MB/s | 2.2 MB 00:01
CentOS-8 - Extras 8.6 kB/s | 7.0 kB 00:00
.......
Complete!
Removing intermediate container 8a6d9866e4ae
---> 8963fb608c33
Step 4/6 : RUN echo Nginx Website in Docker > /usr/share/nginx/html/index.html
---> Running in 04d4287aac49
Removing intermediate container 04d4287aac49
---> 9a95e56b9bc0
Step 5/6 : EXPOSE 80
---> Running in 8534523d8aa6
Removing intermediate container 8534523d8aa6
---> 23cca5737903
Step 6/6 : CMD ["nginx", "-g", "daemon off;"]
---> Running in d52fcc21444f
Removing intermediate container d52fcc21444f
---> afdaec99eb35
Successfully built afdaec99eb35
Successfully tagged nginx_centos8.2:v1.14.1
[root@ubuntu1804 ~]#docker run -d -P --name nginx-web nginx_centos8.2:v1.14.1
3faba5a49f63ab3a1b031da5e4940303b10ddf349069184597e703c572d257d8
[root@ubuntu1804 ~]#docker ps
CONTAINER ID IMAGE COMMAND CREATED
STATUS PORTS NAMES
3faba5a49f63 nginx_centos8.2:v1.14.1 "nginx -g 'daemon of…" 4 seconds
ago Up 3 seconds 0.0.0.0:32775->80/tcp nginx-web
[root@ubuntu1804 ~]#curl http://127.0.0.1:32775
Nginx Website in Docker
[root@ubuntu1804 ~]#curl -I http://127.0.0.1:32775
HTTP/1.1 200 OK
Server: nginx/1.14.1
Date: Wed, 22 Jul 2020 17:09:15 GMT
Content-Type: text/html
Content-Length: 24
Last-Modified: Wed, 22 Jul 2020 17:04:40 GMT
Connection: keep-alive
ETag: "5f1871a8-18"
Accept-Ranges: bytes
范例: 刷新镜像缓存重新构建新镜像
[root@ubuntu1804 ~]#cat /data/Dockerfile
FROM centos
LABEL maintainer="ayaka <root@ayaka.com>"
RUN yum install -y nginx
RUN echo Nginx Website in Docker > /usr/share/nginx/html/index.html
#修改下面行,从下面行开始不再使用缓存
ENV REFRESH_DATA 2020-01-01
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
[root@ubuntu1804 ~]#docker build -t nginx_centos8.2:v1.14.1 /data/
Sending build context to Docker daemon 209.2MB
Step 1/7 : FROM centos
---> 831691599b88
Step 2/7 : LABEL maintainer="ayaka <root@ayaka.com>"
---> Using cache
---> 598318841b8a
Step 3/7 : RUN yum install -y nginx
---> Using cache
---> 8963fb608c33
Step 4/7 : RUN echo Nginx Website in Docker > /usr/share/nginx/html/index.html
---> Using cache
---> 9a95e56b9bc0
Step 5/7 : ENV REFRESH_DATA 2020-01-01 #从此行开始不再利用缓存
---> Running in 4607ee0d0e77
Removing intermediate container 4607ee0d0e77
---> d6235889f336
Step 6/7 : EXPOSE 80
---> Running in 6924aab5c5c8
Removing intermediate container 6924aab5c5c8
---> 545393760683
Step 7/7 : CMD ["nginx", "-g", "daemon off;"]
---> Running in 345bbc6179d8
Removing intermediate container 345bbc6179d8
---> 4bafc2d0c7e0
Successfully built 4bafc2d0c7e0
Successfully tagged nginx_centos8.2:v1.14.1
#全部不利用缓存重新构建镜像
[root@ubuntu1804 ~]#docker build --no-cache -t nginx_centos8.2:v1.14.1 /data/
Sending build context to Docker daemon 209.2MB
Step 1/7 : FROM centos
---> 831691599b88
Step 2/7 : LABEL maintainer="ayaka <root@ayaka.com>"
---> Running in 41f2aab6657f
Removing intermediate container 41f2aab6657f
---> 091969d0ed9e
Step 3/7 : RUN yum install -y nginx
---> Running in 6e174d492348
CentOS-8 - AppStream 4.2 MB/s | 5.8 MB 00:01
CentOS-8 - Base 1.7 MB/s | 2.2 MB 00:01
CentOS-8 - Extras 1.2 kB/s | 7.0 kB 00:05
Dependencies resolved.
......
Complete!
Removing intermediate container 6e174d492348
---> ba62ac34b951
Step 4/7 : RUN echo Nginx Website in Docker > /usr/share/nginx/html/index.html
---> Running in d6f785b28ef6
Removing intermediate container d6f785b28ef6
---> 6e15fdc84e21
Step 5/7 : ENV REFRESH_DATA 2020-06-06
---> Running in c6fd87ed95f6
Removing intermediate container c6fd87ed95f6
---> 328b8621ec36
Step 6/7 : EXPOSE 80
---> Running in 1af3d6964d81
Removing intermediate container 1af3d6964d81
---> 7c513643b182
Step 7/7 : CMD ["nginx", "-g", "daemon off;"]
---> Running in fd9216490941
Removing intermediate container fd9216490941
---> 0b2b61dd0445
Successfully built 0b2b61dd0445
Successfully tagged nginx_centos8.2:v1.14.1
从镜像导出对应的 Dockerfile
想查看一个其它组织的Docker镜像是怎么构建的,可以通过whaler 实现
https://github.com/P3GLEG/Whaler
范例:Docker 安装 whaler
# alias export docker image to dockerfile
alias whaler="docker run -t --rm -v /var/run/docker.sock:/var/run/docker.sock:ro pegleg/whaler"
范例:查看 alpine:3.15的Dockerfile内容
[root@ubuntu2204 ~]#whaler alpine:3.15
3.15: Pulling from library/alpine
0ce1dd7918a4: Pull complete
Digest: sha256:ecbdce53b2c2f43ab1b19418bcbd3f120a23547d9497030c8d978114512b883e
Status: Downloaded newer image for alpine:3.15
Analyzing alpine:3.15
Docker Version: 20.10.23
GraphDriver: overlay2
Environment Variables
|PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Image user
|User is root
Potential secrets:
|Found match etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub
Possible public key \.pub$
d4fe24b10af18656416183114772e2b4841724cc847e275cf3e5bebe0acd20e1/layer.tar
|Found match etc/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub
Possible public key \.pub$
d4fe24b10af18656416183114772e2b4841724cc847e275cf3e5bebe0acd20e1/layer.tar
|Found match etc/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub
Possible public key \.pub$
d4fe24b10af18656416183114772e2b4841724cc847e275cf3e5bebe0acd20e1/layer.tar
|Found match etc/apk/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub
Possible public key \.pub$
d4fe24b10af18656416183114772e2b4841724cc847e275cf3e5bebe0acd20e1/layer.tar
|Found match etc/apk/keys/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub
Possible public key \.pub$
d4fe24b10af18656416183114772e2b4841724cc847e275cf3e5bebe0acd20e1/layer.tar
|Found match etc/udhcpd.conf DHCP server configs dhcpd[^ ]*.conf
d4fe24b10af18656416183114772e2b4841724cc847e275cf3e5bebe0acd20e1/layer.tar
Dockerfile:
CMD ["/bin/sh"]