在使用 Dockerfile 构建镜像时,WORKDIR 是一个必不可少的指令,该指令主要是用于定义工作目录。
WORKDIR 指令在 Dockerfile 中有些讲究,就是必须要 FROM 指令之后、ADD 或者 COPY 等指令之前。该指令会定义之后的 ADD、COPY 等指令的工作区域。
仅仅使用文字描述不太好理解,先直接看下简答的示例。
在目录下创建一个简单的 Dockerfile 文件,内容如下(如果对 Dockerfile 还不理解也没关系,先往下看):
# 定义基础镜像FROM openjdk:8# 定义工作目录WORKDIR app# 将 docker-web-demo.jar 添加到镜像中ADD docker-web-demo.jar .ENTRYPOINT ["/bin/bash", "-c", "java -jar docker-web-demo.jar"]
在 Docekrfile 中,我们第一步定义了一个基础镜像 openjdk:8,需要说明的一点是 FROM 指令一定要是 Dockerfile 文件中的第一行。
之后定义了一个工作目录 app,这个 app 在 Dockerfile 构建时会自动创建一个 app 目录,后面会进行说明。
下一个指令 ADD 是 WORKDIR 指令具体应用的重点,我们仅仅是使用 ADD 指令将 docker-web-demo.jar 文件拷贝到基础镜像中,但是我们并没有指定具体的位置,这里点需要明白。之后,会具体做说明。
之后的 ENTRYPOINT 指令也是一样,在使用 java -jar 命令的时候同样仅仅是指定了 JAR 文件的名称,但是没有指定具体的路径,这同样是 WORKDIR 的作用。
先看下当前文件夹下的内容:
$ ls
Dockerfile docker-web-demo.jar
现在使用 docker build 命令构建该镜像:
$ sudo docker build -t web:v1 .
构建过程如下所述:
Sending build context to Docker daemon 35.5MB
Step 1/4 : FROM openjdk:8
---> d61c96e2d100
Step 2/4 : WORKDIR app
---> Running in 8d9fb1bd0c7f
Removing intermediate container 8d9fb1bd0c7f
---> 070f798d4f8e
Step 3/4 : ADD docker-web-demo.jar .
---> 4794b1ed9652
Step 4/4 : ENTRYPOINT ["/bin/bash", "-c", "java -jar docker-web-demo.jar"]
---> Running in 58a2b86fa9ee
Removing intermediate container 58a2b86fa9ee
---> 504c88658d6f
Successfully built 504c88658d6f
Successfully tagged web:v1
现在使用如下命令运行该镜像:
$ sudo docker run -p 8080:8080 web:v1
正常的话,控制台会输出如下信息:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.17.RELEASE)
2021-06-28 14:56:38.214 INFO 1 --- [ main] com.itumate.docker.DockerWebApplication : Starting DockerWebApplication on e0cdf700dc80 with PID 1 (/app/docker-web-demo.jar started by root in /app)
2021-06-28 14:56:38.227 INFO 1 --- [ main] com.itumate.docker.DockerWebApplication : The following profiles are active: local
2021-06-28 14:56:41.405 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-06-28 14:56:41.518 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-06-28 14:56:41.518 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.38]
2021-06-28 14:56:41.749 INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-06-28 14:56:41.749 INFO 1 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 3372 ms
2021-06-28 14:56:42.180 INFO 1 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2021-06-28 14:56:42.756 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-06-28 14:56:42.766 INFO 1 --- [ main] com.itumate.docker.DockerWebApplication : Started DockerWebApplication in 6.851 seconds (JVM running for 7.942)
这表示一个 web 项目被正常启动,使用 curl 方式调用试试:
$ curl -XGET localhost:8080
输出结果:
{
"name" : "张三",
"age" : 18,
"i18nTitle" : "本地化",
"date" : "2021-06-28",
"time" : "14:58:16",
"dateTime" : "2021-06-28 14:58:16"
}
做了这么多操作仅仅是为了证明服务正常启动,现在就来说下 WORKDIR 的作用:
不要关闭当前终端,另起一个终端,输入如下命令查询运行状态的容器ID:
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e0cdf700dc80 web:v1 "/bin/bash -c 'java …" 3 minutes ago Up 3 minutes 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp dreamy_meninsky
可以看到容器ID是: e0cdf700dc80,现在就来使用交互命令 docker exec -it 进入容器:
docker exec -it e0cdf700dc80 /bin/bash
看下截图:

看下截图的最后一行信息,也就是执行交互命令之后的一行,即:
root@e0cdf700dc80:/app#
你会发现,进入容器后所处的目录是 /app,继续在容器中使用 pwd 命令看下当前位置信息:
root@e0cdf700dc80:/app# pwd
/app
现在,更直观的可以看出 /app 目录是在根目录下,这个 /app 我们并没有手动去创建,而是在构建镜像时自动创建的。
这说明 **docker** 在构建镜像时如果读取到了 **WORKDIR** 指令,就会在根目录下创建一个相应的文件夹,这个文件夹就是我们使用 **WORKDIR** 定义的名称。
现在我们再来看下这个工作目录下的内容:
root@e0cdf700dc80:/app# ls
docker-web-demo.jar
好吧,现在你会发现你使用 ADD 指令添加进去的 jar 文件也默认被放置在这个工作目录,更有意思的是 ENTRYPOINT 指令:
ENTRYPOINT ["/bin/bash", "-c", "java -jar docker-web-demo.jar"]
这个指令本质上就是:
java -jar docker-web-demo.jar
这里你没感觉到奇怪吗?docker-web-demo.jar 文件其实是在工作目录 /app 下,按理说如果想要运行该 jar 必须要明确指定文件所在位置才对。即:
java -jar /app/docker-web-demo.jar
实际上我们并没有进行明确指定,这又说明了 WORKDIR 的一个作用。就是在设置了工作目录之后,默认情况下当前容器有一个“隐式用户”在工作目录中,之后当我们要执行一个指令时实际上就是在这个“隐式用户”去指定某个操作。
说了这么多也不知道能不能理解笔者的意思,如果无法理解的话希望多看几遍理解一下,尽可能自己跟着步骤手动的创建一个镜像并使用交互命令进入容器查看下有什么奇特。
另外还有一点,在文章开头笔者就说了。 WORKDIR 是在 ADD 和 COPY 指令之前,也就是说 WORKDIR 影响的不仅仅是 ADD ,而且还影响之后的所有指令。
不信?那我们就再来使用 RUN 指令创建一个文件试试:
# 定义基础镜像
FROM openjdk:8
# 定义工作目录
WORKDIR app
# 使用 RUN 指令创建一个文件
RUN echo "hello world" > hello.txt
ENTRYPOINT ["cat", "hello.txt"]
看,使用 RUN 指令借助 echo 创建了一个 hello.txt 文件。如果 hello.txt 确实在 /app 工作目录下,那么当运行容器时应该会输出 hello world 才对,现在就来看看:
$ docker build -t web:v2 .
Sending build context to Docker daemon 35.5MB
Step 1/4 : FROM openjdk:8
---> d61c96e2d100
Step 2/4 : WORKDIR app
---> Using cache
---> 070f798d4f8e
Step 3/4 : RUN echo "hello world" > hello.txt
---> Running in f7868a12fef1
Removing intermediate container f7868a12fef1
---> ea7a650c3bf3
Step 4/4 : ENTRYPOINT ["cat", "hello.txt"]
---> Running in b338277d9c9f
Removing intermediate container b338277d9c9f
---> c246d6cacecb
Successfully built c246d6cacecb
Successfully tagged web:v2
运行容器:
$ docker run --rm web:v3
hello world
完结,撒花~
