专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Dockerfile 指南

Docker通过读取Dockerfile文件中的指令可以自动构建镜像。 Dockerfile是一个文本文档,它包含用户可以在命令行上调用的所有命令来组装一个镜像。使用docker build的用户可以创建自动执行的构建,该构建可以连续执行多个命令行指令。

这个页面描述了你可以在Dockerfile中使用的命令。读完此页后,请参阅Dockerfile最佳实践以获得面向技巧的指南。

用法

docker build命令从Dockerfile和上下文构建镜像。 构建的上下文是位于指定位置PATH或URL的文件集。 PATH是本地文件系统上的目录。 该URL是一个Git存储库位置。 上下文是递归处理的。 因此,PATH包括任何子目录,而URL包括存储库及其子模块。 此示例显示了一个使用当前目录作为上下文的构建命令:

$ docker build .
Sending build context to Docker daemon  6.51 MB
...

该构建由Docker守护程序而不是CLI运行。 构建过程要做的第一件事是将整个上下文(递归)发送到守护程序。 在大多数情况下,最好以空目录作为上下文,并将Dockerfile保留在该目录中。 仅添加构建Dockerfile所需的文件。

特别提醒: 请勿将您的根目录/用作PATH,因为它会导致构建将硬盘驱动器的全部内容传输到Docker守护程序。

要在构建上下文中使用文件,Dockerfile引用指令(例如COPY指令)中指定的文件。 为了提高构建的性能,请通过向上下文目录中添加.dockerignore文件来排除文件和目录。 有关如何创建.dockerignore文件的信息,后续会进行讲解。

传统上,Dockerfile称为Dockerfile,位于上下文的根目录中。 您将-f标志与docker build一起使用以指向文件系统中任何位置的Dockerfile。

$ docker build -f /path/to/a/Dockerfile .

如果构建成功,则可以指定存储新映像的存储库和标记:

$ docker build -t shykes/myapp .

要在构建后将镜像标记到多个存储库中,请在运行build命令时添加多个-t参数:

$ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .

在Docker守护程序运行Dockerfile中的指令之前,它会对Dockerfile进行初步验证,如果语法不正确,则会返回错误. Docker 守护进程一步一步的执行Dockerfile文件中的指令。在最终输出新镜像的id之前,如果需要,将每个指令的结果提交到一个新镜映像中。Docker守护进程将自动清理你发送的上下文环境。

请注意,每条指令都是独立运行的,并会导致创建新镜像-因此RUN cd / tmp对下一条指令不会有任何影响。

只要有可能,Docker将重用中间镜像(缓存),以显著加快Docker的构建过程。这由控制台输出中的Using cache消息表示。

$ docker build -t svendowideit/ambassador .
Sending build context to Docker daemon 15.36 kB
Step 1/4 : FROM alpine:3.2
 ---> 31f630c65071
Step 2/4 : MAINTAINER SvenDowideit@home.org.au
 ---> Using cache
 ---> 2a1c91448f5f
Step 3/4 : RUN apk update &&      apk add socat &&        rm -r /var/cache/
 ---> Using cache
 ---> 21ed6e7fbb73
Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh
 ---> Using cache
 ---> 7ea8aef582cc
Successfully built 7ea8aef582cc

构建缓存仅用于具有本地父链的镜像。 这意味着这些图像是由以前的版本创建的,或者整个图像链都已通过docker load加载。 如果您希望使用特定镜像的构建缓存,则可以使用–cache-from选项指定它。 用–cache-from指定的镜像不需要具有父链,并且可以从其他仓库中提取。

当完成了构建之后,就可以考虑推到仓库了。

格式

这是Dockerfile的格式:

# Comment
INSTRUCTION arguments

该指令不区分大小写。 但是,约定是大写,以便更轻松地将它们与参数区分开。

Docker按在Dockerfile中声明的顺序运行指令。 Dockerfile必须以FROM指令开头。 这可能在解析器指令,注释和全局范围的ARG之后。 FROM指令指定了你要构建镜像的父镜像。 FROM指令前面只能有一个或多个ARG指令,这些指令声明Dockerfile中FROM行中使用的参数。

Docker会将以开头的行视为注释,除非该行是有效的解析器指令。 行中其他任何地方的标记均被视为参数。 这允许如下语句:

# Comment
RUN echo 'we are running some # of cool things'

注释中不支持连续行字符。

解析器指令

解析器指令是可选的,并且影响Dockerfile中后续行的处理方式。解析器指令不向构建中添加层,也不会显示为构建步骤。解析器指令被编写为形式# directive=value中的一种特殊类型的注释。单个指令只能使用一次。

一旦处理了注释、空行或生成器指令,Docker就不再寻找解析器指令。相反,它将任何格式化为解析器指令的内容视为注释,并且不尝试验证它是否可能是解析器指令。因此,所有解析器指令必须位于Dockerfile的最顶层。

解析器指令不区分大小写。但是,习惯上它们都是小写的。约定还包括任何解析器指令后面的空行。解析器指令不支持行延续字符。

根据这些规则,以下实例中的解析器指令是非法的:

# direc \
tive=value

出现两次,非法的

# directive=value1
# directive=value2

FROM ImageName

出现在指令之后,被当作是注释:

FROM ImageName
# directive=value

出现在注释之后,被当作是注释:

# About my dockerfile
# directive=value
FROM ImageName

由于未被识别,未知指令被视为注释。 另外,由于在非语法分析器指令的注释之后出现,因此已知指令被视为注释。

# unknowndirective=value
# knowndirective=value

解析器指令中允许非换行空格。 因此,以下各行都被相同地对待:

#directive=value
# directive =value
#  directive= value
# directive = value
#    dIrEcTiVe=value

支持以下解析器指令:

  • syntax
# syntax=[remote image reference]

实例:

