当前位置 博文首页 > RtxTitanV的博客:通过Dockerfile构建Docker镜像
Dockerfile
是Docker用来构建镜像的文本文件,包含自定义的指令和格式。可以通过docker build
命令利用Dockerfile
构建镜像。Dockerfile
提供了一系列统一的资源配置语法指令,开发人员可以根据需求定制Dockerfile
,然后使用这份Dockerfile
文件进行自动化镜像构建,简化了构建镜像的复杂过程,同时Dockerfile
与镜像配合使用,使Docker在构建时可以充分利用镜像的功能进行缓存,大大提升了Docker的使用效率。
本文主要对Dockerfile指令和使用Dockerfile构建镜像进行简单总结。
Dockerfile
由一行行命令语句组成,支持以#
开头的注释行。一般Dockerfile
主体分为基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令四个部分。下面是从Docker Hub上拿来的nginx的一个Dockerfile的例子,可以对Dockerfile的基本结构有个大体的了解,我们在编写自己的Dockerfile时也可以参考Docker Hub上优秀镜像的Dockerfile,通过优秀镜像的Dockerfile来学习和总结经验来编写高效的Dockerfile文件。
# 指定所构建镜像的基础镜像
FROM debian:buster-slim
# 指定生成镜像的元数据标签信息
LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"
# 指定环境变量
ENV NGINX_VERSION 1.18.0
ENV NJS_VERSION 0.4.0
ENV PKG_RELEASE 1~buster
# 运行指定命令
RUN set -x \
# create nginx user/group first, to be consistent throughout docker variants
&& addgroup --system --gid 101 nginx \
&& adduser --system --disabled-login --ingroup nginx --no-create-home --home /nonexistent --gecos "nginx user" --shell /bin/false --uid 101 nginx \
&& apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y gnupg1 ca-certificates \
&& \
NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \
found=''; \
for server in \
ha.pool.sks-keyservers.net \
hkp://keyserver.ubuntu.com:80 \
hkp://p80.pool.sks-keyservers.net:80 \
pgp.mit.edu \
; do \
echo "Fetching GPG key $NGINX_GPGKEY from $server"; \
apt-key adv --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$NGINX_GPGKEY" && found=yes && break; \
done; \
test -z "$found" && echo >&2 "error: failed to fetch GPG key $NGINX_GPGKEY" && exit 1; \
apt-get remove --purge --auto-remove -y gnupg1 && rm -rf /var/lib/apt/lists/* \
&& dpkgArch="$(dpkg --print-architecture)" \
&& nginxPackages=" \
nginx=${NGINX_VERSION}-${PKG_RELEASE} \
nginx-module-xslt=${NGINX_VERSION}-${PKG_RELEASE} \
nginx-module-geoip=${NGINX_VERSION}-${PKG_RELEASE} \
nginx-module-image-filter=${NGINX_VERSION}-${PKG_RELEASE} \
nginx-module-njs=${NGINX_VERSION}.${NJS_VERSION}-${PKG_RELEASE} \
" \
&& case "$dpkgArch" in \
amd64|i386) \
# arches officialy built by upstream
echo "deb https://nginx.org/packages/debian/ buster nginx" >> /etc/apt/sources.list.d/nginx.list \
&& apt-get update \
;; \
*) \
# we're on an architecture upstream doesn't officially build for
# let's build binaries from the published source packages
echo "deb-src https://nginx.org/packages/debian/ buster nginx" >> /etc/apt/sources.list.d/nginx.list \
\
# new directory for storing sources and .deb files
&& tempDir="$(mktemp -d)" \
&& chmod 777 "$tempDir" \
# (777 to ensure APT's "_apt" user can access it too)
\
# save list of currently-installed packages so build dependencies can be cleanly removed later
&& savedAptMark="$(apt-mark showmanual)" \
\
# build .deb files from upstream's source packages (which are verified by apt-get)
&& apt-get update \
&& apt-get build-dep -y $nginxPackages \
&& ( \
cd "$tempDir" \
&& DEB_BUILD_OPTIONS="nocheck parallel=$(nproc)" \
apt-get source --compile $nginxPackages \
) \
# we don't remove APT lists here because they get re-downloaded and removed later
\
# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies
# (which is done after we install the built packages so we don't have to redownload any overlapping dependencies)
&& apt-mark showmanual | xargs apt-mark auto > /dev/null \
&& { [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; } \
\
# create a temporary local APT repo to install from (so that dependency resolution can be handled by APT, as it should be)
&& ls -lAFh "$tempDir" \
&& ( cd "$tempDir" && dpkg-scanpackages . > Packages ) \
&& grep '^Package: ' "$tempDir/Packages" \
&& echo "deb [ trusted=yes ] file://$tempDir ./" > /etc/apt/sources.list.d/temp.list \
# work around the following APT issue by using "Acquire::GzipIndexes=false" (overriding "/etc/apt/apt.conf.d/docker-gzip-indexes")
# Could not open file /var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages - open (13: Permission denied)
# ...
# E: Failed to fetch store:/var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages Could not open file /var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages - open (13: Permission denied)
&& apt-get -o Acquire::GzipIndexes=false update \
;; \
esac \
\
&& apt-get install --no-install-recommends --no-install-suggests -y \
$nginxPackages \
gettext-base \
curl \
&& apt-get remove --purge --auto-remove -y && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx.list \
\
# if we have leftovers from building, let's purge them (including extra, unnecessary build deps)
&& if [ -n "$tempDir" ]; then \
apt-get purge -y --auto-remove \
&& rm -rf "$tempDir" /etc/apt/sources.list.d/temp.list; \
fi
# forward request and error logs to docker log collector
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
# make default server listen on ipv6
RUN sed -i -E 's,listen 80;,listen 80;\n listen [::]:80;,' \
/etc/nginx/conf.d/default.conf
# 指定镜像内服务所监听的端口
EXPOSE 80
# 指定所创建镜像启动的容器接收退出的信号值
STOPSIGNAL SIGTERM
# 指定启动容器时默认执行的命令
CMD ["nginx", "-g", "daemon off;"]
Dockerfile
的格式如下:
# Comment
INSTRUCTION arguments
其中指令(INSTRUCTION)不区分大小写,但是为了与参数区分,通常情况大写。Docker会按顺序运行Dockerfile
中的指令。Dockerfile
必须以FROM
指令开头。它可以在解析器指令(parser directives),注释(comments)和全局范围参数(globally scoped ARGs)之后。FROM
指令具体指定你想要构建的镜像的父镜像(Parent Image)。FROM
指令只能在一个或多个声明在Dockerfile
中的FROM
行中使用的参数即ARG
指令之前。
Docker将以#
开头的行视为注释,除非该行是有效的解析器指令(parser directive)。在一行中的任何其他位置使用#
标记都将被视为参数,它允许下面这样的语句。还有注释中不支持行连续字符(\
)。
# 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
解析器指令支持syntax
和escape
。
# 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后端时才启用此功能,syntax
指令设定用于构建当前Dockerfile的Dockerfile构建器的位置。BuildKit后端允许无缝使用以Docker镜像的形式分发并在容器沙箱环境中执行的构建器的外部实现。
自定义的Dockerfile实现能够:
Docker分发了可用于docker/dockerfile
在Docker Hub上的仓库下构建Dockerfile的镜像的正式版本。这里有稳定版(stable)和测试版(experimental)两个发布新镜像的渠道。
稳定版渠道遵循的版本控制示例如下:
docker/dockerfile:1.0.0
- 只允许不可变版本 1.0.0
docker/dockerfile:1.0
- 允许版本 1.0.*
docker/dockerfile:1
- 允许版本 1.*.*
docker/dockerfile:latest
- 稳定渠道发布的最新版本测试版渠道在正式发布时使用稳定版渠道中主要和次要组成部分的增量版本控制的示例如下:
docker/dockerfile:1.0.1-experimental
- 只允许不可变版本1.0.1-experimental
docker/dockerfile:1.0-experimental
- 1.0
版本之后最新发布的测试版docker/dockerfile:experimental
- 测试渠道发布的最新版本我们可以根据需求选择合适的发布渠道,如果只想修正错误,则应使用docker/dockerfile:1.0
,如果想从测试功能中受益,则应使用测试版渠道,如果正在使用测试版渠道,则较新的版本可能无法向后兼容,因此建议使用不可变的完整版本。
# escape=\ (backslash)
或 # escape=` (backtick)
escape
指令设定在Dockerfile
中作为转义字符的字符,如果未指定,默认转义字符为\
。
转义字符既用于转义行中的字符,也用于转义换行符,这允许Dockerfile
指令跨越多行。注意,无论escape
解析器指令是否包含在Dockerfile
中,都不会在RUN
命令中执行转义,除非在行尾。
将转义字符设置为`
在Windows
上特别有用,其中\
是目录路径分隔符。`
与Windows PowerShell一致。
通过以下示例可以对escape
指令做一个简单的了解,该示例在Windows
系统环境中会运行失败,第二行末尾的第二个\
将被解释为换行符的转义符,而不是第一个\
的转义目标,类似的第三行结尾的 \
将会被作为换行符处理,这个dockerfile的结果就是第二行和第三行被认为是单个指令:
FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\
上面的一个解决方案是使用/
作为COPY
指令和dir
的目标。但是这种语法对于Windows
上的路径来说并不自然,并且最坏的情况是由于Windows
上的所有命令都不支持/
作为路径分隔符,因此容易出错。
但是通过添加escape
解析器指令,以下Dockerfile
会在Windows
上按照预期的方式的成功使用自然路径执行,具体方式如下:
# escape=`
FROM microsoft/nanoserver
COPY testfile.txt c:\
RUN dir c:\
用ENV
声明的环境变量也可以在Dockerfile
的某些指令中作为参数使用,还可以处理转义,将字符串中的类似变量的语法包含在语句中。
在Dockerfile
中使用$variable_name
或${variable_name}
标注环境变量具有相同的效果,并且括号语法通常用来解决没有空格的变量名称的问题,例如${foo}_bar
。
${variable_name}
语法还支持以下指定的一些标准bash
修饰符,下面的word
在所有情况下可以是包括其他环境变量的任何字符串:
${variable:-word}
:如果设置了variable
,结果将是该值,如果未设置variable
,结果将是word
。${variable:+word}
:如果设置了variable
,结果将是word
,否则结果是空字符串 。我们还可以通过在变量前添加\
来进行转义。例如\$foo
或\${foo}
,将分别转换为$foo
和${foo}
文字,下面是一个应用示例,#
之后显示的是解析后的表示:
FROM busybox
ENV foo /bar
WORKDIR ${foo} # WORKDIR /bar
ADD . $foo # ADD . /bar
COPY \$foo /quux # COPY $foo /quux
另外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
的相同指令的一部分。
Dockerfile
的格式上文已经总结过了,其中用于构建镜像的构建指令一般格式为INSTRUCTION arguments
,而Dockerfile
中一般有以下构建指令:FROM
、LABEL
、MAINTAINER
、EXPOSE
、ENV
、ENTRYPOINT
、VOLUME
、USER
、WORKDIR
、ARG
、ONBUILD
、STOPSIGNAL
、HEALTHCHECK
、SHELL
、RUN
、CMD
、ADD
、COPY
。下面将对这些指令进行简单总结。
FROM [--platform=<platform>] <image> [AS <name>]
或
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
或
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
FROM
指令初始化一个新的构建阶段并为后续指令设置基础镜像。因此有效的Dockerfile
必须以FROM
指令开头,其中基础镜像可以是任何有效镜像,而通过从公有仓库中拉取镜像来启动它尤其容易。
--platform
选项在FROM
引用了多平台镜像的情况下可以用于指定镜像所属的平台。例如linux/amd64
,linux/arm64
或windows/amd64
,默认会使用发起构建请求的目标平台,也可以在该选项的值中使用全局构建参数。
ARG
是Dockerfile
中唯一可以在FROM
之前的指令。
FROM
可以在单个Dockerfile
中多次出现以创建多个镜像,或者使用一个构建阶段作为另一个构建阶段的依赖项。这只需在每个新的FROM
指令之前记下提交输出的最后一个镜像ID。每个FROM
指令会清除先前指令创建的任何状态。
通过将AS name
添加到FROM
指令可以将可选的名称赋予到新的构建阶段,该名称可以在后续的FROM
和COPY --from=<name|index>
指令中使用,以引用此阶段构建的镜像。
tag
或digest
的值是可选的,如果省略其中任何一个,构建器默认采用latest
标签,如果找不到tag
值,构建器将返回错误。
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
LABEL <key>=<value> <key>=<value> <key>=<value> ...
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."
一个镜像可以有多个label,可以在一行中指定多个label。在Docker 1.10之前,这减小了最终镜像的大小,但现在不再是这样了。我们仍然可以选择在单个指令中指定多个label,有以下两种方式:
LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
基础或父镜像(FROM
指定镜像)中包含的label将继承到新构建的镜像,如果label已存在但具有不同的值,则最近应用的值将覆盖任何之前设置的值。
使用docker image inspect
命令可以查看镜像的label,使用--format
选项可以只显示label,例如命令docker image inspect --format='' myimage
。
MAINTAINER <name>
MAINTAINER
指令设置生成镜像的作者(Author)字段。不过LABEL
指令使用起来比MAINTAINER
更灵活,可以设置我们所需的任何元数据并且能够轻松查看这些元数据,所以比起MAINTAINER
指令更推荐使用LABEL
指令。要设置与MAINTAINER
字段对应的label,可以使用下面示例中的LABEL
指令:
LABEL maintainer="SvenDowideit@home.org.au"
该MAINTAINER
字段可以通过docker inspect
与其他label一起显示出来。
EXPOSE <port> [<port>/<protocol>...]
EXPOSE
指令通知Docker容器在运行时监听指定的网络端口,可以指定端口是监听TCP还是UDP,如果未指定网络协议,默认监听TCP。
EXPOSE
指令只是起到声明作用,并不会自动完成端口映射,也就是实际上不会发布端口。
如果想要在运行容器时实际发布端口,即完成端口映射,需要在docker run
即创建运行容器时使用-p
具体指定一个或多个主机端口与容器端口间的映射,或者使用-P
,对容器中被监听的每个端口,Docker都将自动分配一个主机临时端口与之完成映射。
默认情况EXPOSE
指定端口是监听TCP,也可以指定为UDP,例如markup EXPOSE 80/udp
。如果要指定端口是同时监听TCP和UDP,需要包括两行指令,下面是一个示例,在这种情况如果将-P
与docker run
一起使用,则端口将对TCP和UDP各暴露一次,由于-P
使用的是Docker自动分配的主机临时端口,所以在TCP和UDP上与EXPOSE
指定端口映射的主机端口不相同。
EXPOSE 80/tcp
EXPOSE 80/udp
无论EXPOSE
如何设置,在运行时都可以使用-p
进行覆盖。示例如下:
docker run -p 80:80/tcp -p 80:80/udp ...
ENV <key> <value>
或
ENV <key>=<value> ...
ENV
指令指定一个环境变量<key>
的值<value>
,该值将存在于构建阶段中所有后续指令的环境中,并且可以在许多时候进行内部替换。
ENV
指令有两种形式,第一种ENV <key> <value>
设置单个变量的值,第一个空格后面的整个字符串包括空格字符都将被视为<value>
,因为该值将针对其他环境变量进行解释,在未对其进行转义的情况下将删除引号字符。示例如下:
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy
第二种形式ENV <key>=<value> ...
允许一次设置多个变量,注意与第一种形式不同的是第二种形式在语法中使用等号(=)。与命令行解析一样,引号和反斜杠可用于在值内包含空格。示例如下,该例在最终生成的镜像中与上例一样会产生相同的结果。
ENV myName="John Doe" myDog=Rex\ The\ Dog \
myCat=fluffy
从生成的镜像运行容器时使用ENV
设置的环境变量将保持不变,可以使用docker inspect
来查看环境变量的值,还可以使用docker run --env <key>=<value>
来改变指定环境变量的值。
环境变量的持久性可能会导致意想不到的副作用。例如设置ENV DEBIAN_FRONTEND noninteractive
可能会使基于Debian的镜像上的apt-get用户感到困惑。要为单个命令设置值,可以使用RUN <key>=<value> <command>
。
ENTRYPOINT
指令有两种形式:
ENTRYPOINT ["executable", "param1", "param2"]
(exec形式,推荐形式)ENTRYPOINT command param1 param2
(shell形式)ENTRYPOINT
指令指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令执行,所有传入值作为该命令的参数。
使用exec形式时docker run <image>
传入的命令行参数会被附加到ENTRYPOINT
所有元素之后,并将覆盖CMD
指定的所有元素。这允许将参数传递给入口点,即docker run <image> -d
将-d
参数传递给入口点。使用docker run --entrypoint
可以覆盖ENTRYPOINT
指令。
使用shell形式时ENTRYPOINT
指令会防止任何CMD
或run
命令行参数被使用,但缺点是ENTRYPOINT
将作为/bin/sh -c
的子命令启动,它不传递信号。这意味着进程在容器中的PID
不是1并且不会接收Unix信号,所以命令进程将不会从docker stop <container>
中接收到SIGTERM
信号,即在通过docker stop
的形式停止容器的时候接收不到停止信号将会导致异常终止。
在Dockerfile
中只有最后一个ENTRYPOINT
指令才会生效。
exec形式将被解析为JSON数组,所以必须使用"
来包围单词而不是'
。
exec形式不会调用命令shell,也就不会发生正常的shell处理,例如ENTRYPOINT [ "echo", "$HOME" ]
不会对$HOME
进行变量替换。如果需要shell处理,可以使用shell形式或者直接执行shell,例如ENTRYPOINT [ "sh", "-c", "echo $HOME" ]
。当使用exec形式并直接执行shell时,跟shell形式的情况一样是执行环境变量扩展的shell而不是docker。
如果CMD
在基础镜像中被设置过,设置ENTRYPOINT
将会重置CMD
为一个空值,这种情况下CMD
必须在当前镜像中设置一个值。
CMD
和ENTRYPOINT
指令都定义了运行容器时执行的命令,以下为它们之间协作的规则。
CMD
或ENTRYPOINT
命令。ENTRYPOINT
。CMD
应该作为ENTRYPOINT
命令定义默认参数或在容器中执行特定命令的方法。CMD
将被覆盖。下表展示了不同的ENTRYPOINT
和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 |
VOLUME ["/data"]
VOLUME
指令创建一个具有指定名称的挂载点,并将其标记为从本机主机或其他容器保存的外部挂载卷,该值可以是JSON数组或具有多个参数的普通字符串。使用docker run
命令会用任何存在于基础镜像内指定位置的数据初始化新创建的卷。
以下是关于Dockerfile中的卷的注意事项:
基于Windows容器的卷:使用基于Windows的容器时,容器中卷的目标必须是一个不存在的或空目录和C:
以外的驱动器这两者之一。
从Dockerfile中更改卷:如果任何构建步骤在声明后更改卷内的数据,那么这些更改将被丢弃。
JSON格式:将列表解析为JSON数组必须使用"
而不是'
。
主机目录在容器运行时被声明:主机目录(挂载点)本质上是依赖于主机的。这是为了保持镜像的可移植性,因为不能保证给定的主机目录在所有主机上都可用,因此无法从Dockerfile中挂载主机目录。VOLUME
指令不支持指定host-dir
参数,在创建或运行容器时必须指定挂载点。
USER <user>[:<group>]
或
USER <UID>[:<GID>]
USER
指令设置用户名或UID以及可选的用户组或GID,当运行镜像以及在Dockerfile
中紧接着的所有RUN
,CMD
和ENTRYPOINT
指令时会使用该指定用户。
当为用户指定用户组时,用户将只有指定的用户组的成员身份,任何其他已配置的用户组的成员身份将被忽略。
当用户没有主用户组时将使用root
组运行镜像或下一条指令。
在Windows上,如果用户不是内置帐户,则必须先创建用户,这可以通过作为Dockerfile一部分调用的net user
命令来完成。示例如下:
FROM microsoft/windowsservercore
# 在容器中创建Windows用户
RUN net user /add patrick
# 为后续命令设置用户名
USER patrick
WORKDIR /path/to/workdir
WORKDIR
指令为Dockerfile
中的任何RUN
、CMD
、ENTRYPOINT
、COPY
和ADD
指令设置工作目录。如果WORKDIR
不存在,即使它没有在任何后续Dockerfile
指令中被使用,也将创建它。
WORKDIR
指令可以在Dockerfile
中多次使用,如果提供了相对路径,则这个工作目录会基于之前WORKDIR
指令指定的路径。示例如下,该Dockerfile
最终的pwd
命令输出为/a/b/c
。
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd