Docker 镜像制作:包含自定义镜像及常用命令

Docker镜像制作

文章目录

镜像制作及原因

镜像制作是因为某种需求,官方的镜像无法满足需求,需要自定义镜像来满足要求:

往往因为以下原因自己制作镜像:

  1. 编写的代码如何打包到镜像中直接跟随镜像发布
  2. 第三方制作的内容安全性未知,如含有安全漏洞
  3. 特定的需求或者功能无法满足,如需要数据库,加审计功能
  4. 公司内部要求基于公司内部的系统制作镜像,如公司内部要求使用自己的操作系统作为基础镜像

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镜像制作
  1. 创建临时工作目录
shell 复制代码
mkdir -p /data/drw/commitimage
cd /data/drw/commitimage
  1. 编写C++源代码文件,demo.cpp
c 复制代码
#include <stdio.h>

int main()
{
    printf("hello docker!\n");
    return 0;
}
  1. 启动一个centos7的容器
shell 复制代码
root@139-159-150-152:/data/drw/commitimage# docker run -it --name mycppcommit centos:7 bash
[root@40de1bf45017 /]#
  1. 安装编译软件,并创建源代码目录
shell 复制代码
[root@40de1bf45017 /]# yum install -y gcc
[root@40de1bf45017 /]# mkdir /src
  1. 打开另外一个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
  1. 编译运行
shell 复制代码
[root@40de1bf45017 /]# cd /src
[root@40de1bf45017 src]# gcc demo.c -o demo
[root@40de1bf45017 src]# ./demo
hello docker!
  1. 提交为一个镜像
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
  1. 测试镜像能否正常运行
shell 复制代码
root@139-159-150-152:/data/maxhou/commitimage# docker run -it mycppimg:v1.0 /src/demo
hello docker!
实战二、Springboot镜像制作
  1. 启动一个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)
  1. 打开另外一个shell窗口,拷贝制作过的jar包到容器的目录
shell 复制代码
docker cp ./springboot-demo-1.0-SNAPSHOT.jar myjavacommit:/app.jar
  1. 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)
  1. 执行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
  1. 使用新创建的容器启动一个服务检查是否能够正常运行
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
  1. 通过浏览器访问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

实战

  1. 创建Docker目录,确保目录没有内容
shell 复制代码
mkdir -p /data/mydocker/dockerfile/web1
cd /data/mydocker/dockerfile/web1
  1. 编辑Dockerfile,测试FROM指令和注释,在web1目录中vi Dockerfile,输入以下内容
shell 复制代码
#我的web站点 by me
FROM ubuntu:22.04 as buildbase
  1. 执行构建,打造镜像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
  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>"

实战

  1. FROM添加制作者信息,使用MAINTAINER
shell 复制代码
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
MAINTAINER "drw"
  1. 再次编译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
  1. 查看镜像信息,可以看到作者信息已经添加完成
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"

实战

  1. 我们使用LABEL添加额外的元数据信息,继续vi Dockerfile
shell 复制代码
#我的web站点 by maxhou
FROM ubuntu:22.04 as buildbase
MAINTAINER "maxhou <maxhou@bit.com>"
LABEL company="com.bit" app="nginx"
  1. 我们继续构建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
  1. 查看镜像元数据
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/

实战

  1. 创建一个index.html,作为我们站点的首页,vi index.html输入下面内容
html 复制代码
<html>
    <h1>Hello My Home Page by bit</h1>
</html>
  1. 通过COPY命令添加到镜像中,并且指定我们的根目录为/data/web/www
shell 复制代码
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
COPY index.html /data/web/www/
  1. 再次编译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
  1. 运行镜像,可以看到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

实战

  1. 目录后面可能变化,将目录的位置提取为变量,通过ENV来设置,再次编辑Dockerfile
shell 复制代码
#我的web站点 by drw
FROM ubuntu:22.04 as buildbase
ENV WEB_ROOT=/data/web/www/
COPY index.html ${WEB_ROOT}
  1. 再次编译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
  1. 运行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
  1. 也可以通过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

实战

  1. 使用相对路径来执行下一个目录,再次编辑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
  1. 再次编译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
  1. 执行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:修改用户和组

实战

  1. 登录nginx官网https://nginx.org/,找到最新稳定版本的nginx的下载地址**https://nginx.org/download/nginx-1.22.1.tar.gz .**

  2. 通过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
  1. 执行编译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
  1. 运行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
  1. 此时并没有被解压
  2. 手动下载下来这个压缩包,放到我们的服务器目录,此时目录的结构如下:
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
  1. 再次编辑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
  1. 执行命令编译v0.8