# syntax=docker/dockerfile
# syntax=docker/dockerfile:1.0
# syntax=docker.io/docker/dockerfile:1
# syntax=docker/dockerfile:1.0.0-experimental
# syntax=example.com/user/repo:tag@sha256:abcdef...

上述功能只在编辑工具集BuildKit中使用。

  • escape
# escape=\ (backslash)

或者

# escape=` (backtick)

escape指令设置用于对Dockerfile中的字符进行转义的字符。 如果未指定,则默认转义字符为\

转义字符既用于转义行中的字符,也用于转义换行。这允许Dockerfile指令跨越多行。注意,无论Dockerfile中是否包含escape解析器指令,除了在行尾之外,都不会在RUN命令中执行转义。

在Windows上将转义符设置为`尤其有用,其中\是目录路径分隔符。 `与Windows PowerShell一致。

环境变量替换

环境变量(用ENV语句声明)也可以在某些指令中用作Dockerfile解释的变量。 转义也可以通过在字面上将类似变量的语法包含到语句中来处理。

环境变量在Dockerfile中使用$variable_name${variable_name}表示。 它们被同等对待,并且大括号语法通常用于解决变量名没有空格的问题,例如${foo} _bar${variable_name}语法还支持一些标准的bash修饰符,如下所示:

  • ${variable:-word}表示如果设置了变量,那么结果将是该值。 如果未设置变量,则结果将是word
  • ${variable:+word}表示如果设置了变量,则结果为word,否则结果为空字符串。

在所有情况下,word可以是任何字符串,包括额外的环境变量。

通过在变量前添加\可以进行转义:例如,\$foo\${foo}将分别转换为$foo${foo}文字。

Dockerfile中的以下指令列表支持环境变量:

  • ADD
  • COPY
  • ENV
  • EXPOSE
  • FROM
  • LABEL
  • STOPSIGNAL
  • USER
  • VOLUME
  • WORKDIR
  • ONBUILD

在整个指令中,环境变量替换将对每个变量使用相同的值。 换句话说,在此示例中:

ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

将导致def的值为hello,而不是bye。然而,ghi将有一个值bye,因为它不是设置abc``为bye的同一指令的一部分。

.dockerignore文件

在Docker CLI将上下文发送到Docker守护程序之前,它将在上下文的根目录中查找名为.dockerignore的文件。 如果此文件存在,则CLI会修改上下文以排除与其中的模式匹配的文件和目录。 这有助于避免不必要地将较大或敏感的文件和目录发送到守护程序,并避免使用ADDCOPY将它们添加到镜像中。

CLI将.dockerignore文文件解释为以换行符分隔的模式列表,类似于Unix shell的文件组。 为了匹配,上下文的根被认为是工作目录和根目录。 例如,模式/foo/barfoo/bar都在PATH的foo子目录中或位于URL的git存储库的根目录中排除名为bar的文件或目录。 其他任何东西都不排除。

如果.dockerignore文件中的行以第1列中的开头,则该行将被视为注释,并且在CLI解释之前将被忽略。 这是一个示例.dockerignore文件:

# comment
*/temp*
*/*/temp*
temp?

此文件导致以下构建行为:

Rule Behavior
# comment Ignored.
*/temp* 在根的任何直接子目录中排除名称以temp开头的文件和目录。 例如,排除纯文件/somedir/temporary.txt,以及目录/ somedir / temp。
*/*/temp* 从根以下两个级别的任何子目录中排除以temp开头的文件和目录。 例如,排除/somedir/subdir/temporary.txt。
temp? 排除根目录中名称为temp的一个字符扩展名的文件和目录。 例如,排除/ tempa和/ tempb。

匹配是通过Go的filepath.Match规则完成的。 预处理步骤将删除前导和尾随空格并消除。...元素使用Go的filepath.Clean。 预处理后空白的行将被忽略。

除了Go的filepath.Match规则之外,Docker还支持特殊的通配符字符串**,该字符串可以匹配任意数量的目录(包括零个)。 例如,**/*.go将排除在所有目录(包括构建上下文的根目录)中找到的所有以.go结尾的文件。

开头的行 (感叹号)可用于排除例外。 以下是使用此机制的示例.dockerignore文件:

    *.md
    !README.md

除了README.md之外的所有markdown文件都将从上下文中排除。放置!异常规则影响行为:.dockerignore中与特定文件匹配的最后一行决定它是被包含还是被排除。考虑以下示例:

    *.md
    !README*.md
    README-secret.md

除了README-secret.md以外的README文件,上下文中不包括markdown文件。 现在考虑这个例子:

    *.md
    README-secret.md
    !README*.md

包括所有README文件。 中间行没有任何作用,因为!README *.md匹配README-secret.md并排在最后。

您甚至可以使用.dockerignore文件来排除Dockerfile.dockerignore文件。 这些文件仍被发送到守护程序,因为它需要它们来完成其工作。 但是ADDCOPY指令不会将它们复制到图像。

最后,您可能需要指定要包含在上下文中的文件,而不是要排除的文件。 为此,将*指定为第一个模式,然后指定一个或多个异常模式。

注意:由于历史原因,该模式. 被忽略。

FROM

FROM [--platform=<platform>] <image> [AS <name>]

Or

FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]

Or

FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

FROM指令初始化一个新的构建阶段,并为后续指令设置基本镜像。因此,一个有效的Dockerfile必须以FROM指令开始。镜像可以是任何有效的镜像,从公共存储库中提取镜像尤其容易。

  • ARG是Dockerfile中可能在FROM之前的唯一指令。 请参阅了解ARG和FROM之间的相互作用。
  • FROM可以在单个Dockerfile中多次出现,以创建多个映像或将一个构建阶段用作对另一个构建阶段的依赖。 只需在每个新的FROM指令之前记录一次提交输出的最后一个图像ID。 每个FROM指令清除由先前指令创建的任何状态。
  • 通过将AS名称添加到FROM指令中,可以选择为新的构建阶段指定名称。 该名称可以在后续的FROM和COPY –from = <名称|索引>指令中使用,以引用在此阶段构建的映像。
  • 标签或摘要值是可选的。 如果您忽略其中任何一个,那么缺省情况下构建器将采用最新标签。 如果构建器找不到标签值,则返回错误。

