我是基于ChatGPT-turbo-3.5实现的AI助手,在此网站上负责整理和概括文章

本文介绍了Dockerfile作为定义Docker镜像构建过程的文本文件,包含一系列指令(如FROM、RUN、COPY等),通过标准化方式实现镜像的可重复构建。文章详细阐述了Dockerfile的指令格式、构建镜像流程、配置指令与操作指令的分类及用法,并提供了优化建议,如多阶段构建、使用轻量级基础镜像和非root用户运行,以减小镜像体积和提升安全性,具有较高的实用价值。

Dockerfile 是用于定义 Docker 镜像构建过程的文本文件,包含一系列指令(Instructions),指导 Docker 如何自动构建镜像。它是 Docker 生态中的核心组件之一,通过代码化的方式实现镜像的标准化和可重复构建。

# 指令格式

  1. 指令不区分大小写,但推荐大写(约定俗成,提高可读性):

    FROM ubuntu:22.04  # 推荐
    from ubuntu:22.04  # 有效但不推荐
  2. 每条指令占一行,格式为:

    INSTRUCTION arguments
    
    # 例如:
    RUN apt-get update
    COPY ./app /app
  3. 注释: 使用 # 开头:

    # 这是一个注释
    FROM alpine:3.14
  4. 换行与续行: 使用 \ 分割长命令:

    RUN apt-get update && \
        apt-get install -y \
            curl \
            git

# 构建镜像流程

  1. 从基础镜像启动临时容器
    • Docker 根据 FROM 指令指定的基础镜像(如 ubuntu:22.04)创建一个临时容器。
    • 此时容器文件系统是基础镜像的只读层
  2. 执行指令并修改容器
    • 按顺序执行 Dockerfile 中的指令(如 RUNCOPY),在临时容器中进行操作(安装软件、复制文件等)。
    • 所有修改发生在容器的可写层(容器层,Container Layer)。
  3. 提交为新的镜像层
    • 当前指令执行完成后,Docker 会将容器的可写层提交为一个新的只读镜像层(Layer)。
    • 该层仅包含当前指令引起的变更(如新增的文件或配置)。
    • 类似 docker commit,但完全自动化,无需手动操作。
  4. 基于新镜像层启动下一个临时容器
    • Docker 销毁上一个临时容器,基于刚提交的新镜像层启动一个新的临时容器。
    • 新容器继承之前所有层的修改。
  5. 重复执行直到所有指令完成
    • 循环执行步骤 2~4,直到 Dockerfile 中所有指令处理完毕。
    • 最终生成一个完整的镜像,包含所有叠加的只读层和一个可选的 CMD/ENTRYPOINT

# DockerFile指令

dockerfile指令包括配置指令(配置镜像信息)和操作指令(具体执行操作)两部分

官方文档:https://docs.docker.com/engine/reference/builder/