shell 复制代码
docker build -t web1:v0.8 .
  1. 运行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安装实战

  1. 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
  1. 再次编译镜像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
  1. 源码安装所以需要编译安装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
  1. 再次构建,然后查看构建的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
  1. 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;
        #}
    }
}
  1. 可以看到包的编译特别耗时,此时 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
  1. 进入 nginx 目录
  2. 执行编译和构建
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
  1. 再构建镜像 v1.1
shell 复制代码
docker build -t web1:v1.1 .
  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"]

实战

  1. 因为 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
  1. 进入 nginx 目录
  2. 执行编译和构建
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;"]
  1. 再编译 v1.2 版本
shell 复制代码
docker build -t web1:v1.2 .
  1. 我们再次运行,然后 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

实战

  1. 通过外部的浏览器访问,发现无法访问,是因为端口还没对外开放
  2. 通过 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;"]
  1. 此时构建 v1.3 版本
shell 复制代码
docker build -t web1:v1.3 .
  1. 再次运行,通过浏览器访问,不过要先停止之前的容器
shell 复制代码
docker stop web1
docker rm web1
docker run --name web1 -d web1:v1.3
docker ps
  1. 再次通过 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;"]

实战

  1. 我们将 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;"]
  1. 再次构建,运行我们的镜像
shell 复制代码
docker build -t web1:v1.4 .
docker stop web1
docker rm web1
docker run --name web1 -d -p 80:80 web1:v1.4
  1. 此时通过浏览器可以再次访问,发现可以正常访问
    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 中的参数

    dockerfile 复制代码
    FROM 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

实战

  1. 把基础镜像升级到 22.10, ARG 就排上用场了
  2. 定义一个 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;"]
  1. 再次构建 v1.5 版本的镜像,指定 ARG 参数,可以看到镜像重新构建
shell 复制代码
docker build --build-arg UBUNTU_VER=22.10 -t web1:v1.5 .
  1. 再次运行 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

实战

  1. 我们创建一个 Dockerfile,如下指定一个卷
dockerfile 复制代码
FROM busybox
RUN mkdir /data && echo "hello world" > /data/myvolume.txt
CMD ["tail","-f","/dev/null"]
  1. 构建镜像
shell 复制代码
docker build -t volume:v0.1 .
  1. 启动容器
shell 复制代码
docker run -d --name myvolume volume:v0.1
  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": ""
    }
]
  1. 可以看到挂载卷目录,进入目录可以看到文件
shell 复制代码
cd /data/var/lib/docker/volumes/1b0b6a1a5f39dacc68acb03d144d67a90f853e8b42d760415faccf4c9a4b83fd/_data
ls
myvolume.txt
cat myvolume.txt
hello world
  1. 删除容器,查看文件是没有被删除的
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"]
  • 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

实战

  1. 我们可以在构建的时候切换使用不同的 shell
  2. 新建一个目录
shell 复制代码
mkdir -p /data/myworkdir/dockerfile/shell
  1. 新建 Dockerfile,vi Dockerfile
dockerfile 复制代码
FROM ubuntu:22.04
RUN ls -l / > /test1.txt
SHELL ["/bin/bash", "-c"]
RUN ls -l / > /test2.txt
  1. 构建镜像
shell 复制代码
docker build -t shell:v0.1 --no-cache --progress=plain .
  1. 运行查看结果
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: 用户 id
  • gid: 组 id

注意事项

  • UID 可以为任意数字,但必须为 /etc/passwd 中某用户的有效 UID,否则运行失败

样例

dockerfile 复制代码
USER docker:docker

实战

  1. USER 用于指定后续命令的运行用户,建议不要直接用 root 用户操作
  2. 我们创建一个目录
shell 复制代码
mkdir -p /data/myworkdir/dockerfile/user
  1. 创建 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
  1. 执行编译
shell 复制代码
docker build -t user:v0.1 .
  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

实战

  1. 创建目录
shell 复制代码
mkdir -p /data/myworkdir/dockerfile/healthcheck
  1. 拉取一个 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
  1. 构建镜像,然后运行
shell 复制代码
docker build -t healthcheck:v0.1 .
docker run -d --name hc1 healthcheck:v0.1
  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
  1. 通过 docker inspect 我们可以看到我们配置的参数
shell 复制代码
docker inspect hc1

输出片段:

json 复制代码
"Healthcheck": {
    "Test": [
        "CMD-SHELL",
        "curl -fs http://localhost/ || exit 1"
    ],
    "Interval": 5000000000,
    "Timeout": 3000000000,
}
  1. 调整 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
  1. 再构建镜像 v0.2,停止第一个镜像
shell 复制代码
docker build -t healthcheck:v0.2 .
docker stop hc1
docker rm hc1
docker run -d --name hc2 healthcheck:v0.2
  1. 查看 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
  1. 通过 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": ""
        }
    ]
}
  1. 删除容器释放空间
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