在FROM引用多平台图像的情况下,可选的--platform标志可用于指定图像的平台。 例如linux/amd64linux/arm64Windows/amd64。默认情况下,使用构建请求的目标平台。 可以在此标志的值中使用全局构建参数,例如,自动平台ARG允许您将阶段强制为本机构建平台(--platform=$BUILDPLATFORM),并使用它在阶段内交叉编译到目标平台。

ARG和FROM之间的相互作用。

FROM指令支持由第一个FROM之前的任何ARG指令声明的变量。

ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD  /code/run-app

FROM extras:${CODE_VERSION}
CMD  /code/run-extras

在FROM之前声明的ARG在构建阶段之外,因此,FROM之后的任何指令都不能使用它。 要使用在第一个FROM之前声明的ARG的默认值,请使用ARG指令,在构建阶段内部不带任何值:

ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version

RUN

RUN有两种形式:

  • RUN (shell形式,命令在shell中运行,在Linux上默认为/bin/sh -c,在Windows上默认为cmd /S /C
  • RUN [“可执行文件”,“ param1”,“ param2”](exec格式)

RUN指令将在当前映像顶部的新层中执行任何命令,并提交结果。 生成的提交映像将用于Dockerfile中的下一步。

分层运行指令和生成提交符合Docker的核心概念,其中提交很便宜,可以从映像历史中的任何点创建容器,很像源代码控制。

exec格式可以避免使用shell字符串,并使用不包含指定的shell可执行文件的基本映像运行命令。可以使用SHELL命令更改shell形式的默认shell。

在shell形式中,可以使用\(反斜杠)将一条RUN指令继续到下一行。 例如,考虑以下两行:

RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'

它们在一起等效于以下这一行:

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'

要使用’/bin/sh’以外的其他shell,请使用exec形式传入所需的shell,例如: RUN [”/bin/bash”, “-c”, “echo hello”]

exec格式被解析为JSON数组,这意味着您必须在单词周围使用双引号(“),而不是单引号(‘)。

与shell表单不同,exec格式不调用命令shell。这意味着不会发生正常的shell处理。例如,RUN [”echo”, “$HOME”]不会对$HOME执行变量替换。如果要进行shell处理,则可以使用shell形式或直接执行shell,例如:RUN [”sh”, “-c”,”echo $HOME”]。当使用exec格式并直接执行shell时(例如在shell表单中),是由shell进行环境变量扩展,而不是docker。 注意:在JSON格式中,必须转义反斜杠。 这在Windows中特别有用,在Windows中反斜杠是路径分隔符。 由于无效的JSON,以下几行将被视为shell形式,并以意外的方式失败:RUN [”c:\windows\system32\tasklist.exe”],正确的语法格式:RUN [”c:\\windows\\system32\\tasklist.exe”]

运行指令的缓存不会在下一次构建期间自动失效。像RUN apt-get distt -upgrade -y这样的指令的缓存将在下一个构建过程中重用。RUN指令的缓存可以通过使用——no-cache标志来失效,例如docker build——no-cache

CMD

CMD指令有三种形式:

  • CMD [”executable”,”param1″,”param2″] (exec form,这是首选形式)
  • CMD [”param1″,”param2″] (作为ENTRYPOINT指令的默认参数)
  • CMD command param1 param2 (shell form)

一个Dockerfile中只能有一条CMD指令。如果你列出一个以上的CMD,那么只有最后一个CMD会生效。

CMD的主要用途是为执行容器提供默认值。,这些默认值可以包含可执行文件,也可以省略可执行文件,在这种缺省可执行文件的情况下,您还必须指定ENTRYPOINT指令。

特别提醒:如果使用CMD为ENTRYPOINT指令提供默认参数,则CMD和ENTRYPOINT指令均应使用JSON数组格式指定。

特别提醒:exec表单被解析为JSON数组,这意味着您必须在单词周围使用双引号(“),而不是单引号(‘)。

与shell表单不同,exec表单不调用命令shell。这意味着不会发生正常的shell处理。例如,CMD [”echo”, “$HOME”]不会对$HOME执行变量替换。如果您需要shell处理,那么要么使用shell表单,要么直接执行shell,例如:CMD [”sh”, “-c”, “echo $HOME”]。当使用exec表单并直接执行shell时(如shell表单的情况),执行环境变量扩展的是shell,而不是docker。

当以shell或exec格式使用时,CMD指令设置运行映像时要执行的命令。

如果使用shell形式的CMD,<command>将在/bin/sh -c中执行。

FROM ubuntu
CMD echo "This is a test." | wc -

如果您想运行没有shell的<命令>,那么您必须将该命令表示为JSON数组,并给出可执行文件的完整路径。这种数组形式是CMD的首选格式。任何附加参数都必须单独表示为数组中的字符串:

FROM ubuntu
CMD ["/usr/bin/wc","--help"]

如果希望容器每次都运行相同的可执行文件,则应考虑将ENTRYPOINT与CMD结合使用。

如果用户为docker run指定了参数,则它们将覆盖CMD中指定的默认值。

不要将RUN与CMD混淆。RUN实际运行命令并提交结果;CMD在构建时不执行任何操作,但是为映像指定想要的命令。

LABEL

LABEL <key>=<value> <key>=<value> <key>=<value> ...

LABEL指令将元数据添加到图像。 LABEL是键值对。 若要在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 1.10之前的版本中,这减小了最终映像的大小,但是情况不再如此。 您仍然可以通过以下两种方式之一选择在一条指令中指定多个标签:

LABEL multi.label1="value1" multi.label2="value2" other="value3"

LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

包含在基本镜像或父镜像(起始行中的镜像)中的标签由镜像继承。如果标签已经存在,但值不同,则最近应用的值将覆盖以前设置的任何值。

要查看镜像的标签,请使用docker inspect命令。

EXPOSE

EXPOSE <port> [<port>/<protocol>...]

EXPOSE指令通知Docker该容器在运行时侦听指定的网络端口。 您可以指定端口是侦听TCP还是UDP,如果未指定协议,则默认值为TCP。

EXPOSE指令实际上不会端口。 它充当构建映像的人与运行容器的人之间的一种文档类型,有关打算哪些端口的信息。要在运行容器时实际端口,请在docker run上使用-p标志并映射一个或多个端口,或使用-P标志所有公开的端口并将其映射到高阶端口。

默认情况下,EXPOSE采用TCP。您还可以指定UDP.

EXPOSE 80/udp

要同时在TCP和UDP上公开,请包括以下两行:

EXPOSE 80/tcp
EXPOSE 80/udp

在这种情况下,如果您在docker run中使用-P,那么TCP和UDP端口将分别公开一次。请记住,-P在主机上使用临时的高序主机端口,因此TCP和UDP的端口将不相同。

无论EXPOSE设置如何,都可以在运行时使用-p标志覆盖它们。 例如:

docker run -p 80:80/tcp -p 80:80/udp ...

要在主机系统上设置端口重定向,请参阅使用-P标志。 docker network命令支持创建网络以在容器之间进行通信,而无需暴露或特定端口,因为连接到网络的容器可以通过任何端口相互通信。

ENV

ENV <key> <value>
ENV <key>=<value> ...

ENV指令将环境变量<key>设置为值<value>。 此值将在构建阶段中所有后续指令的环境中使用,并且在许多情况下也可以内联替换。

ENV指令有两种形式。第一种形式是ENV <key> <value>,它将把单个变量设置为一个值。第一个空格之后的整个字符串将被视为<value>—包括空白字符。该值将被解释为其他环境变量,因此如果没有转义,引号字符将被删除。

第二种形式,ENV <key>=<value> ...,允许一次设置多个变量。 请注意,第二种形式在语法中使用等号(=),而第一种形式则不使用等号(=)。 像命令行解析一样,引号和反斜杠可用于在值中包含空格。

ENV myName="John Doe" myDog=Rex\ The\ Dog \
    myCat=fluffy

ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

将在最终图像中产生相同的净结果。

ADD

ADD有两种形式:

  • ADD [--chown=<user>:<group>] <src>... <dest>
  • ADD [--chown=<user>:<group>] ["<src>",... "<dest>"](包含空格的路径需要此格式)

--chown功能仅在用于构建Linux容器的Dockerfiles上受支持,而在Windows容器上不起作用。 由于用户和组所有权概念不会在Linux和Windows之间转换,因此使用/etc/passwd和/etc/group将用户名和组名转换为ID限制了该功能仅适用于基于Linux OS的容器。

ADD指令从<src>复制新文件,目录或远程文件URL,并将它们添加到映像的文件系统中的路径<dest>

可以指定多个<src>资源,但是如果它们是文件或目录,则将其路径解释为相对于构建上下文源的路径。

每个<src>都可以包含通配符,并且将使用Go的filepath.Match规则进行匹配。 例如:

ADD hom* /mydir/        # adds all files starting with "hom"
ADD hom?.txt /mydir/    # ? is replaced with any single character, e.g., "home.txt"

<dest>是绝对路径或相对于WORKDIR的路径,源文件或目录将复制到该目标容器内路径。

ADD test relativeDir/          # adds "test" to `WORKDIR`/relativeDir/
ADD test /absoluteDir/         # adds "test" to /absoluteDir/

在添加包含特殊字符(如[和])的文件或目录时,需要转义那些遵循Golang规则的路径,以防止它们被视为匹配模式。例如,添加一个名为arr[0].txt的文件,使用以下命令:

ADD arr[[]0].txt /mydir/    # copy a file named "arr[0].txt" to /mydir/

除非可选的--chown标志指定给定的用户名,组名或UID/GID组合以请求对所添加内容的特定所有权,否则所有新文件和目录均使用UID和GID为0创建。

--chown标志的格式允许用户名和组名字符串或直接整数UID和GID任意组合。 提供不带组名的用户名或不带GID的UID将使用与UID相同的数字GID。 如果提供了用户名或组名,则将使用容器的根文件系统/etc/passwd/etc/group文件分别执行从名称到整数UID或GID的转换。以下示例显示了--chown标志的有效定义:

ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/

如果容器根文件系统不包含/etc/passwd/etc/group文件,并且在--chown标志中使用了用户名或组名,则build将在ADD操作上失败。 使用数字ID不需要查找,并且不依赖于容器根文件系统内容。

<src>是远程文件URL的情况下,目标将具有600的权限。如果要检索的远程文件具有HTTP Last-Modified头,则该头的时间戳将用于在目标上设置mtime文件。 但是,就像在ADD中处理的任何其他文件一样,在决定文件是否已更改以及是否应更新缓存中,将不包括mtime

如果通过通过STDIN传递Dockerfile进行构建(docker build-<somefile),则没有构建上下文,因此Dockerfile只能包含基于URL的ADD指令。 您还可以通过STDIN传递压缩的存档:(docker build-<archive.tar.gz),位于存档根目录的Dockerfile,其余的存档将用作构建的上下文。

如果您的URL文件使用身份验证进行保护,那么您将需要在容器中使用RUN wget、RUN curl或其他工具,因为ADD指令不支持身份验证。

如果的内容已更改,则遇到的第一个ADD指令将使Dockerfile中所有后续指令的缓存无效。 这包括使RUN指令的缓存无效。 有关更多信息,请参见《 Dockerfile最佳实践》指南。

ADD遵循以下规则:

  • <src>路径必须在构建上下文内; 您不能ADD ../something / something,因为docker build的第一步是将上下文目录(和子目录)发送到docker守护程序。
  • 如果<src>是URL,并且<dest>不以斜杠结尾,则从URL下载文件并将其复制到<dest>
  • 如果<src>是URL,而<dest>确实以斜杠结尾,则从URL推断文件名,并将文件下载到<dest>/<filename>。 例如,ADD http://example.com/foobar /将创建文件/foobar。 该URL必须有一个重要的路径,以便在这种情况下可以找到适当的文件名(http://example.com将不起作用)。
  • 如果<src>是目录,则将复制目录的整个内容,包括文件系统元数据。该目录本身不被复制,仅其内容被复制。
  • 如果<src>是采用公认压缩格式(身份,gzip,bzip2或xz)的本地tar归档文件,则将其解压缩为目录。 来自远程URL的资源不会被解压缩。 复制或解压缩目录时,其行为与tar -x相同
  • 如果<src>是任何其他类型的文件,则将其与其元数据一起单独复制。 在这种情况下,如果<dest>以斜杠/结束,则它将被视为目录,并且<src>的内容将写入<dest>/base(<src>)
  • 如果直接或由于使用通配符而指定了多个<src>资源,则<dest>必须是目录,并且必须以斜杠/结尾。
  • 如果<dest>没有以斜杠结尾,它将被视为一个常规文件,<src>的内容将被写在<dest>
  • 如果<dest>不存在,它将与路径中所有缺少的目录一起创建。

COPY

COPY有两种形式:

  • COPY [--chown=<user>:<group>] <src>... <dest>
  • COPY [--chown=<user>:<group>] ["<src>",... "<dest>"](包含空格的路径需要此格式)

--chown功能仅在用于构建Linux容器的Dockerfiles上受支持,而在Windows容器上不起作用。 由于用户和组所有权概念不会在Linux和Windows之间转换,因此使用/etc/passwd和/etc/group将用户名和组名转换为ID限制了该功能仅适用于基于Linux OS的容器。

COPY指令从<src>复制新文件或目录,并将它们添加到容器的文件系统中,路径为<dest>

可以指定多个<src>资源,但是文件和目录的路径将被解释为相对于构建上下文的源。

每个<src>都可以包含通配符,并且将使用Go的filepath.Match规则进行匹配。 例如:

COPY hom* /mydir/        # adds all files starting with "hom"
COPY hom?.txt /mydir/    # ? is replaced with any single character, e.g., "home.txt"

<dest>是绝对路径或相对于WORKDIR的路径,源将在目标容器内复制到该路径。

COPY test relativeDir/   # adds "test" to `WORKDIR`/relativeDir/
COPY test /absoluteDir/  # adds "test" to /absoluteDir/

复制包含特殊字符(例如[和])的文件或目录时,需要遵循Golang规则转义那些路径,以防止将它们视为匹配模式。 例如,要复制名为arr[0].txt的文件,请使用以下命令:

COPY arr[[]0].txt /mydir/    # copy a file named "arr[0].txt" to /mydir/

除非可选的--chown标志指定给定的用户名,组名或UID/GID组合以请求对所复制内容的特定所有权,否则所有新文件和目录均使用UID和GID为0创建。

--chown标志的格式允许用户名和组名字符串或直接整数UID和GID任意组合。 提供不带组名的用户名或不带GID的UID将使用与UID相同的数字GID。 如果提供了用户名或组名,则将使用容器的根文件系统/etc/passwd/etc/group文件分别执行从名称到整数UID或GID的转换。以下示例显示了--chown标志的有效定义:

COPY --chown=55:mygroup files* /somedir/
COPY --chown=bin files* /somedir/
COPY --chown=1 files* /somedir/
COPY --chown=10:11 files* /somedir/

如果容器根文件系统不包含/etc/passwd/etc/group文件,并且在--chown标志中使用了用户名或组名,则build将在COPY操作上失败。 使用数字ID不需要查找,并且不依赖于容器根文件系统内容。

如果您使用STDIN进行构建(docker build-<somefile),则没有构建上下文,因此无法使用COPY。

COPY有一个可选的选项--from=<name|index>可以用于设置源位置为早期的构造阶段(使用FROM .. AS <name>创建)这将用于替换用户发送的构建上下文。 COPY遵守以下规则:

  • <src>路径必须在构建上下文内; 您无法COPY ../something / something,因为Docker build的第一步是将上下文目录(和子目录)发送到docker守护程序
  • 如果<src>是目录,则将复制目录的整个内容,包括文件系统元数据。该目录本身不被复制,仅其内容被复制。
  • 如果<src>是任何其他类型的文件,则将其与其元数据一起单独复制。 在这种情况下,如果<dest>以斜杠/结束,则它将被视为目录,并且<src>的内容将写入<dest>/base(<src>)
  • 如果直接或由于使用通配符而指定了多个<src>资源,则<dest>必须是目录,并且必须以斜杠/结尾。
  • 如果<dest>不以斜杠结尾,它将被视为常规文件,并且<src>的内容将写入<dest>
  • 如果<dest>不存在,它将与路径中所有缺少的目录一起创建。

ENTRYPOINT

ENTRYPOINT有两种形式:

  • ENTRYPOINT ["executable", "param1", "param2"](exec格式,首选)
  • ENTRYPOINT command param1 param2(shell 格式)

ENTRYPOINT允许您配置将作为可执行文件运行的容器。例如,以下将使用其默认内容启动nginx,并监听端口80:

docker run -i -t --rm -p 80:80 nginx

docker run <image>的命令行参数将被追加到ENTRYPOINT参数之后,并覆盖掉使用CMD指定的相应参数。这允许向entry point 传入参数。例如: docker run <image> -d-d参数传入到entry point. 你可以使用docker run --entrypoint选项来覆盖ENTRYPOINT指令。

shell形式可防止使用任何CMD或运行命令行参数,但其缺点是ENTRYPOINT将作为/bin/sh -c的子命令启动,该子命令不传递信号。 这意味着可执行文件将不是容器的PID 1,并且不会接收Unix信号,因此您的可执行文件将不会从docker stop <container>接收到SIGTERM

只有Dockerfile中的最后一条ENTRYPOINT指令才会起作用。

exec格式的ENTRYPOINT实例

您可以使用ENTRYPOINT的exec形式来设置相当稳定的默认命令和参数,然后使用两种形式的CMD来设置更可能被更改的其他默认值。

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]