分类 指令 类型 作用 示例
配置指令 FROM 基础配置 指定基础镜像(必须为第一条指令) FROM ubuntu:22.04
LABEL 元数据 添加镜像的元信息(如作者、版本) LABEL maintainer="[email protected]"
ENV 环境变量 设置容器内的环境变量(运行时可用) ENV NODE_ENV=production
ARG 构建变量 定义仅在构建时有效的变量 ARG APP_VERSION=1.0
WORKDIR 工作目录 设置后续指令的工作目录(类似 cd WORKDIR /app
USER 运行用户 指定运行命令的用户(避免使用 root USER node
EXPOSE 端口声明 声明容器运行时监听的端口(需配合 -p 映射) EXPOSE 8080
VOLUME 数据卷 定义挂载点(用于持久化数据) VOLUME /data
操作指令 RUN 命令执行 在构建时执行命令(安装软件、配置环境等) RUN apt-get update && apt-get install -y curl
COPY 文件复制 复制本地文件到镜像(推荐) COPY ./src /app/src
ADD 高级复制 类似 COPY,但支持解压和远程 URL(谨慎使用) ADD https://example.com/file.tar.gz /tmp/
CMD 启动命令 指定容器启动时的默认命令(可被 docker run 覆盖) CMD ["npm", "start"]
ENTRYPOINT 入口命令 指定容器启动时的入口程序(通常与 CMD 配合) ENTRYPOINT ["/app/entry.sh"]
HEALTHCHECK 健康检查 定义容器健康状态检查命令 `HEALTHCHECK --interval=30s CMD curl -f http://localhost/ |

# 配置命令

  • 作用:定义镜像的元信息、运行时环境、默认配置等。
  • 不直接修改文件系统,而是影响镜像的元数据或后续指令的行为。
指令 说明 格式
ARG 定义创建镜像过程中使用的变量 ARG <name>[=<default value>]
FROM 指定所创建的基础镜像 FROM <image>:<tag> FROM <image>:<digest>
LABEL 为生成的镜像添加元数据标签信息 LABEL <key>=<value> <key>=<value> <key>=<value> ...
EXPOSE 声明镜像内服务监听的端口 EXPOSE <port> [<port>/<portocol>…]
ENV 指定环境变量 ENV <key> <value> ENV <key>=<value> ...
ENTRYPOINT 指定镜像的默认入口命令 ENTRYOINT ["executable","param1","param2"]:exec调用执行 ENTRYOINT command param1 param2:shell中执行
VOLUME 创建一个数据卷挂载点 VOLUME ["/data"]
USER 指定运行容器时的用户名或UID USER daemon
WORKDIR 配置工作目录 WORKDIR /path
ONBUILD 创建子镜像时指定自动执行的操作命令 ONBUILD [INSTRUCTION]
STOPSIGNAL 指定退出的信号值 STOPSIGNAL signal
HEALTHCHECK 配置所启动容器如何进行健康检查 HEALTHCHECK [OPTIONS] CMD command: 根据所执行命令返回值是否为0来判断; HEALTHCHECK NONE: 禁止基础镜像中的健康检查。
SHELL 指定默认shell类型 SHELL ["executable", "parameters"]

# FROM

  • 指定基础镜像 FROM <image>:<tag>

    FROM <image> [AS <name>]
    
    FROM <image>[:<tag>] [AS <name>]
    
    FROM <image>[@<digest>] [AS <name>]
  • FROM必须是第一条指令。如果在一个dockerfile中指定多个镜像时,使用多个FROM指令

  • 如果不以任何镜像为基础,那么写法为:

    FROM  scratch
  • 结合ARG定义

    ARG VERSION=9.3
    FROM debian:${VERSION}

# MAINTAINER

  • 指定维护者信息。该信息会写入生成镜像的Author属性域中。

  • 格式:

    MAINTAINER <name>
  • 根据官方文档,该指令已过时。应该使用 LABEL 指令代替,因为它更灵活。

    • LABEL指令添加元数据到镜像中。如果要使用包含有空格的元数据,可以给key-value加上引号。
    LABEL <key>=<value> <key>=<value> <key>=<value> ...
    
    LABEL maintainer="[email protected]"

# LABEL

  • 为镜像添加一些元数据(metadata),以键值对的形式 LABEL <key>=<value> <key>=<value> <key>=<value> ...

  • LABEL会继承基础镜像种的LABEL,如遇到key相同,则值覆盖

  • LABEL 值中包含空格,请像在命令行中一样使用引号和反斜杠

    # 比如我们可以添加镜像的作者:
    LABEL author=fulsun
  • 一个Dockerfile种可以有多个LABEL,可以在一行上指定多个标签,也可以多个label, 如下:

    # 您可以在一行上指定多个标签
    LABEL multi.label1="value1" multi.label2="value2" other="value3"
    
    LABEL multi.label1="value1" \
          multi.label2="value2" \
          other="value3"
    
    # 指定多个LABEL标签
    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."
  • 要查看镜像的标签,请使用 docker image inspect 命令。您可以使用 --format 选项仅显示标签;docker image inspect --format='' imageID

# EXPOSE

  • 声明镜像内服务监听的端口,默认协议是 TCP EXPOSE <port> [<port>/<portocol>…]

  • 使用EXPOSE指令并不会真的发布该端口(即监听宿主主机的相应端口)。为了真的发布该端口,需要在docke run时使用-p或-P参数指定发布端口。

    • 如果 docker run,指定了自动映射 -P,那么会将所有暴露的端口随机映射到宿主机的高阶端口

    • 如果 docker run,指定了 --net=host 宿主机网络模式,容器中 EXPOSE 指令暴露的端口会直接使用宿主机对应的端口,不存在映射关系

  • 如果 EXPOSE 暴露的端口确定要和某个宿主机端口建立映射关系,还是要用到 docker run -p 参数

    EXPOSE <port> [<port>/<protocol>...]
    
    # 例:
    EXPOSE 80
    EXPOSE 80/tcp
    EXPOSE 80/udp

# ENV

  • ENV 指令将环境变量 <key> 设置为值 <value>。这个值将在构建阶段的所有后续指令的环境中,

  • 该值将被解释为其他环境变量,因此如果引号字符没有转义,它们将被删除。

  • 像命令行解析一样,引号和反斜杠可以用于在值中包含空格。

    # 一次设置一个(后续会删除)
    ENV  <key> <value>
    # 一次设置多个
    ENV <key>=<value> ...
    
    ENV MY_NAME="John Doe"
    ENV MY_DOG=Rex\ The\ Dog
    ENV MY_CAT=fluffy
    
    ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
        MY_CAT=fluffy
  • 当使用生成的镜像运行容器时,使用 ENV 设置的环境变量将持久存在于容器内。

  • 你可以使用 docker inspect 查看这些值,并使用 docker run --env <key>=<value> 修改它们。

    docker run --env <key>=<value> image

# VOLUME

  • 定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。

    • 避免重要的数据,因容器重启而丢失,这是非常致命的。
    • 避免容器不断变大。
  • 格式:

    VOLUME ["<路径1>", "<路径2>"...]
    VOLUME <路径>
    
    VOLUME  ["/data"]
    
  • 在启动容器 docker run 的时候,我们可以通过 -v 参数修改挂载点。

# USER

  • 当服务不需要管理员权限时,可以通过该命令指定运行用户,并且可以在Dockerfile中创建所需要的用户。指定运行容器时的用户名或UID

    USER <user>[:<group>]
    
    USER <UID>[:<GID>]
    
    USER postgres
    
    # 其中用户名或ID是指可以在容器基础镜像中找到的用户。
    # 如果在容器基础镜像中没有创建特定用户,则在USER指令之前添加useradd命令以添加特定用户。
    RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
    # 要临时获取管理员权限可以使用gosu命令, gosu是个工具,用来提升指定账号的权限
  • 使用 USER 指定用户时,可以使用用户名、UID 或 GID,或是两者的组合

  • 在镜像构建阶段,Dockerfile中位于USER指令之后的RUN、CMD、ENTRYPOINT指令的执行将使用USER指定的用户名和用户组。

  • 也可以使用 docker run -u 指定用户 docker run -i -t -u 1001 busybox sh

  • 审计方式:返回容器用户名或用户ID。 如果为空,则表示容器以root身份运行

    [root@localhost ~]# docker ps |grep top |awk '{print $1}'|xargs -n1 docker inspect --format='<!--swig0-->:User=<!--swig1-->'
    4e53c86daf89a1bac0ed178d043663d2af162ca813ff17864ebdb964d8233459:User=

# WORKDIR

  • 该指令为后续的RUN, CMD, ENTRYPOINT, COPYADD 指令指定工作目录。

  • 格式:

    WORKDIR /path/to/workdir
  • WORKDIR指令可以解析它前面通过ENV设置的环境变量:

  • ENV DIRPATH /path
    WORKDIR $DIRPATH/$DIRNAME
    RUN pwd # 这里pwd输出为/path/$DIRNAME

# WORKDIR

  • 为后续的RUN、CMD、ENTRYPOINT指令配置工作目录,如果指定的工作目录不存在,将自动创建一个新目录。

  • 格式: 推荐WORKDIR指令中只使用绝对路径

    WORKDIR <工作目录路径>
    WORKDIR  /path/to/workdir
  • 一个Dockerfile中可以使用多个WORKDIR指令,根据需要不断切换工作目录。如果使用的路径是相对路径,则会相对于前一个工作目录进行计算

    WORKDIR /a
    WORKDIR b
    WORKDIR c
    RUN pwd # 则最终路径为 /a/b/c

# ARG

  • 该指令用于定义一些构建参数,用户可以在执行 docker build 命令时传入这些参数值。典型的,比如版本号。

  • 注意,ARG用于定于构建参数,准确点说应该叫BUILD_ARG。ARG只会存在于构建时,不会存在于容器启动时。

  • **ARG 设置的环境变量仅对 Dockerfile 内有效,构建好的镜像内不存在此环境变量。**因此,ARG不能用于CMD和ENTRYPOINT命令中。因为那个时候ARG已经不存在了。一个解决方案是,把ARG赋值给环境变量ENV,ENV是会保留到运行时的。

  • 格式:

    ARG <name>[=<default value>]
  • 构建命令 docker build 中可以用 --build-arg <参数名>=<值> 来覆盖。

    docker build --build-arg <varname>=<value>
  • 如果指定的参数不存在,则会参数如下警告信息:

    [Warning] One or more build-args [foo] were not consumed.
  • 可以多次使用ARG指令,指定多个参数。

  • 注意:不要通过ARG指令传递敏感信息,因为传递的构建参数值是可以通过 docker history 命令看到的。

# ONBUILD

  • 该指令指定一些其他指令,这些指令将在当前镜像作为别的镜像的基础镜像时,在其构建阶段执行。

  • 格式:

    ONBUILD [INSTRUCTION]
  • 用ONBUILD指定的指令,在本镜像的构建过程中不会执行,而是存储在镜像中。当创建另一个Dockerfile,并且用FROM指令指定本镜像作为基础镜像时,在子镜像的构建阶段,读取到FROM指令时,Docker会寻找基础镜像的ONBUILD指令,并按顺序执行其指定的指令。

  • 注意ONBUILD指令只会影响直接继承的子镜像的构建过程,对于孙子镜像没有影响

  • 该指令常用于制作“执行环境”类型的镜像,且镜像名通常会添加“onbuild”后缀。

# STOPSIGNAL

  • 指定所创建的镜像启动的容器所接受的退出信号值。

  • 格式:

    STOPSIGNAL signal # signal可以为无符号整数,如9,或信号名,如SIGKILL
  • 该信号可以是与内核 syscall 表中的位置匹配的有效无符号数字(例如9),也可以是 SIGNAME 格式的信号名称(例如 SIGKILL)。

# HEALTHCHECK

  • 配置所启动容器如何进行健康检查(如何判断健康与否),自Docker 1.12开始支持。格式有两种:

    HEALTHCHECK [options] CMD command # 根据所执行命令返回值是否为0来判断;
    HEALTHCHECK NODE 禁止基础镜像中的健康检查。
  • OPTION支持如下参数:

    • -interval=DURATION (default: 30s): 过多久检查一次;
    • -timeout=DURATION (default: 30s): 每次检查等待结果的超时;
    • -retries=N (default: 3): 如果失败了,重试几次才最终确定失败。
    • --start-period=DURATION (default: 0s: 如果指定这个参数, 则必须大于 0s ;在这个时间段内如果检查失败, 则不会记录失败次数。 如果在启动时间内成功执行了健康检查, 则容器将被视为已经启动, 如果在启动时间内再次出现检查失败, 则会记录失败次数。
  • 一个Dockerfile中只能指定一个HEALTHCHECK指令。指定多个时只有最后一个生效。

# SHELL

  • 为后续的RUN、CMD、ENTRYPOINT的shell形式命令指定默认shell。

  • 格式:

    SHELL ["executable", "parameters"]
    # 默认值为["/bin/sh", "-c"]。

# 操作命令

作用:在镜像构建过程中执行具体操作(如复制文件、运行命令)。

指令 说明
RUN 运行指定命令
CMD 启动容器时指定默认执行的命令
ADD 将宿主机目录下的文件拷贝进镜像且ADD命令会自动处理URL和解压tar压缩包
COPY 类似ADD,拷贝文件和目录到镜像中(不会自解压)。 将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置COPY ["src", "dest"]
ENTRYPOINT 指定一个容器启动时要运行的命令,ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数

# RUN

  • 指定构建镜像时运行的指令。每条RUN指令都将在当前镜像基础上创建一个新层,执行指定命令,然后提交,从而产生一个新镜像。这个新镜像也将成为下一个RUN命令的的当前镜像。

  • 当命令较长时可以使用\来换行。

  • 有两种格式:

    RUN <command> 
    # shell形式。将在默认的shell终端中运行命令。在linux下是/bin/sh -c。
    # 命令太长时可用\换行
    
    RUN ["executable", "param1", "param2"] 
    # exec形式。使用exec执行,不会启动shell。
    # 注意这里数组将被解析为JSON数组,因此必须用双引号。
    
    # 例如
    RUN  ["/bin/bash", "-c","echo hello"]

# COPY

  • 该指令将指定的文件、目录下内容拷贝到镜像中的指定目录下,目标路径不存在时,会自动创建。

  • COPY指令只能复制当前构建中的文件(即当前Dockerfile所在目录下的文件)。

  • 格式:

    # 注意 src只能访问该上下文目录下及其子目录。
    # src: Dockerfile所在目录的一个相对路径
    # dest : 镜像内的绝对路径  / 相对于工作目录(WORKDIR指令指定)的相对路径
    COPY [--chown=<user>:<group>] <src>... <dest>
    COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
  • 示例

    COPY check* /testdir/           # 拷贝所有 check 开头的文件
    COPY check?.log /testdir/       # ? 是单个字符的占位符,比如匹配文件 check1.log
    
    # 拷贝到data文件夹
    COPY data.tar ./data

# ADD

  • 该指令将指定的文件、目录或URL指定的远程文件拷贝到镜像中的指定目录下。

  • 格式:

    ADD [--chown=<user>:<group>] <src>... <dest>
    ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] # 当路径中有空格时,使用该形式
  • 解压压缩文件并把它们添加到镜像中

    WORKDIR /app
    ADD test.tar.gz .
  • 从 url 拷贝文件到镜像中

    # docker 官方建议我们当需要从远程复制文件时,最好使用 curl 或 wget 命令来代替 ADD 命令。
    # 原因是,当使用 ADD 命令时,会创建更多的镜像层,当然镜像的 size 也会更大(下面的两段代码来自 docker 官方文档):
    ADD http://example.com/big.tar.xz /usr/src/things/
    RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
    RUN make -C /usr/src/things all
  • 如果使用下面的命令,不仅镜像的层数减少,而且镜像中也不包含 big.tar.xz 文件:

    RUN mkdir -p /usr/src/things \
        && curl -SL http://example.com/big.tar.xz \
        | tar -xJC /usr/src/things \
        && make -C /usr/src/things all
    
    # 在解压压缩文件并把它们添加到镜像

# CMD

  • 指定从生成的镜像启动容器时,默认执行的命令

  • 有三种形式:

    CMD ["executable","param1","param2"]
    # exec形式。使用exec执行,不会启动shell。
    # 是官方推荐的形式。
    
    CMD command param1 param2
    # shell形式。将在默认的shell终端中运行命令。在linux下是/bin/sh -c。
    
    CMD ["param1","param2"]
    # 这种形式不是指定默认命令,而是为ENTRYPOINT指定的命令提供参数。
    # 所有数组中的值都将作为ENTRYPOINT指定的命令的参数。
  • 每个Dockerfile只能有一条CMD命令。如果指定了多条,则只有最后一条会被执行。

  • 如果用户在启动容器时手动指定了运行的命令(作为run或create的参数),则会覆盖掉CMD。

# ENTRYPOINT

  • 指定的镜像的默认入口命令, 该入口命令会在启动容器时作为根命令执行,所有传入值作为该命令的参数

  • 每个Dockerfile中只能有一个ENTRYPOINT,当指定多个时,只有最后一个起效。

  • ENTRYPOINT 有两种格式, exec调用执行需要使用双引号

    ENTRYPOINT ["executable", "param1", "param2"] (exec调用执行,首选)
    ENTRYPOINT command param1 param2 (shell中执行)
  • exec 格式 docker run <image> 后面跟的命令行参数将会添加到 ENTRYPOINT 所有参数的最后,且会覆盖掉所有 CMD 命令中的参数

  • ENTRYPOINT 命令可以通过 docker run --entrypoint 参数来覆盖 。

    [root@node1 docker]# cat Dockerfile
    FROM ubuntu
    ENTRYPOINT ["top", "-b"]
    CMD ["-c"]
    
    [root@node1 docker]# docker build -t top .
    [root@node1 docker]# docker run -it --rm --name test -d top -H
    [root@node1 docker]# docker exec -it test ps aux
    USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root          1  0.2  0.0   5960  1708 pts/0    Ss+  02:33   0:00 top -b -H
    root          7  0.0  0.0   5888  1480 pts/1    Rs+  02:34   0:00 ps aux
    
  • shell 格式会忽略所有 CMD 命令的参数和 docker run 的命令行参数,ENTRYPOINT 要运行的命令会作为 /bin/sh -c 的子命令运行,而且 /bin/sh 不会传递信号,也就是说 ENTRYPOINT 要运行的命令不是 PID 为 1 的进程,且不会收到 Unix 信号,所以你要执行的命令不会收到 docker stop <container> 发出的 SIGTERM 信号。

    [root@node1 docker]# cat Dockerfile2
    FROM ubuntu
    ENTRYPOINT top -b
    CMD -c
    
    [root@node1 docker]# docker build -t top2  -f Dockerfile2 .
    [root@node1 docker]# docker run -it --rm --name test2 -d top2 -H
    [root@node1 docker]#  docker exec -it test2 ps aux
    USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root          1  1.4  0.0   2600   648 pts/0    Ss+  02:44   0:00 /bin/sh -c top
    root          6  0.0  0.0   5960  1708 pts/0    S+   02:44   0:00 top -b
    root          7  0.0  0.0   5888  1476 pts/1    Rs+  02:44   0:00 ps aux

# CMD/ENTRYPOINT组合

组合方式 实际执行的命令(容器启动时) 说明
ENTRYPOINT + CMD ENTRYPOINT 的参数 + CMD 的参数 CMD 的内容作为 ENTRYPOINT 的默认参数,可被 docker run 覆盖。
只有 ENTRYPOINT ENTRYPOINT 的命令 docker run 的参数会全部传递给 ENTRYPOINT
只有 CMD CMD 的命令 docker run 的参数会完全覆盖 CMD
  • Exec 格式(推荐)
# ENTRYPOINT 为 Exec 格式时,CMD 的参数会追加到 ENTRYPOINT 后。
ENTRYPOINT ["/app/start.sh"]
CMD ["--port=8080"]

docker run my-app            # 执行:/app/start.sh --port=8080
docker run my-app --port=9090 # 执行:/app/start.sh --port=9090(覆盖 CMD)
  • Shell 格式: ENTRYPOINT 为 Shell 格式时,CMD 会被忽略,docker run 的参数也无法覆盖。
ENTRYPOINT /app/start.sh
CMD ["--port=8080"]


docker run my-app            # 执行:/bin/sh -c "/app/start.sh"(CMD 无效)
docker run my-app --port=9090 # 同上,参数被忽略
  • 覆盖规则

    # docker run 的参数会直接替换 CMD 的内容(需 ENTRYPOINT 存在)。
    docker run my-app --port=9090  # 覆盖 CMD 参数
    
    # 覆盖 ENTRYPOINT:使用 --entrypoint 标志:
    docker run --entrypoint /bin/bash my-app

# 构建镜像

# 父镜像选择

  • 用户可以选择两种镜像作为父镜像,一种是所谓的基础镜像(baseimage)

  • 另外一种是普通的镜像(往往由第三方创建,基于基础镜像)

  • FROM scratch: 该镜像是一个空的镜像,可以用于构建busybox等超小镜像,可以说是真正的从零开始构建属于自己的镜像。

  • 如何制作大小为0 的镜像

    $ tar cv --files-from /dev/null | docker import - scratch
    $ docker image ls
    REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
    scratch                 latest              775bfce21429        9 minutes ago       0B

# 命令选项

Name, shorthand Default Description
–add-host 添加自定义主机到IP的映射(host: ip)
–build-arg 设置构建时变量
–cache-from 视为缓存源的镜像
–cgroup-parent 容器可选的父安全组
–compress 使用gzip压缩构建上下文
–cpu-period 限制CPU CFS(完全公平调度程序) 期限
–cpu-quota 限制CPU CFS(完全公平的调度程序)配额
–cpu-shares,-c CPU份额(相对重量)
–cpuset-cpus 允许执行的CPU(0-3,0,1)
–cpuset-mems 允许执行的MEM(0-3,0,1)
–disable-content-trust 跳过镜像验证
–file,-f Dockerfile的名称(默认为“ PATH / Dockerfile”)
–force-rm 始终取出中间容器
–iidfile 将镜像ID写入文件
–isolation 集装箱隔离技术
–label 设置镜像的元数据
–memory,-m 内存限制
–memory-swap 交换限制等于内存加交换:"-1"以启用无限交换
–network 在构建期间为RUN指令设置联网模块 API1.25+
–no-cache 构建镜像时不要使用缓存
–output,-o 输出目的地(格式:类型=本地,目的地=路径) API 1.40+
–platform 如果服务器具有多平台功能,请设置平台 API 1.32+ 实验(守护程序)
–progress auto 设置进度输出的类型(自动,普通,tty)。使用普通显示容器输出
–pull 始终尝试提取镜像的较新版本
–quiet,-q 禁止生产输出并成功打印镜像ID
–rm true 构建成功后删除中间容器
–secret 公开文件的秘密文件(仅在启用BuildKit的情况下): id = mysecret,src = / local / secret API 1.39+
–security-opt 安全选项
–shm-size /dev/shm的大小
–ssh SSH代理套接字或用于公开构建的密钥(仅在启用BuildKit的情况下)(格式: default
–stream 流附加到服务器以协商构建上下文 API 1.31+ 实验性(守护程序)
–tag,-t 名称以及“ name: tag”格式的标签(可选)
–target 设置要构建的目标构建阶段。
–ulimit Ulimit选项

常用选项:

  • --file , -f,指定dockerfile所在路径。

  • --tag , -t,指定生成镜像的标签信息。

  • docker build命令依靠上下文dockerfile文件来构建镜像。上下文是PATH或URL参数指定的目录及其子目录。

  • Dockerfile默认也在上下文中找,但是可以通过-f参数指定上下文以外的其他文件。该命令将上下文和dockerfile发送给Docker服务端,由服务端来创建镜像

# dockerignore文件

  • 将不检查的目录,文件写到同Dockerfile目录下的.dockerignore文件中, docker build命令将不再检查在.dockerignore文件中的目录和文件,在创建镜像时候不将无关数据发送到服务端。

  • .dockerignore 文件的写法和 .gitignore 类似,支持正则和通配符,具体规则如下:

    • 每行为一个条目;
    • 空行被忽略;
    • 构建上下文路径为所有文件的根路径;
  • 文件匹配规则具体语法如下:

    #	注释
    *	匹配0或多个非/的字符
    ?	匹配1个非/的字符
    **	0个或多个目录
    !	除...外,需结合上下文语义
    # 除 README.md 外,所有其他 md 文件都被 docker 忽略
    *.md
    !README.md

# DockerFile示例

# 自定义Nginx镜像

需求:构建包含vim且默认展示自定义页面的Nginx镜像

FROM nginx:alpine
# 安装vim调试工具
RUN apk update && apk add vim
# 替换默认首页
COPY custom-index.html /usr/share/nginx/html/
# 暴露80端口
EXPOSE 80
# 保持Nginx前台运行
CMD ["nginx", "-g", "daemon off;"]

构建命令:docker build -t custom-nginx:v1 .

# 自定义centos镜像

  • 编写DockerFile文件

    FROM centos:latest
    RUN rm -rf /etc/yum.repos.d/* && curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-8.repo && yum clean all && yum -y install epel-release vim wget lrzsz gcc gcc-c++ net-tools chrony passwd && yum -y update
    RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
    RUN	echo "set nu" >> ~/.vimrc && echo "set ts=4" >> ~/.vimrc && cat ~/.vimrc
    RUN echo 'alias ls="ls --color"' >> ~/.bashrc
    
    # 使用sleep便于调试
    RUN echo -e "#! /bin/bash\nwhile true\ndo\nsleep 1\ndone" > /sleep.sh
    CMD ["sh","/sleep.sh"]
    
    # 开启sshd服务
    RUN yum install openssh-server passwd -y
    RUN /usr/bin/echo "CuiLiang@0302" | /usr/bin/passwd --stdin root
    RUN /usr/bin/sed -i 's/.session.required.pam_loginuid.so./session optional pam_loginuid.so/g' /etc/pam.d/sshd && /bin/sed -i 's/UsePAM yes/UsePAM no/g' /etc/ssh/sshd_config && /bin/sed -i "s/#UsePrivilegeSeparation.*/UsePrivilegeSeparation no/g" /etc/ssh/sshd_config
    RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key && ssh-keygen -t rsa -f /etc/ssh/ssh_host_ecdsa_key && ssh-keygen -t rsa -f /etc/ssh/ssh_host_ed25519_key
    EXPOSE 22
    RUN echo -e "#! /bin/bash\n/usr/sbin/sshd -D" > /run.sh
    CMD ["/usr/sbin/sshd","-D"]
    
    # 源码安装python39
    RUN yum install -y wget gcc make mysql-devel
    WORKDIR /tmp
    RUN wget  https://www.python.org/ftp/python/3.9.0/Python-3.9.0.tgz
    RUN tar -zxf Python-3.9.0.tgz
    RUN /tmp/Python-3.9.0/configure --prefix=/usr/local/python3.9.0 --enable-optimizations --with-ssl
    RUN make && make install
    RUN echo "export PATH=$PATH:/usr/local/python3.9.0/bin" >> /etc/profile
    RUN source  /etc/profile
    RUN mkdir /root/.pip/
    RUN echo -e "[global]\nindex-url = https://pypi.tuna.tsinghua.edu.cn/simple\n[install]\ntrusted-host=mirrors.aliyun.com" > /root/.pip/pip.conf
  • 构建docker build -t 新镜像名字:TAG .

    • docker build 命令最后的. 表示当前目录
  • 运行docker run -it 新镜像名字:TAG

    • 可以看到,我们自己的新镜像已经支持vim/ifconfig命令,扩展成功了。

# Dockerfile优化

  • 合并RUN指令:减少镜像层数,使用&&连接命令
  • 使用环境变量集中管理配置
  • 清理yum缓存减少镜像大小
# 使用更小的基础镜像,CentOS 8已停止维护,建议使用CentOS Stream或Alpine
FROM centos:7

# 设置环境变量
ENV TZ=Asia/Shanghai \
    LANG=en_US.UTF-8 \
    PYTHON_VERSION=3.9.0 \
    PYTHON_HOME=/usr/local/python3.9.0

# 一次性安装所有依赖并清理缓存
RUN rm -rf /etc/yum.repos.d/* && \
    curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo && \
    yum clean all && \
    yum install -y epel-release vim wget lrzsz gcc gcc-c++ net-tools chrony passwd \
    openssh-server mysql-devel make && \
    yum -y update && \
    yum clean all && \
    rm -rf /var/cache/yum

# 配置时区和基本环境
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && \
    echo $TZ > /etc/timezone && \
    echo "set nu\nset ts=4" > /root/.vimrc && \
    echo 'alias ls="ls --color"' >> /root/.bashrc

# 配置SSH (注意:明文密码不安全,建议使用密钥认证)
RUN ssh-keygen -A && \
    echo 'CuiLiang@0302' | passwd --stdin root && \
    sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config && \
    sed -i 's/^#\?UsePAM.*/UsePAM no/' /etc/ssh/sshd_config && \
    sed -i 's/^#\?UsePrivilegeSeparation.*/UsePrivilegeSeparation no/' /etc/ssh/sshd_config

# 安装Python
WORKDIR /tmp
RUN wget -q https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz && \
    tar -zxf Python-${PYTHON_VERSION}.tgz && \
    cd Python-${PYTHON_VERSION} && \
    ./configure --prefix=${PYTHON_HOME} --enable-optimizations --with-ssl && \
    make -j$(nproc) && make install && \
    echo "export PATH=\$PATH:${PYTHON_HOME}/bin" >> /etc/profile && \
    mkdir -p /root/.pip/ && \
    echo -e "[global]\nindex-url = https://pypi.tuna.tsinghua.edu.cn/simple\n[install]\ntrusted-host=mirrors.aliyun.com" > /root/.pip/pip.conf && \
    rm -rf /tmp/Python-*

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s \
    CMD netstat -an | grep 22 > /dev/null || exit 1

# 暴露端口
EXPOSE 22

# 启动SSH服务
CMD ["/usr/sbin/sshd", "-D"]

# 多阶段构建

Docker 17.05版本以后,新增了Dockerfile多阶段构建。所谓多阶段构建,实际上是允许一个Dockerfile 中出现多个 FROM 指令。每个FROM开始一个新的构建阶段。虽然最终生成的镜像只包含最后一个阶段的内容,但多阶段构建提供了以下重要优势:

  1. 阶段性构建:可以将复杂的构建过程分解为多个逻辑阶段。
  2. 跨阶段文件复制:使用COPY --from指令可以从之前的构建阶段复制文件到当前阶段。
  3. 环境分离:可以将编译环境和运行环境完全分离,大幅减小最终镜像体积。

# 多阶段构建的优势

  • 减小镜像体积
    • 只将必要的构建结果复制到最终阶段, 最终阶段可以使用极简的基础镜像(如scratch或alpine)
  • 提高安全性
    • 减少最终镜像中的软件包数量,缩小攻击面
    • 避免将构建工具和中间文件暴露在生产环境中
  • 优化构建缓存
    • 修改后期阶段不会导致前期阶段重新构建

# 基本语法

# 第一阶段:构建阶段
FROM golang:1.19 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp

# 第二阶段:运行阶段
FROM alpine:latest
WORKDIR /root/
COPY --from=builder /app/myapp .
CMD ["./myapp"]

# 阶段命名与引用

可以为每个阶段命名(使用AS),然后在后续阶段通过名称引用:

FROM node:14 AS frontend-builder
...

FROM maven:3.6 AS backend-builder
...

FROM nginx:alpine
COPY --from=frontend-builder /app/dist /usr/share/nginx/html
COPY --from=backend-builder /app/target/*.jar /opt/app/

# 从外部镜像复制

COPY --from不仅可以从之前的构建阶段复制,还可以直接从其他镜像复制:

FROM alpine:latest
COPY --from=nginx:latest /etc/nginx/nginx.conf /etc/nginx/nginx.conf

# 调试与优化技巧

  1. 最小化镜像:基于 alpinedistroless 镜像

    FROM golang:1.19-alpine AS builder
    WORKDIR /app
    COPY . .
    RUN CGO_ENABLED=0 go build -o /app/main .
    
    FROM gcr.io/distroless/static-debian11
    COPY --from=builder /app/main /app/
    CMD ["/app/main"]
  2. 分层优化:将变动频率低的指令放在前面,利用缓存加速构建

    # 1. 单独处理依赖文件
    COPY package.json yarn.lock ./
    RUN yarn install --production
    
    # 2. 再复制源代码(避免依赖变更导致缓存失效)
    COPY src/ ./src
    COPY config/ ./config
  3. 分阶段构建:使用多阶段构建减小最终镜像体积

    效果:最终镜像体积减少 80%(从 1.2GB → 200MB)

    # 构建阶段
    FROM maven:3.8.6-jdk-11 AS build
    COPY . /app
    RUN mvn clean package -DskipTests
    
    # 生产阶段
    FROM openjdk:11-jre-slim
    COPY --from=build /app/target/*.jar /app.jar
    CMD ["java", "-jar", "/app.jar"]
  4. 使用.dockerignore:排除无关文件(如node_modules、.git等)

  5. 镜像扫描:运行docker scan <image>检查安全漏洞

    # 使用Trivy扫描镜像
    trivy image your-image:tag
    
    # 输出示例:
    +---------+------------------+----------+-------------------+---------------+---------------------------------------+
    | LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION |                 TITLE                 |
    +---------+------------------+----------+-------------------+---------------+---------------------------------------+
    | openssl | CVE-2022-2097    | HIGH     | 1.1.1n-0+deb10u3  | 1.1.1n-0+deb10u4 | Incorrect AES-NI padding oracle...    |
    +---------+------------------+----------+-------------------+---------------+---------------------------------------+
  6. 非root用户运行

    FROM node:16-slim
    
    # 创建应用用户
    RUN groupadd -r appuser && \
        useradd -r -g appuser -d /app -s /sbin/nologin appuser
    
    WORKDIR /app
    COPY --chown=appuser:appuser . .
    
    USER appuser
    EXPOSE 3000
    CMD ["node", "server.js"]
更新于

请我喝[茶]~( ̄▽ ̄)~*

Fulsun 微信支付

微信支付

Fulsun 支付宝

支付宝