实战

  1. 创建目录
shell 复制代码
mkdir -p /data/myworkdir/dockerfile/build
  1. 在里面创建 Dockerfile1 构建第一个基础镜像,设置 ONBUILD 的时候写入一次文件,Dockerfile1 内容如下
dockerfile 复制代码
FROM ubuntu:22.04
LABEL version="0.1"
ONBUILD RUN echo "in build" >> /tmp/build.txt
  1. 构建作为基础镜像
shell 复制代码
docker build -t build:v0.1 -f Dockerfile1 .
  1. 使用 build:v0.1 作为基础镜像,新建一个 Dockerfile2,来配置构建 build:v0.2 的镜像,Dockerfile2 的内容如下
dockerfile 复制代码
FROM build:v0.1
LABEL version="0.2"
  1. 构建 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

实战

  1. 创建目录
shell 复制代码
mkdir -p /data/myworkdir/dockerfile/ss
  1. 编辑 Dockerfile 如下
dockerfile 复制代码
FROM nginx:1.22.1
STOPSIGNAL 9
ENTRYPOINT ["nginx","-g","daemon off;"]
  1. 执行镜像构建
shell 复制代码
docker build -t stopsignal:v0.1 .
  1. 运行镜像
shell 复制代码
docker run --name stopsignal1 --rm -d stopsignal:v0.1
  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
  1. 打开另外一个 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
  1. 在原来的 shell 窗口中执行 docker stop,然后查看日志是突然退出
shell 复制代码
docker stop stopsignal1

日志窗口无任何正常退出信息,进程直接消失。

  1. 再创建一个 Dockerfile2,不配置停止信号
dockerfile 复制代码
FROM nginx:1.22.1
ENTRYPOINT ["nginx","-g","daemon off;"]
  1. 构建然后运行容器
shell 复制代码
docker build -t stopsignal:v0.2 .
docker run --name stopsignal2 --rm -d stopsignal:v0.2
  1. 在 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
  1. 我们可以看到正常的退出 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 优秀编写应该具备以下几点

  1. 善用 .dockerignore 文件
    用它可以标记在执行 docker build 时忽略的路径和文件,避免发送不必要的数据内容。
  2. 镜像的多阶段构建
    通过多步骤构建,可以将编译和运行过程分开,保证最终生成的镜像只包含运行应用所需要的最小化环境。用户也可以通过分别构建编译镜像和运行镜像来达到类似的结果,但这种方式需要维护多个 Dockerfile。
  3. 合理使用缓存,减少内容目录下的文件,内容不变的指令尽量放在前面,这样可以重复使用 cache
  4. 基础镜像尽量使用官方镜像,并选择体积较小镜像
    容器的核心是应用,大的平台微服务可能几十上百个。选择过大的父镜像(如 Ubuntu 系统镜像)会造成最终生成应用镜像的臃肿,推荐选用 Busybox 或应用镜像(如 nginx:alpine),或者更为小巧的系统镜像(如 alpine、busybox 或 debian),减少镜像层数
  5. 减少镜像层数
    如果希望所生成镜像的层数尽量少,则要尽量将多个 RUN、ADD、COPY 指令合并到一行,多的 RUN 指令可以合并为一条 RUN 指令,如 apt-get install && apt install 尽量写到一行。
  6. 精简镜像用途
    尽量让每个镜像的用途都比较集中单一,避免构造大而复杂、多功能的镜像。
  7. 减少外部源的干扰
    如果确实要从外部引入数据,需要指定持久的地址,并带版本信息等,让他人可以复用而不出错。
  8. 减少不必要的包安装
    只安装需要的包,不要安装无用的包,减少镜像体积。
相关推荐
源远流长jerry2 小时前
Linux 网络性能优化:从应用到内核
linux·运维·服务器·网络·网络协议·性能优化
goyeer2 小时前
【ITIL】指导原则
linux·运维·服务器·数字化·itil
苍煜2 小时前
K8s 核心资源详解(Pod/Deployment/Service 实战)
云原生·容器·kubernetes
江湖有缘2 小时前
容器化笔记:Memory应用在Docker环境下的部署与配置
笔记·docker·容器
yuanpan3 小时前
Python + psutil 实战:开发一个简易系统监控工具
linux·运维·python
苍煜3 小时前
Docker Compose 多容器编排实战(系列第五篇:开发环境一键部署)
运维·docker·容器
坚持就完事了3 小时前
Linux的ln命令
linux·运维·服务器
鹿角片ljp3 小时前
实验室显卡与本机远程连接复盘:直连SSH到ZeroTier
运维·ssh
sbjdhjd3 小时前
企业级 Docker 镜像仓库建设与运维规范
linux·运维·docker·云原生·容器·eureka·开源