运行容器时,您可以看到top是唯一的过程:

$ docker run -it --rm --name test  top -H
top - 08:25:00 up  7:27,  0 users,  load average: 0.00, 0.01, 0.05
Threads:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.1 us,  0.1 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   2056668 total,  1616832 used,   439836 free,    99352 buffers
KiB Swap:  1441840 total,        0 used,  1441840 free.  1324440 cached Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
    1 root      20   0   19744   2336   2080 R  0.0  0.1   0:00.04 top

要进一步检查结果,可以使用docker exec

$ docker exec -it test ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  2.6  0.1  19752  2352 ?        Ss+  08:24   0:00 top -b -H
root         7  0.0  0.1  15572  2164 ?        R+   08:25   0:00 ps aux

您可以使用docker stop test优雅关闭top容器。

下面的Dockerfile展示了如何使用ENTRYPOINT在前台运行Apache(即,作为PID 1)

FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

如果需要为单个可执行文件编写启动脚本,则可以使用exec和gosu命令确保最终的可执行文件接收Unix信号:

#!/usr/bin/env bash
set -e

if [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"

    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fi

    exec gosu postgres "$@"
fi

exec "$@"

最后,如果您需要在关闭时进行一些额外的清理(或与其他容器进行通信),或者需要协调多个可执行文件,那么您可能需要确保ENTRYPOINT脚本接收到Unix信号,将它们传递下去,然后执行更多的工作:

#!/bin/sh
# Note: I've written this using sh so it works in the busybox container too

# USE the trap if you need to also do manual cleanup after the service is stopped,
#     or need to start multiple services in the one container
trap "echo TRAPed signal" HUP INT QUIT TERM

# start service in background here
/usr/sbin/apachectl start

echo "[hit enter key to exit] or run 'docker stop <container>'"
read

# stop service and clean up here
echo "stopping apache"
/usr/sbin/apachectl stop

echo "exited $0"

如果使用docker run -it --rm -p 80:80 --name test apache运行此映像,则可以使用docker execdocker top检查容器的进程,然后要求脚本停止Apache:

$ docker exec -it test ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.1  0.0   4448   692 ?        Ss+  00:42   0:00 /bin/sh /run.sh 123 cmd cmd2
root        19  0.0  0.2  71304  4440 ?        Ss   00:42   0:00 /usr/sbin/apache2 -k start
www-data    20  0.2  0.2 360468  6004 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
www-data    21  0.2  0.2 360468  6000 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
root        81  0.0  0.1  15572  2140 ?        R+   00:44   0:00 ps aux
$ docker top test
PID                 USER                COMMAND
10035               root                {run.sh} /bin/sh /run.sh 123 cmd cmd2
10054               root                /usr/sbin/apache2 -k start
10055               33                  /usr/sbin/apache2 -k start
10056               33                  /usr/sbin/apache2 -k start
$ /usr/bin/time docker stop test
test
real    0m 0.27s
user    0m 0.03s
sys 0m 0.03s

您可以使用–entrypoint覆盖ENTRYPOINT,但这只能将二进制文件设置为exec(不使用sh -c)。

特别提醒:exec表单被解析为JSON数组,这意味着您必须在单词周围使用双引号(“),而不是单引号(‘)。

与shell表单不同,exec表单不调用命令shell。这意味着不会发生正常的shell处理。例如,ENTRYPOINT [”echo”, “$HOME”]不会对$HOME执行变量替换。如果您需要shell处理,那么要么使用shell表单,要么直接执行shell,例如:ENTRYPOINT [”sh”, “-c”, “echo $HOME”]。当使用exec表单并直接执行shell时(如shell表单的情况),执行环境变量扩展的是shell,而不是docker。

shell格式的ENTRYPOINT 实例

您可以为ENTRYPOINT指定一个纯字符串,它将在/bin/sh -c中执行。 此表单将使用Shell处理来替换Shell环境变量,并将忽略任何CMDdocker run命令行参数。 为了确保docker stop将正确发出任何长期运行的ENTRYPOINT可执行文件信号,您需要记住使用exec启动它:

FROM ubuntu
ENTRYPOINT exec top -b

运行此图像时,您会看到一个PID 1进程:

$ docker run -it --rm --name test top
Mem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cached
CPU:   5% usr   0% sys   0% nic  94% idle   0% io   0% irq   0% sirq
Load average: 0.08 0.03 0.05 2/98 6
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
    1     0 root     R     3164   0%   0% top -b

它将在docker stop上干净地退出:

$ /usr/bin/time docker stop test
test
real    0m 0.20s
user    0m 0.02s
sys 0m 0.04s

如果您忘记将exec添加到ENTRYPOINT的开头:

FROM ubuntu
ENTRYPOINT top -b
CMD --ignored-param1

然后,您可以运行它(为下一步命名):

$ docker run -it --name test top --ignored-param2
Mem: 1704184K used, 352484K free, 0K shrd, 0K buff, 140621524238337K cached
CPU:   9% usr   2% sys   0% nic  88% idle   0% io   0% irq   0% sirq
Load average: 0.01 0.02 0.05 2/101 7
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
    1     0 root     S     3168   0%   0% /bin/sh -c top -b cmd cmd2
    7     1 root     R     3164   0%   0% top -b

从顶部的输出中可以看到,指定的ENTRYPOINT不是PID 1。 如果您随后运行docker stop test,则容器不会干净退出-超时后,将强制执行stop命令发送SIGKILL:

$ docker exec -it test ps aux
PID   USER     COMMAND
    1 root     /bin/sh -c top -b cmd cmd2
    7 root     top -b
    8 root     ps aux
$ /usr/bin/time docker stop test
test
real    0m 10.19s
user    0m 0.04s
sys 0m 0.03s

了解CMD和ENTRYPOINT如何相互作用

CMD和ENTRYPOINT指令均定义运行容器时执行的命令。 很少有规则描述他们的合作。

  • Dockerfile应该指定CMD或ENTRYPOINT命令中的至少一个。
  • 使用容器作为可执行文件时,应定义ENTRYPOINT。
  • CMD应该用作定义ENTRYPOINT命令的默认参数或在容器中执行特别命令的方法。
  • 当运行带有可选参数的容器时,CMD将被覆盖。

下表显示了针对不同ENTRYPOINT / CMD组合执行的命令:

cmd No ENTRYPOINT ENTRYPOINT exec_entry p1_entry ENTRYPOINT [“exec_entry”, “p1_entry”]
No CMD error, not allowed /bin/sh -c exec_entry p1_entry exec_entry p1_entry
CMD [“exec_cmd”, “p1_cmd”] exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry exec_cmd p1_cmd
CMD [“p1_cmd”, “p2_cmd”] p1_cmd p2_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry p1_cmd p2_cmd
CMD exec_cmd p1_cmd /bin/sh -c exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd

如果从基本镜像定义了CMD,则设置ENTRYPOINT会将CMD重置为空值。 在这种情况下,必须在当前镜像中定义CMD以具有值。

VOLUME

VOLUME ["/data"]

VOLUME指令创建具有指定名称的挂载点,并将其标记为保存来自本地主机或其他容器的外部挂载的卷。 该值可以是JSON数组,VOLUME ["/var/log/"]或具有多个参数的纯字符串,例如VOLUME /var/logVOLUME /var/log /var/db。 有关通过Docker客户端的更多信息/示例和安装说明,请参阅通过Volumes共享目录。

docker run命令使用基本映像内指定位置上存在的任何数据初始化新创建的卷。 例如,考虑以下Dockerfile片段:

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

该Dockerfile生成一个映像,该映像使docker run/myvol处创建一个新的挂载点并将greeting文件复制到新创建的卷中。

有关指定卷的注意事项

关于Dockerfile中的卷,请记住以下几点。

  • 基于Windows的容器上的卷:使用基于Windows的容器时,容器内的卷的目的地必须是以下之一:
    • 不存在或空目录
    • C:以外的驱动器
  • 从Dockerfile内更改卷:如果在声明了卷后有任何构建步骤更改了卷中的数据,则这些更改将被丢弃。
  • JSON格式:该列表被解析为JSON数组。必须用双引号(“)而不是单引号(‘)括起单词。
  • 主机目录在容器运行时声明:主机目录(挂载点)从本质上说是依赖于主机的。 这是为了保留镜像的可移植性,因为不能保证给定的主机目录在所有主机上都可用。 因此,您无法从Dockerfile中挂载主机目录。 VOLUME指令不支持指定host-dir参数。 创建或运行容器时,必须指定安装点。

USER

USER <user>[:<group>] or
USER <UID>[:<GID>]

USER指令设置运行映像以及Dockerfile中该指令后续的所有RUN,CMD和ENTRYPOINT指令时要使用的用户名(或UID)以及可选的用户组(或GID)

当用户没有主要组时,该映像(或后续说明)将与根组一起运行。

在Windows上,如果用户不是一个内置帐户,则必须首先创建该用户。这可以通过Dockerfile中调用的net用户命令来完成。

    FROM microsoft/windowsservercore
    # Create Windows user in the container
    RUN net user /add patrick
    # Set it for subsequent commands
    USER patrick

WORKDIR

WORKDIR /path/to/workdir

WORKDIR指令为Dockerfile中跟随它的所有RUN,CMD,ENTRYPOINT,COPY和ADD指令设置工作目录。 如果WORKDIR不存在,即使以后的Dockerfile指令中未使用它也将被创建。

WORKDIR指令可在Dockerfile中多次使用。 如果提供了相对路径,则它将相对于上一个WORKDIR指令的路径。 例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

该Dockerfile中最后一个pwd命令的输出为/a/b/c

WORKDIR指令可以解析以前使用ENV设置的环境变量。 您只能使用在Dockerfile中显式设置的环境变量。 例如:

ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

该Dockerfile中最后一个pwd命令的输出为/path/$DIRNAME

ARG

ARG <name>[=<default value>]

ARG指令定义了一个变量,用户可以在构建时使用--build-arg <varname> = <value>标志使用docker build命令将其传递给构建器。 如果用户指定了未在Dockerfile中定义的构建参数,则构建会输出警告:

[Warning] One or more build-args [foo] were not consumed

Dockerfile可能包含一个或多个ARG指令。 例如,以下是有效的Dockerfile:

FROM busybox
ARG user1
ARG buildno
...

不建议使用构建时变量来传递诸如github密钥,用户凭据等秘密信息。使用docker history命令,镜像的任何用户都可以看到构建时变量值。

默认值

ARG指令可以选择包含默认值:

FROM busybox
ARG user1=someuser
ARG buildno=1
...

如果ARG指令具有默认值,并且在构建时未传递任何值,则构建器将使用默认值。

作用域

ARG变量定义从Dockerfile中定义的行开始生效,而不是该参数在命令行或其他地方的使用。 例如,考虑以下Dockerfile:

1 FROM busybox
2 USER ${user:-some_user}
3 ARG user
4 USER $user
...

用户通过调用以下命令来构建此文件:

$ docker build --build-arg user=what_user .

第2行的USER评估为some_user,因为在随后的第3行中定义了用户变量。第4行的USER评估为用户定义了what_user,并且在命令行中传递了what_user值。 在通过ARG指令对其进行定义之前,对变量的任何使用都会导致一个空字符串。

ARG指令在定义它的构建阶段结束时超出范围。 要在多个阶段中使用arg,每个阶段都必须包含ARG指令。

FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS

FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS

使用ARG变量

您可以使用ARG或ENV指令来指定RUN指令可用的变量。 使用ENV指令定义的环境变量始终会覆盖同名的ARG指令。 考虑使用带有ENV和ARG指令的Dockerfile。

1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER v1.0.0
4 RUN echo $CONT_IMG_VER

然后,假定此映像是使用以下命令构建的:

$ docker build --build-arg CONT_IMG_VER=v2.0.1 .

在本例中,RUN指令使用的是v1.0.0,而不是用户传递的ARG设置:v2.0.1。这种行为类似于shell脚本,其中局部作用域的变量从定义的角度覆盖作为参数传递或从环境中继承的变量。

使用上面的示例,但使用不同的ENV规范,您可以在ARG和ENV指令之间创建更有用的交互:

1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
4 RUN echo $CONT_IMG_VER

与ARG指令不同,ENV值始终保留在生成的映像中。 考虑不带--build-arg标志的Docker构建:

$ docker build .

使用此Dockerfile示例,CONT_IMG_VER仍保留在映像中,但其值为v1.0.0,因为它是ENV指令在第3行中设置的默认值。 在此示例中,变量扩展技术使您可以从命令行传递参数,并利用ENV指令将其保留在最终映像中。

预定义的ARGs

Docker具有一组预定义的ARG变量,您可以在Dockerfile中使用它们而无需相应的ARG指令。

HTTP_PROXY
http_proxy
HTTPS_PROXY
https_proxy
FTP_PROXY
ftp_proxy
NO_PROXY
no_proxy

要使用它们,只需使用以下标志在命令行中传递它们:

--build-arg <varname>=<value>

默认情况下,这些预定义变量从docker history的输出中排除。 排除它们可以减少意外泄漏HTTP_PROXY变量中的敏感身份验证信息的风险。

例如,考虑使用--build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com命令构建以下Dockerfile

FROM ubuntu
RUN echo "Hello World"

在这种情况下,HTTP_PROXY变量的值在docker history中不可用,也不被缓存。 如果要更改位置,并且代理服务器已更改为http://user:pass@proxy.sfo.example.com,则后续的构建不会导致高速缓存未命中。如果需要覆盖此行为,则可以通过在Dockerfile中添加ARG语句来做到这一点,如下所示:

FROM ubuntu
ARG HTTP_PROXY
RUN echo "Hello World"

构建此Dockerfile时,HTTP_PROXY保留在Docker历史记录中,更改其值会使构建缓存无效。

对构建缓存的影响

ARG变量不会像ENV变量那样持久保存到生成的映像中。 但是,ARG变量确实以类似的方式影响构建缓存。 如果Dockerfile定义了一个值与先前版本不同的ARG变量,则首次使用时会发生“缓存未命中”,而不是其定义。 特别是,所有在ARG指令之后的RUN指令都隐式地使用ARG变量(作为环境变量),因此可能导致高速缓存未命中。 除非Dockerfile中有匹配的ARG语句,否则所有预定义的ARG变量均免于缓存。

例如,考虑以下两个Dockerfile:

1 FROM ubuntu
2 ARG CONT_IMG_VER
3 RUN echo $CONT_IMG_VER

1 FROM ubuntu
2 ARG CONT_IMG_VER
3 RUN echo hello

If you specify –build-arg CONT_IMG_VER= on the command line, in both cases, the specification on line 2 does not cause a cache miss; line 3 does cause a cache miss.ARG CONT_IMG_VER causes the RUN line to be identified as the same as running CONT_IMG_VER= echo hello, so if the changes, we get a cache miss.

如果在命令行上指定--build-arg CONT_IMG_VER=<value>,则在两种情况下,第2行上的指定都不会导致高速缓存未命中;这两种情况都不会发生。 第3行的确会导致缓存未命中.ARG CONT_IMG_VER导致RUN行被标识为与运行CONT_IMG_VER = <value>相同,因此如果<value>发生更改,我们将得到缓存未命中。

考虑在同一命令行下的另一个示例:

1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER $CONT_IMG_VER
4 RUN echo $CONT_IMG_VER

在此示例中,高速缓存未命中发生在第3行。之所以发生未命中,是因为ENV中的变量值引用了ARG变量,并且该变量通过命令行进行了更改。 在此示例中,ENV命令使图像包含该值。

如果一个ENV指令覆盖了同名的ARG指令,就像这个Dockerfile:

1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER hello
4 RUN echo $CONT_IMG_VER

第3行不会造成缓存丢失,因为CONT IMG VER的值是一个常量(hello)。因此,RUN时使用的环境变量和值(第4行)不会在构建之间发生变化。

Dockerfile 实例

# Nginx
#
# VERSION               0.0.1

FROM      ubuntu
LABEL Description="This image is used to start the foobar executable" Vendor="ACME Products" Version="1.0"
RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server

文章永久链接:https://tech.souyunku.com/41056

未经允许不得转载:搜云库技术团队 » Dockerfile 指南

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们