Docker镜像制作
文章目录
- Docker镜像制作
-
- 镜像制作及原因
- Docker镜像制作方式
- 快照方式制作镜像
-
- 制作命令
-
- [docker commit](#docker commit)
- 快照制作镜像实战
-
- [实战一、C++ HelloWorld镜像制作](#实战一、C++ HelloWorld镜像制作)
- 实战二、Springboot镜像制作
- Dockerfile制作镜像
- FROM
- MAINTAINER
- LABEL
- COPY
- ENV
- WORKDIR
- ADD
- RUN
- CMD
- EXPOSE
- ENTRYPOINT
- ARG
- VOLUME
- SHELL
- USER
- HEALTHCHECK
- ONBUILD
- STOPSIGNAL
- [制作命令 docker build](#制作命令 docker build)
- [Dockerfile 优秀编写应该具备以下几点](#Dockerfile 优秀编写应该具备以下几点)
镜像制作及原因
镜像制作是因为某种需求,官方的镜像无法满足需求,需要自定义镜像来满足要求:
往往因为以下原因自己制作镜像:
- 编写的代码如何打包到镜像中直接跟随镜像发布
- 第三方制作的内容安全性未知,如含有安全漏洞
- 特定的需求或者功能无法满足,如需要数据库,加审计功能
- 公司内部要求基于公司内部的系统制作镜像,如公司内部要求使用自己的操作系统作为基础镜像
Docker镜像制作方式
制作容器镜像,主要有两种方法:
- 制作快照方式获得镜像(偶尔制作的镜像):在基础镜像上(比如Ubuntu),先登录容器中,然后安装需要的所有软件,最后整体制作快照。
- Dockerfile方式构建镜像(经常更新的镜像):将软件安装的流程写成Dockerfile,使用docker build构建成容器镜像。
快照方式制作镜像
制作命令
docker commit
- 功能:从容器创建一个新的镜像。
- 语法:
shell
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
- 参数:
-a:提交的镜像作者;-c:使用Dockerfile指令来创建镜像;可以修改启动指令-m:提交时的说明文字;-p:在commit时,将容器暂停。
- 样例:
shell
docker commit c3f279d17e0a drw/mynginx:v01
快照制作镜像实战
实战一、C++ HelloWorld镜像制作
- 创建临时工作目录
shell
mkdir -p /data/drw/commitimage
cd /data/drw/commitimage
- 编写C++源代码文件,demo.cpp
c
#include <stdio.h>
int main()
{
printf("hello docker!\n");
return 0;
}
- 启动一个centos7的容器
shell
root@139-159-150-152:/data/drw/commitimage# docker run -it --name mycppcommit centos:7 bash
[root@40de1bf45017 /]#
- 安装编译软件,并创建源代码目录
shell
[root@40de1bf45017 /]# yum install -y gcc
[root@40de1bf45017 /]# mkdir /src
- 打开另外一个shell,拷贝源代码到容器中
shell
root@139-159-150-152:/data/drw/commitimage# docker cp ./demo.c mycppcommit:/src
Successfully copied 2.048kB to mycppcommit:/src
# 查看容器中
[root@40de1bf45017 /]# ls -l /src
total 4
-rw-r--r-- 1 root root 80 May 16 05:30 demo.c
- 编译运行
shell
[root@40de1bf45017 /]# cd /src
[root@40de1bf45017 src]# gcc demo.c -o demo
[root@40de1bf45017 src]# ./demo
hello docker!
- 提交为一个镜像
shell
root@139-159-150-152:/data/maxhou/commitimage# docker commit mycppcommit mycppimg:v1.0
sha256:97d178ba9e5d94d8276fe0e23dc73510abea7f03f7f3c3a59a978dc8f+a2c
root@139-159-150-152:/data/maxhou/commitimage# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mycppimg v1.0 97d178ba9e5d 13 seconds ago 714MB
- 测试镜像能否正常运行
shell
root@139-159-150-152:/data/maxhou/commitimage# docker run -it mycppimg:v1.0 /src/demo
hello docker!
实战二、Springboot镜像制作
- 启动一个java8的容器
原来的java8改名为了openjdk:8,openjdk也有openjdk17等镜像
shell
root@139-159-150-152:/data/maxhou/commitimage# docker run -it --name myjavacommit openjdk:8 bash
root@9ac4233dc4c8:/# java -version
openjdk version "1.8.0_111"
OpenJDK Runtime Environment (build 1.8.0_111-8u111-b14-2~bpo8+1-b14)
OpenJDK 64-Bit Server VM (build 25.111-b14, mixed mode)
- 打开另外一个shell窗口,拷贝制作过的jar包到容器的目录
shell
docker cp ./springboot-demo-1.0-SNAPSHOT.jar myjavacommit:/app.jar
- java -jar完成启动测试
shell
root@9ac4233dc4c8:/# java -jar ./app.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.8)
2023-05-16 06:09:30.161 INFO 16 --- [ main] com.bit.Main : Starting Main v1.0-SNAPSHOT using Java 1.8.0_111 on 9ac4233dc4c8 with PID 16 (/app.jar started by root in /)
2023-05-16 06:09:30.173 INFO 16 --- [ main] com.bit.Main : No active profile set, falling back to 1 default profile: "default"
2023-05-16 06:09:32.912 INFO 16 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8799 (http)
2023-05-16 06:09:32.961 INFO 16 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-05-16 06:09:32.962 INFO 16 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.71]
2023-05-16 06:09:33.186 INFO 16 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-05-16 06:09:33.187 INFO 16 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2814 ms
2023-05-16 06:09:34.629 INFO 16 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8799 (http) with context path ''
2023-05-16 06:09:34.654 INFO 16 --- [ main] com.bit.Main : Started Main in 5.777 seconds (JVM running for 7.582)
- 执行docker commit
shell
root@139-159-150-152:/data/maxhou/commitimage# docker commit -c 'CMD ["java", "-jar", "/app.jar"]' myjavacommit myjavaimg:v1.0
sha256:865d428c7a87ec54819bbb55d2e21d071f6149011d77a074ba638223ffc491a22
root@139-159-150-152:/data/maxhou/commitimage# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
myjavaimg v1.0 865d428c7a87 18 seconds ago 661MB
- 使用新创建的容器启动一个服务检查是否能够正常运行
shell
root@139-159-150-152:/data/maxhou/commitimage# docker run -p 8799:8799 -d --name mycommittjavaimg1 myjavaimg:v1.0
e336d7157d6c0cd0fba16a5aeb1f7ba2c19b449dde6320f7ac2880529b1d
root@139-159-150-152:/data/maxhou/commitimage# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e336d7157d6c myjavaimg:v1.0 "java -jar /app.jar" 2 seconds ago Up 2 seconds 0.0.0.0:8799->8799/tcp, :::8799->8799/tcp mycommittjavaimg1
- 通过浏览器访问8799端口对应的接口
访问:http://139.159.150.152:8799/hello
返回:Hello docker!
Dockerfile制作镜像
Dockerfile是什么
镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么就是Dockerfile。
Dockerfile格式
shell
# Comment
INSTRUCTION arguments
该指令不区分大小写。约定大写以便更容易地将它们与参数区分开。
以#开头的行视为注释。行中其他任何地方的标记#都被视为参数:
shell
# Run echo 'we are running some # of cool things'
RUN echo 'we are running some # of cool things'
为什么需要Dockerfile
Dockerfile 是容器镜像构建的标准最佳实践,它将镜像构建流程代码化,彻底替代了黑箱式的 docker commit,实现了可重复、可追溯的自动化构建;它让镜像变更可审计、维护更便捷,还能通过多阶段构建剔除冗余文件,产出更轻量、标准化的镜像。
Dockerfile指令
指令清单
| 指令 | 功能 |
|---|---|
| FROM | 构建镜像基于哪个镜像,也就是基础镜像 |
| MAINTAINER | 镜像维护者姓名或邮箱地址 |
| LABEL | 为镜像添加元数据 |
| COPY | 拷贝文件或者目录到镜像中,跟ADD类似 |
| ADD | 拷贝文件或者目录到镜像中,如果是URL或压缩包便会自动下载或自动解压 |
| WORKDIR | 指定工作目录 |
| RUN | 指定docker build过程中运行的程序 |
| VOLUME | 指定容器挂载点 |
| EXPOSE | 声明容器的服务端口(仅仅是声明) |
| ENV | 设置环境变量 |
| CMD | 运行容器时执行的命令 |
| ENTRYPOINT | 运行容器时程序入口 |
| ARG | 指定构建时的参数 |
| SHELL | 指定采用哪个shell |
| USER | 指定当前用户 |
| HEALTHCHECK | 健康检查指令 |
| ONBUILD | 当前镜像被构建时,不会被执行,只有当以该镜像为基础镜像进行构建的时候才会被执行 |
| STOPSIGNAL | 允许您覆盖发送到容器的默认信号 |
FROM
功能
- FROM指令用于为镜像文件构建过程指定基础镜像,后续的指令运行于此基础镜像所提供的运行环境。
- 实践中,基础镜像可以是任何可用镜像文件,默认情况下,docker build会在docker主机上查找指定的镜像文件,在其不存在时,则会自动从Docker Hub公共仓库拉取镜像下来。如果找不到指定的镜像文件,docker build会返回一个错误信息。
- FROM可以在一个Dockerfile中出现多次,如果有需求在一个Dockerfile中创建多个镜像,或将一个构建阶段作为另一个的依赖。
- 如果FROM语句没有指定镜像标签,则默认使用latest标签。
语法
shell
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
- 参数:
--platform=<platform>:构建的CPU架构,如linux/amd64, linux/arm64<image>:指定作为base image的名称;<tag>:base image的标签,省略时默认latest;<digest>:镜像的哈希码;AS <name>:指定构建的名称,配合COPY --from可以完成多级构建
样例
shell
FROM mysql:latest
实战
- 创建Docker目录,确保目录没有内容
shell
mkdir -p /data/mydocker/dockerfile/web1
cd /data/mydocker/dockerfile/web1
- 编辑Dockerfile,测试FROM指令和注释,在web1目录中vi Dockerfile,输入以下内容
shell
#我的web站点 by me
FROM ubuntu:22.04 as buildbase
- 执行构建,打造镜像v0.1版本
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.1 .
[+] Building 0.1s (5/5) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 95B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/ubuntu:22.04
=> [1/1] FROM docker.io/library/ubuntu:22.04
=> exporting to image
=> => exporting layers
=> => writing image sha256:6c84b273e28b1d0a4f12b9d04dc8a329acddfb212134b961ee26
=> => naming to docker.io/library/web1:v0.1
- 运行制作的镜像,可以看到操作系统版本
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it --name test-ubuntu-22.04 web1:v0.1 cat /etc/release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.2 LTS"
PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
MAINTAINER
功能
- 用于让dockerfile制作者提供本人的详细信息
- 该功能已经废弃,由label替代
语法
shell
MAINTAINER <author's detail>
- 参数:
<author's detail>:作者信息
样例
shell
MAINTAINER "maxhou <maxhou@bit.com>"
实战
- FROM添加制作者信息,使用MAINTAINER
shell
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
MAINTAINER "drw"
- 再次编译0.2版本
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.2 .
[+] Building 0.1s (5/5) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 130B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/ubuntu:22.04
=> CACHED [1/1] FROM docker.io/library/ubuntu:22.04
=> exporting to image
=> => exporting layers
=> => writing image sha256:b49e798e7b7b37dfd1edf796faa9cc2355dc7463b5219b7bc4134d8
=> => naming to docker.io/library/web1:v0.2
- 查看镜像信息,可以看到作者信息已经添加完成
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker image inspect web1:v0.2
[
...
"DockerVersion": "",
"Author": "drw",
...
]
LABEL
功能
- 为镜像添加元数据,元数据是key对形式
语法
shell
LABEL <key>=<value> <key>=<value> <key>=<value> ...
样例
shell
LABEL com.example.label-with-value="foo"
LABEL multi.label1="value1" multi.label2="value2" other="value3"
实战
- 我们使用LABEL添加额外的元数据信息,继续vi Dockerfile
shell
#我的web站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou <maxhou@bit.com>"
LABEL company="com.bit" app="nginx"
- 我们继续构建v0.3版本
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.3 .
[+] Building 0.1s (5/5) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 188B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/ubuntu:22.04
=> CACHED [1/1] FROM docker.io/library/ubuntu:22.04
=> exporting to image
=> => exporting layers
=> => writing image sha256:2d33a63430f8f43ceb3f1cc264f487499f05b0ffe46e5e5db53967ea5f24f
=> => naming to docker.io/library/web1:v0.3
- 查看镜像元数据
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker inspect web1:v0.3
[
...
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/bash"
],
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {
"app": "nginx",
"company": "com.bit",
"org.opencontainers.image.ref.name": "ubuntu",
"org.opencontainers.image.version": "22.04"
}
},
...
]
COPY
功能
- 用于从Docker主机复制文件或者目录至创建的新镜像指定路径中。
语法
shell
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 >必须是build上下文中的路径,不能是其父目录中的文件;
- 如果< src> 是目录,则其内部文件或子目录会被递归复制,但< src>目录自身不会被复制;
- 如果指定了多个< src>,或在< src>中使用了通配符,则< dest>必须是一个目录,且必须以/结尾;
- 如果< dest>事先不存在,它将会被自动创建,这包括父目录路径。
样例
shell
# 要确保dockerfile同级路径下有index.html文件
COPY index.html /data/web/html/
实战
- 创建一个index.html,作为我们站点的首页,vi index.html输入下面内容
html
<html>
<h1>Hello My Home Page by bit</h1>
</html>
- 通过COPY命令添加到镜像中,并且指定我们的根目录为/data/web/www
shell
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
COPY index.html /data/web/www/
- 再次编译v0.4版本镜像
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.4 .
[+] Building 0.2s (7/7) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 199B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/ubuntu:22.04
=> CACHED [1/2] FROM docker.io/library/ubuntu:22.04
=> [internal] load build context
=> => transferring context: 90B
=> [2/2] COPY index.html /data/web/www/
=> exporting to image
=> => exporting layers
=> => writing image sha256:ae167b38e1c42832e5012c8d0e417c7ba9745ea643f24ebcb5de9381ee2b
=> => naming to docker.io/library/web1:v0.4
- 运行镜像,可以看到index.html已经进去了
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it web1:v0.4 ls /data/web/www
index.html
ENV
功能
- 用于为镜像定义所需的环境变量,并可被Dockerfile文件中位于其后的其它指令(如ENV、ADD、COPY等)所调用。调用格式为 variable_name或{variable_name}
语法
shell
ENV <key>=<value> ...
样例
shell
ENV myName="John Doe" myDog=Rex\ The\ Dog \
myCat=fluffy
实战
- 目录后面可能变化,将目录的位置提取为变量,通过ENV来设置,再次编辑Dockerfile
shell
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/data/web/www/
COPY index.html ${WEB_ROOT}
- 再次编译v0.5版本
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.5 .
[+] Building 0.1s (7/7) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 224B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/ubuntu:22.04
=> CACHED [1/2] FROM docker.io/library/ubuntu:22.04
=> [internal] load build context
=> => transferring context: 31B
=> [2/2] COPY index.html ${WEB_ROOT}
=> exporting to image
=> => exporting layers
=> => writing image sha256:91a88cc63fecc10166610f43e16de20cb87b2a5dcb14a9d88
=> => naming to docker.io/library/web1:v0.5
- 运行v0.5版本镜像,验证index.html是否在$WEB_ROOT的目录下面
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it web1:v0.5 ls $WEB_ROOT
index.html
- 也可以通过inspect查看,可以看到环境变量已经被添加到镜像里面了
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker image inspect web1:v0.5
[
...
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"WEB_ROOT=/data/web/www/"
],
...
},
...
]
WORKDIR
功能
- 用于为Dockerfile中所有的RUN、CMD、ENTRYPOINT、COPY和ADD指定设定工作目录。
语法
shell
WORKDIR /path/to/workdir
注意事项
- 如果指定的工作目录不存在,它将会被
自动创建; - WORKDIR指令可多次出现,如果指定了相对路径,则它将相对于前一条WORKDIR指令的路径。例如:
shell
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
# 最终输出的输出Dockerfile为/a/b/c
实战
- 使用相对路径来执行下一个目录,再次编辑Dockerfile,指定WORKDIR
shell
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/data/web/www/
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
- 再次编译v0.6版本
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.6 .
[+] Building 0.1s (8/8) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 243B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/ubuntu:22.04
=> CACHED [2/3] FROM docker.io/library/ubuntu:22.04
=> [internal] load build context
=> => transferring context: 31B
=> CACHED [3/3] COPY index.html ${WEB_ROOT}
=> [4/4] WORKDIR /usr/local
=> exporting to image
=> => exporting layers
=> => writing image sha256:718a1930947993b8a32a9164d23d0e5202c5e4b2157f5d15036e17fd738
=> => naming to docker.io/library/web1:v0.6
- 执行pwd命令查看当前目录
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it --name web1-rm web1:v0.6 pwd
/usr/local
ADD
功能
- ADD指令类似于COPY指令,ADD支持使用TAR文件和URL路径,会自动完成解压和下载。
语法
shell
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
- 参数:
<src>:要复制的源文件或目录,支持使用通配符;<dest>:目标路径,即正在创建的image的文件系统路径;建议使用绝对路径,否则,ADD指定以WORKDIR为其真实路径;在路径中有空白字符时,通常使用第2种格式;--chown:修改用户和组
实战
-
登录nginx官网https://nginx.org/,找到最新稳定版本的nginx的下载地址**https://nginx.org/download/nginx-1.22.1.tar.gz .**
-
通过ADD命令下载,可以提取nginx的版本为环境变量,再次编辑Dockerfile
shell
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION=nginx-1.22.1
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
- 执行编译v0.7
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.7 .
[+] Building 2.3s (10/10) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 337B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/ubuntu:22.04
=> [internal] load build context
=> => transferring context: 31B
=> [1/4] FROM docker.io/library/ubuntu:22.04
=> CACHED [2/4] COPY index.html ${WEB_ROOT}
=> CACHED [3/4] WORKDIR /usr/local
=> [4/4] ADD https://nginx.org/download/nginx-1.22.1.tar.gz ./src
=> exporting to image
=> => exporting layers
=> => writing image sha256:8c235c363579b3c3f3fe6f1131684025a9717fac349ef9a7cae2ab7380
=> => naming to docker.io/library/web1:v0.7
- 运行v0.7镜像查看,看是否已经完成了下载
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it --name web1 --rm web1:v0.7 ls -l /usr/local/src
total 1052
-rw------- 1 root root 1073948 Oct 19 09:23 nginx-1.22.1.tar.gz
- 此时并没有被解压
- 手动下载下来这个压缩包,放到我们的服务器目录,此时目录的结构如下:
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# wget https://nginx.org/download/nginx-1.22.1.tar.gz
--2023-03-14 15:32:34-- https://nginx.org/download/nginx-1.22.1.tar.gz
Resolving nginx.org (nginx.org)... 52.58.199.22, 3.125.197.172, 2a05:d014:edb:5704::6, ...
Connecting to nginx.org (nginx.org)|52.58.199.22|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1073948 (1.0M) [application/octet-stream]
Saving to: 'nginx-1.22.1.tar.gz'
nginx-1.22.1.tar.gz 100%[===================>] 1.02M 1.95MB/s in 0.5s
2023-03-14 15:32:36 (1.95 MB/s) - 'nginx-1.22.1.tar.gz' saved [1073948/1073948]
root@139-159-150-152:/data/mydocker/dockerfile/web1# ll -h
drwxr-xr-x 2 root root 4.0K Mar 14 14:37 ./
drwxr-xr-x 3 root root 4.0K Mar 14 15:26 ../
-rw-r--r-- 1 root root 298 Mar 14 15:23 Dockerfile
-rw-r--r-- 1 root root 53 Oct 19 07:43 index.html
-rw-r--r-- 1 root root 1.1M Mar 14 15:32 nginx-1.22.1.tar.gz
- 再次编辑Dockerfile,添加将nginx放到src2目录
shell
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION=nginx-1.22.1
COPY index.html ${WEB_ROOT}
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
ADD ${NGINX_VERSION}.tar.gz ./src2
- 执行命令编译v0.8
shell
docker build -t web1:v0.8 .
- 运行v0.8版本的镜像,查看该镜像的/usr/local/src2目录,nginx已经被解压
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it --name web1 --rm web1:v0.8 ls -l /usr/local/src2
total 4
drwxr-xr-x 8 1001 1001 4096 Oct 19 08:02 nginx-1.22.1
RUN
功能
- 用于指定docker build过程中运行的程序,其可以是任何命令
语法
shell
# shell form
RUN <command>
# exec form
RUN ["executable", "param1", "param2"]
- 参数:
- 第一种格式中,
< command>通常是一个shell命令 ,且以"/bin/sh -c"来运行它,这意味着此进程在容器中的PID不为1,不能接收信号,因此,当使用docker stop < container>命令停止容器时,此进程接收不到SIGTERM信号; - 第二种格式中的参数是JSON格式数组 ,其中
< executable>为要运行的命令,后面的< paramN>为传递给命令的选项或参数;然而,此种格式指定的命令不会以"/bin/sh -c"来发起,因此常见的shell操作如变量替换以及通配符(?,*等)替换将不会进行;不过,如果要运行的命令依赖于此shell特性的话,可以将其替换为类似于下面的格式:
- 第一种格式中,
shell
RUN ["/bin/bash", "-c", "<executable>", "<param1>"]
样例
shell
ENV WEB_SERVER_PACKAGE nginx-1.21.1.tar.gz
RUN cd ./src && tar -xf ${WEB_SERVER_PACKAGE}
nginx安装实战
- nginx是源码所以我们需要先解压src文件,RUN命令可以完成nginx的解压
shell
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION=nginx-1.22.1
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
- 再次编译镜像v0.9,然后查看镜像 /usr/local/src 目录是否已经解压
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v0.9 .
[+] Building 4.3s (12/12) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 429B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/ubuntu:22.04
=> [internal] load build context
=> => transferring context: 71B
=> [1/6] FROM docker.io/library/ubuntu:22.04
=> CACHED [2/6] COPY index.html ${WEB_ROOT}
=> CACHED [3/6] WORKDIR /usr/local
=> CACHED [4/6] ADD https://nginx.org/download/nginx-1.22.1.tar.gz ./src
=> [5/6] RUN cd ./src && tar zxvf nginx-1.22.1.tar.gz
=> exporting to image
=> => exporting layers
=> => writing image sha256:946dd72dfd3e8106a52480a0b414bed6a69a14458581a46cc94f3e64bccbd
=> => naming to docker.io/library/web1:v0.9
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it --name web1 --rm web1:v0.9 ls -l /usr/local/src
total 1056
drwxr-xr-x 8 1001 1001 4096 Oct 19 08:02 nginx-1.22.1
-rw------- 1 root root 1073948 Oct 19 09:23 nginx-1.22.1.tar.gz
- 源码安装所以需要编译安装nginx,需要下载编译工具,已经依赖库信息,并通过RUN来完成安装,再次修改dockerfile
shell
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/data/web/www/
ENV NGINX_VERSION=nginx-1.22.1
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.安装依赖
#2.安装编译工具
#3.进入nginx目录
RUN apt-get update && apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev zlib1g openssl libssl-dev \
&& cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
- 再次构建,然后查看构建的nginx是否成功生成
shell
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker build -t web1:v1.0 .
root@139-159-150-152:/data/mydocker/dockerfile/web1# docker run -it --name web1 --rm web1:v1.0 /usr/local/nginx/sbin/nginx -v
nginx version: nginx/1.22.1
built by gcc 11.3.0 (Ubuntu 11.3.0-1ubuntu1~22.04)
configure arguments: --prefix=/usr/local/nginx
- nginx的默认配置文件为 /usr/local/nginx/conf/nginx.conf,我们修改server部分,配置自己的配置文件,然后覆盖
shell
user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root /data/web/www;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
}
- 可以看到包的编译特别耗时,此时 Dockerfile 如下:
shell
#helloweb站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/web
ENV NGINX_VERSION=nginx-1.22.1
#1.安装依赖包
RUN apt-get update && apt install -y build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev
#2.下载源码
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz /src
dockerfile
ADD ${NGINX_VERSION}.tar.gz ./src2
解压
shell
RUN cd /src && tar xvf ${NGINX_VERSION}.tar.gz
- 进入 nginx 目录
- 执行编译和构建
shell
RUN cd /src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
目录结构如下:
drwxr-xr-x 2 root root 4096 Mar 14 14:47 .
drwxr-xr-x 1 root root 4096 Mar 14 14:47 ..
-rw-r--r-- 1 root root 790K Mar 14 14:31 nginx.conf
-rw-r--r-- 1 root root 187K Mar 14 14:47 nginx.conf.tar.gz
- 再构建镜像 v1.1
shell
docker build -t web1:v1.1 .
- 再次运行 nginx 检查是否正常运行
shell
docker run -d -p 80:80 --name web1 web1:v1.1
docker exec -it web1 /usr/local/nginx/sbin/nginx -v
nginx version: nginx/1.22.1
configure arguments: --prefix=/usr/local/nginx
CMD
功能
- 类似于 RUN 指令,CMD 指令也可用于运行任何命令或应用程序,不过,二者的运行时间点不同
- RUN 指令运行于镜像文件构建过程中,而 CMD 指令运行于基于 Dockerfile 构建出的新镜像文件启动一个容器时。
- CMD 指令的首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;CMD 指定的命令可以被 docker run 的命令行选项所覆盖
- 在 Dockerfile 中可以存在多个 CMD 指令,
但仅最后一个会生效
语法
shell
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 数组中要使用双引号,单引号会出错
样例
dockerfile
CMD ["/usr/bin/wc","--help"]
实战
- 因为 docker 是需要一个长时间后台运行的,让 nginx 进入前台运行,这就需要我们下面的 CMD 或者 ENTRYPOINT 来完成,我们先用 CMD 来配置,修改 Dockerfile 如下:
dockerfile
#helloweb站点 by maxhou
FROM ubuntu:22.04 as buildbase
LABEL maintainer="maxhou@bit.com"
ENV COMPAY="com.bit"
ENV WEB_ROOT=/web
ENV NGINX_VERSION=nginx-1.22.1
#1.安装依赖包
RUN apt-get update && apt install -y build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev
#2.下载源码
WORKDIR /usr/local
COPY index.html ${WEB_ROOT}/
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz /src
dockerfile
ADD ${NGINX_VERSION}.tar.gz ./src2
解压
shell
RUN cd /src && tar xvf ${NGINX_VERSION}.tar.gz
- 进入 nginx 目录
- 执行编译和构建
shell
RUN cd /src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
dockerfile
COPY nginx.conf /nginx/conf/nginx.conf
CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
- 再编译 v1.2 版本
shell
docker build -t web1:v1.2 .
- 我们再次运行,然后 docker ps 可以看到容器已经在运行了
shell
docker run --name web1 -d -p 80:80 web1:v1.2
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
eb88a30e4dea web1:v1.2 "/usr/local/nginx/sbin/..." 3 seconds ago Up 2 seconds 80/tcp, 0.0.0.0:80->80/tcp web1
EXPOSE
功能
- 用于为容器打开指定要监听的端口以实现与外部通信
- 该 EXPOSE 指令实际上并不发布端口。它充当构建镜像的人和运行容器的人之间的一种文档,关于要发布哪些端口或接口。要在运行容器时实际发布端口,使用
-p参数发布端口到主机端口。
语法
dockerfile
EXPOSE <port> [<port>/<protocol>...]
参数
<protocol>: tcp/udp 协议<port>: 端口
样例
dockerfile
EXPOSE 80/tcp
实战
- 通过外部的浏览器访问,发现无法访问,是因为端口还没对外开放
- 通过 EXPOSE 暴露端口看下,调整 Dockerfile 如下:
shell
#helloweb站点 by drw
FROM ubuntu:22.04 as buildbase
ENV NGINX_VERSION=nginx-1.22.1
#1.安装依赖包
RUN apt-get update && apt install -y build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev
#2.下载源码
COPY index.html /usr/
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz /src
dockerfile
ADD ${NGINX_VERSION}.tar.gz ./src2
解压
shell
RUN cd /src && tar xvf ${NGINX_VERSION}.tar.gz
#3.进入 nginx 目录
#4.执行编译和构建
shell
RUN cd /src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
dockerfile
COPY nginx.conf /nginx/conf/nginx.conf
EXPOSE 80/tcp
CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
- 此时构建 v1.3 版本
shell
docker build -t web1:v1.3 .
- 再次运行,通过浏览器访问,不过要先停止之前的容器
shell
docker stop web1
docker rm web1
docker run --name web1 -d web1:v1.3
docker ps
- 再次通过 docker ps 查看容器正常运行,但是通过浏览器访问还是不行,说明 EXPOSE 仅仅是声明,并没有起作用
ENTRYPOINT
功能
- 用于指定容器的启动入口
语法
shell
ENTRYPOINT ["executable", "param1", "param2"] (exec form, preferred)
ENTRYPOINT command param1 param2 (shell form)
参数
- json 数组中,要使用双引号,单引号会出错
样例
dockerfile
ENTRYPOINT ["nginx", "-g", "daemon off;"]
实战
- 我们将 CMD 调整为 EntryPoint 重新测试下,此时 Dockerfile 如下:
shell
#helloweb站点 by drw
FROM ubuntu:22.04 as buildbase
ENV NGINX_VERSION=nginx-1.22.1
#1.安装依赖包
RUN apt-get update && apt install -y build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev
#2.下载源码
COPY index.html /usr/
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz /src
ADD ${NGINX_VERSION}.tar.gz ./src2
RUN cd /src && tar xvf ${NGINX_VERSION}.tar.gz
#3.进入 nginx 目录
#4.执行编译和构建
RUN cd /src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
COPY nginx.conf /nginx/conf/nginx.conf
EXPOSE 80/tcp
#CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
ENTRYPOINT ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
- 再次构建,运行我们的镜像
shell
docker build -t web1:v1.4 .
docker stop web1
docker rm web1
docker run --name web1 -d -p 80:80 web1:v1.4
- 此时通过浏览器可以再次访问,发现可以正常访问
Hello, My Home Page! by bit
核心区别:
- CMD:提供容器启动的默认命令或参数,可以被 docker run 后面的命令行参数直接覆盖。
- ENTRYPOINT:指定容器启动的固定命令,不会被 docker run 的参数覆盖,而是将这些参数作为额外输入传递给 ENTRYPOINT。
ARG
功能
- ARG 指令类似 ENV,定义了一个变量;区别于 ENV,用户可以在构建时
docker build --build-arg <varname>=<value>进行对变量的修改;ENV 不可以。 - docker build 指令中未在 Dockerfile 中定义的构建参数,那么构建输出警告。
语法
dockerfile
ARG <name>[=<default value>]
注意事项
-
Dockerfile 可以包含一个或多个 ARG 指令
-
ARG 支持指定默认值
-
使用 ARG 定义之后才能使用,定义之前的为空,如下的实例,执行命令
docker build --build-arg username=someuser .,结果为someuser而不是指定的build-arg中的参数dockerfileFROM busybox ARG username RUN echo "user is ${username}" ARG username=someuser -
ENV 和 ARG 同时存在,ENV 会覆盖 ARG
dockerfile
FROM ubuntu
ARG UBUNTU_VER
ENV CON_IMG_VER=$UBUNTU_VER
RUN echo $CON_IMG_VER
执行下面指令:
shell
docker build --build-arg UBUNTU_VER=v1.0.0 .
我们可以优化写法:
dockerfile
FROM ubuntu
ARG CON_IMG_VER=v1.0.0
ENV CON_IMG_VER=$CON_IMG_VER
RUN echo $CON_IMG_VER
系统内置了一些 ARG 变量
- HTTP_PROXY
- http_proxy
- HTTPS_PROXY
- https_proxy
- FTP_PROXY
- ftp_proxy
- NO_PROXY
- no_proxy
- ALL_PROXY
- all_proxy
实战
- 把基础镜像升级到 22.10, ARG 就排上用场了
- 定义一个 ARG 变量指定操作系统版本,修改后的 dockerfile 如下:
shell
#helloweb站点 by drw
ARG UBUNTU_VER=22.04
FROM ubuntu:${UBUNTU_VER} as buildbase
ENV NGINX_VERSION=nginx-1.22.1
#1.安装依赖包
RUN apt-get update && apt install -y build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev
#2.下载源码
COPY index.html /usr/
WORKDIR /usr/local
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz /src
ADD ${NGINX_VERSION}.tar.gz ./src2
RUN cd /src && tar xvf ${NGINX_VERSION}.tar.gz
#3.进入 nginx 目录
#4.执行编译和构建
RUN cd /src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
COPY nginx.conf /nginx/conf/nginx.conf
EXPOSE 80/tcp
ENTRYPOINT ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
- 再次构建 v1.5 版本的镜像,指定 ARG 参数,可以看到镜像重新构建
shell
docker build --build-arg UBUNTU_VER=22.10 -t web1:v1.5 .
- 再次运行 v1.5 镜像可以看到容器正常运行
shell
docker stop web1
docker rm web1
docker run --name web1 -d -p 80:80 web1:v1.5
VOLUME
功能
- 用于在 image 中创建一个挂载点目录
- 通过 VOLUME 指令创建的挂载点,无法指定主机上对应的目录,是自动生成的。
语法
dockerfile
VOLUME ["<mountpoint>"]
VOLUME <mountpoint>
参数
<mountpoint>: 挂载点目录
注意事项
- 如果挂载点目录路径下的文件/文件夹存在,docker run 命令会将其拷贝到挂载卷中
docker run -v会覆盖 Dockerfile 中定义的 VOLUME,但是并没有将 VOLUME 中的内容拷贝到宿主机中- 容器中如果在 VOLUME 目录中写数据,那么容器删除后,数据依然保留在宿主机的挂载目录中,可用于持久化数据
样例
dockerfile
VOLUME /data
实战
- 我们创建一个 Dockerfile,如下指定一个卷
dockerfile
FROM busybox
RUN mkdir /data && echo "hello world" > /data/myvolume.txt
CMD ["tail","-f","/dev/null"]
- 构建镜像
shell
docker build -t volume:v0.1 .
- 启动容器
shell
docker run -d --name myvolume volume:v0.1
- 查看卷信息
shell
docker inspect myvolume
输出片段:
json
"Mounts": [
{
"Type": "volume",
"Name": "1b0b6a1a5f39dacc68acb03d144d67a90f853e8b42d760415faccf4c9a4b83fd",
"Source": "/data/var/lib/docker/volumes/1b0b6a1a5f39dacc68acb03d144d67a90f853e8b42d760415faccf4c9a4b83fd/_data",
"Destination": "/data",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
]
- 可以看到挂载卷目录,进入目录可以看到文件
shell
cd /data/var/lib/docker/volumes/1b0b6a1a5f39dacc68acb03d144d67a90f853e8b42d760415faccf4c9a4b83fd/_data
ls
myvolume.txt
cat myvolume.txt
hello world
- 删除容器,查看文件是没有被删除的
shell
docker stop myvolume
docker rm myvolume
ls /data/var/lib/docker/volumes/1b0b6a1a5f39dacc68acb03d144d67a90f853e8b42d760415faccf4c9a4b83fd/_data
myvolume.txt
SHELL
功能
- SHELL 指令允许覆盖用于 shell 命令形式的默认 shell 。
- Linux 上的默认 shell 是
["/bin/sh", "-c"],在 Windows 上是["cmd", "/S", "/C"]
- Linux 上的默认 shell 是
- SHELL 指令必须以 JSON 格式写入 Dockerfile。
语法
dockerfile
SHELL ["executable", "parameters"]
参数
executable: shell 可执行文件的位置parameters: shell 执行的参数
注意事项
- SHELL 指令可以多次出现
- 每个 SHELL 指令都会覆盖所有先前的 SHELL 指令,并影响所有后续指令
- 该 SHELL 指令在 Windows 上特别有用,因为 windows 行有两种不同的 shell: cmd 和 powershell
样例
dockerfile
FROM microsoft/windowsservercore
# Executed as cmd /S /C echo default
RUN echo default
# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default
# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello
# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S", "/C"]
RUN echo hello
实战
- 我们可以在构建的时候切换使用不同的 shell
- 新建一个目录
shell
mkdir -p /data/myworkdir/dockerfile/shell
- 新建 Dockerfile,vi Dockerfile
dockerfile
FROM ubuntu:22.04
RUN ls -l / > /test1.txt
SHELL ["/bin/bash", "-c"]
RUN ls -l / > /test2.txt
- 构建镜像
shell
docker build -t shell:v0.1 --no-cache --progress=plain .
- 运行查看结果
shell
docker run -it --rm shell:v0.1 cat /test1.txt
docker run -it --rm shell:v0.1 cat /test2.txt
USER
功能
- 用于指定运行 image 时的或运行 Dockerfile 中任何 RUN、CMD 或 ENTRYPOINT 指令指定的程序时的用户名或 UID
- 默认情况下,container 的运行身份为 root 用户
语法
dockerfile
USER <user>[:<group>]
USER <UID>[:<GID>]
参数
user: 用户group: 用户组uid: 用户 idgid: 组 id
注意事项
- UID 可以为任意数字,但必须为
/etc/passwd中某用户的有效 UID,否则运行失败
样例
dockerfile
USER docker:docker
实战
- USER 用于指定后续命令的运行用户,建议不要直接用 root 用户操作
- 我们创建一个目录
shell
mkdir -p /data/myworkdir/dockerfile/user
- 创建 Dockerfile,添加以下内容
dockerfile
FROM ubuntu:22.04
RUN groupadd nginx
RUN useradd -r -g nginx nginx
USER nginx
RUN whoami > /tmp/user1.txt
USER root:root
RUN whoami > /tmp/user2.txt
USER mysql
RUN useradd mysql -g mysql
USER mysql
RUN whoami > /tmp/user3.txt
- 执行编译
shell
docker build -t user:v0.1 .
- 查看用户
shell
docker run -it --rm user:v0.1 cat /tmp/user1.txt
nginx
docker run -it --rm user:v0.1 cat /tmp/user2.txt
root
docker run -it --rm user:v0.1 cat /tmp/user3.txt
mysql
HEALTHCHECK
功能
- HEALTHCHECK 指令告诉 Docker 如何测试容器以检查它是否仍在工作。
- 即使 Web 服务器进程仍在运行,也可以检测出陷入无限循环且无法处理新连接的 Web 服务器等情况。
语法
dockerfile
HEALTHCHECK [OPTIONS] CMD command (check container health by running a command inside the container)
HEALTHCHECK NONE (disable any healthcheck inherited from the base image)
参数
- OPTIONS 选项有:
--interval=DURATION(default: 30s): 每隔多长时间探测一次,默认 30 秒--timeout=DURATION(default: 30s): 服务响应超时时长,默认 30 秒--start-period=DURATION(default: 0s): 服务启动多久后开始探测,默认 0 秒--retries=N(default: 3): 认为检测失败几次为宕机,默认 3 次
- 返回值
- 0: 容器成功是健康的,随时可以使用
- 1: 不健康的容器无法正常工作
- 2: 保留不使用此退出代码
样例
dockerfile
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
实战
- 创建目录
shell
mkdir -p /data/myworkdir/dockerfile/healthcheck
- 拉取一个 nginx 镜像,然后安装 curl,首先检查 80 端口,编辑 Dockerfile 如下
dockerfile
FROM nginx:1.22.1
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
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/ || exit 1
- 构建镜像,然后运行
shell
docker build -t healthcheck:v0.1 .
docker run -d --name hc1 healthcheck:v0.1
- docker ps 可以看到镜像成功运行,因为 nginx 默认是 80 端口
shell
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4eb4760a8235 healthcheck:v0.1 "/docker-entrypoint...." 5 seconds ago Up 4 seconds (healthy) 80/tcp hc1
- 通过 docker inspect 我们可以看到我们配置的参数
shell
docker inspect hc1
输出片段:
json
"Healthcheck": {
"Test": [
"CMD-SHELL",
"curl -fs http://localhost/ || exit 1"
],
"Interval": 5000000000,
"Timeout": 3000000000,
}
- 调整 Dockerfile 端口为 10080,再次运行镜像
dockerfile
FROM nginx:1.22.1
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
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:10080/ || exit 1
- 再构建镜像 v0.2,停止第一个镜像
shell
docker build -t healthcheck:v0.2 .
docker stop hc1
docker rm hc1
docker run -d --name hc2 healthcheck:v0.2
- 查看 docker ps 可以看到显示不健康
shell
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c731d080a82f healthcheck:v0.2 "/docker-entrypoint...." 2 minutes ago Up 2 minutes (unhealthy) 80/tcp hc2
- 通过 docker inspect 查看可以看到已经发生了多次的失败
shell
docker inspect hc2
输出片段:
json
"Health": {
"Status": "unhealthy",
"FailingStreak": 11,
"Log": [
{
"Start": "2023-03-14T11:52:47.242951421+08:00",
"End": "2023-03-14T11:52:47.473606451+08:00",
"ExitCode": 1,
"Output": ""
}
]
}
- 删除容器释放空间
shell
docker stop hc2
docker rm hc2
ONBUILD
功能
- 用于在 Dockerfile 中定义一个触发器
- 以该 Dockerfile 中的作为基础镜像的 FROM 指令在 build 过程中被执行时,将会"触发"创建其 base image 的 Dockerfile 文件中的 ONBUILD 指令定义的触发器
语法
dockerfile
ONBUILD <INSTRUCTION>
参数
INSTRUCTION: dockerfile 的一条指令
样例
dockerfile
ONBUILD ADD . /app/src
实战
- 创建目录
shell
mkdir -p /data/myworkdir/dockerfile/build
- 在里面创建 Dockerfile1 构建第一个基础镜像,设置 ONBUILD 的时候写入一次文件,Dockerfile1 内容如下
dockerfile
FROM ubuntu:22.04
LABEL version="0.1"
ONBUILD RUN echo "in build" >> /tmp/build.txt
- 构建作为基础镜像
shell
docker build -t build:v0.1 -f Dockerfile1 .
- 使用 build:v0.1 作为基础镜像,新建一个 Dockerfile2,来配置构建 build:v0.2 的镜像,Dockerfile2 的内容如下
dockerfile
FROM build:v0.1
LABEL version="0.2"
- 构建 v0.2 镜像,可以看到我们在 v0.1 中设置的钩子自动执行了
shell
docker build -t build:v0.2 -f Dockerfile2 .
STOPSIGNAL
功能
- STOPSIGNAL 指令设置将发送到容器的系统调用信号。
- 此信号可以是与内核的系统调用表中的位置匹配的有效无符号数,例如 9,或者 SIGNAME 格式的信号名,例如 SIGKILL。
语法
dockerfile
STOPSIGNAL signal
参数
signal: 信号名或数字
常见信号:
| 代号 | 名称 | 内容 |
|---|---|---|
| 1 | SIGHUP | 启动被终止的程序,可让该进程重新读取自己的配置文件,类似重新启动。 |
| 2 | SIGINT | 相当于用键盘输入 [ctrl]-c 来中断一个程序的进行。 |
| 9 | SIGKILL | 代表强制中断一个程序的进行,如果该程序进行到一半,那么尚未完成的部分可能会有"半产品"产生,类似 vim 会有 .filename.swp 保留下来。 |
| 15 | SIGTERM | 以正常的方式来终止该程序。由于是正常的终止,所以后续的动作会将他完成。不过,如果该程序已经发生问题,就是无法使用正常的方法终止时,输入这个 signal 也是没有用的。 |
| 19 | SIGSTOP | 相当于用键盘输入 [ctrl]-z 来暂停一个程序的进行。 |
样例
dockerfile
STOPSIGNAL 9
实战
- 创建目录
shell
mkdir -p /data/myworkdir/dockerfile/ss
- 编辑 Dockerfile 如下
dockerfile
FROM nginx:1.22.1
STOPSIGNAL 9
ENTRYPOINT ["nginx","-g","daemon off;"]
- 执行镜像构建
shell
docker build -t stopsignal:v0.1 .
- 运行镜像
shell
docker run --name stopsignal1 --rm -d stopsignal:v0.1
- 通过 docker ps 查看
shell
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9e9d645b604 stopsignal:v0.1 "nginx -g daemon of..." 3 seconds ago Up 2 seconds 80/tcp stopsignal1
- 打开另外一个 shell 窗口 B,执行
docker logs -f查看日志
shell
docker logs -f stopsignal1
2023/03/14 11:33:09 [notice] 1#1: nginx/1.22.1
2023/03/14 11:33:09 [notice] 1#1: using the epoll event method
2023/03/14 11:33:09 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2023/03/14 11:33:09 [notice] 1#1: OS: Linux 5.4.0-100-generic
2023/03/14 11:33:09 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1024:1024
2023/03/14 11:33:09 [notice] 1#1: start worker processes
2023/03/14 11:33:09 [notice] 1#1: start worker process 6
- 在原来的 shell 窗口中执行
docker stop,然后查看日志是突然退出
shell
docker stop stopsignal1
日志窗口无任何正常退出信息,进程直接消失。
- 再创建一个 Dockerfile2,不配置停止信号
dockerfile
FROM nginx:1.22.1
ENTRYPOINT ["nginx","-g","daemon off;"]
- 构建然后运行容器
shell
docker build -t stopsignal:v0.2 .
docker run --name stopsignal2 --rm -d stopsignal:v0.2
- 在 shell 窗口 B 中执行
docker logs查看日志
shell
docker logs -f stopsignal2
2023/03/14 11:38:53 [notice] 1#1: nginx/1.22.1
2023/03/14 11:38:53 [notice] 1#1: using the epoll event method
2023/03/14 11:38:53 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2023/03/14 11:38:53 [notice] 1#1: OS: Linux 5.4.0-100-generic
2023/03/14 11:38:53 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1024:1024
2023/03/14 11:38:53 [notice] 1#1: start worker processes
2023/03/14 11:38:53 [notice] 1#1: start worker process 6
2023/03/14 11:39:45 [notice] 1#1: signal 3 (SIGQUIT) received, shutting down
2023/03/14 11:39:46 [notice] 6#6: gracefully shutting down
2023/03/14 11:39:46 [notice] 6#6: exiting
2023/03/14 11:39:46 [notice] 1#1: signal 17 (SIGCHLD) received
2023/03/14 11:39:46 [notice] 1#1: worker process 6 exited with code 0
2023/03/14 11:39:46 [notice] 1#1: exit
- 我们可以看到正常的退出 nginx 会打印优雅的退出,先退出子进程,再退出主进程,而不是强制退出信号,进程消失没有任何反应。
制作命令 docker build
功能
docker build命令用于使用 Dockerfile 创建镜像。
语法
shell
docker build [OPTIONS] PATH | URL | -
关键参数
--build-arg: 设置镜像创建时的变量;-f: 指定要使用的 Dockerfile 路径;--label: 设置镜像使用的元数据;--no-cache: 创建镜像的过程不使用缓存;--pull: 尝试去更新镜像的新版本;--quiet, -q: 安静模式,成功后只输出镜像 ID;--tag, -t: 镜像的名字及标签,通常name:tag或者name格式;可以在一次构建中为一个镜像设置多个标签。--network: 默认default。在构建期间设置 RUN 指令的网络模式
PATH\URL-等参数都分别代表本地目录路径、远程资源地址、从标准输入读取Dockerfile内容,不发送本地上下文。
样例
shell
docker build -t mynginx:v1 .
Dockerfile 优秀编写应该具备以下几点
- 善用
.dockerignore文件
用它可以标记在执行docker build时忽略的路径和文件,避免发送不必要的数据内容。 - 镜像的多阶段构建
通过多步骤构建,可以将编译和运行过程分开,保证最终生成的镜像只包含运行应用所需要的最小化环境。用户也可以通过分别构建编译镜像和运行镜像来达到类似的结果,但这种方式需要维护多个 Dockerfile。 - 合理使用缓存,减少内容目录下的文件,内容不变的指令尽量放在前面,这样可以重复使用 cache
- 基础镜像尽量使用官方镜像,并选择体积较小镜像
容器的核心是应用,大的平台微服务可能几十上百个。选择过大的父镜像(如 Ubuntu 系统镜像)会造成最终生成应用镜像的臃肿,推荐选用 Busybox 或应用镜像(如nginx:alpine),或者更为小巧的系统镜像(如 alpine、busybox 或 debian),减少镜像层数 - 减少镜像层数
如果希望所生成镜像的层数尽量少,则要尽量将多个 RUN、ADD、COPY 指令合并到一行,多的 RUN 指令可以合并为一条 RUN 指令,如apt-get install && apt install尽量写到一行。 - 精简镜像用途
尽量让每个镜像的用途都比较集中单一,避免构造大而复杂、多功能的镜像。 - 减少外部源的干扰
如果确实要从外部引入数据,需要指定持久的地址,并带版本信息等,让他人可以复用而不出错。 - 减少不必要的包安装
只安装需要的包,不要安装无用的包,减少镜像体积。