在使用 Dockerfile 构建镜像时,WORKDIR 是一个必不可少的指令,该指令主要是用于定义工作目录

    WORKDIR 指令在 Dockerfile 中有些讲究,就是必须要 FROM 指令之后、ADD 或者 COPY 等指令之前。该指令会定义之后的 ADDCOPY 等指令的工作区域。

    仅仅使用文字描述不太好理解,先直接看下简答的示例。

    在目录下创建一个简单的 Dockerfile 文件,内容如下(如果对 Dockerfile 还不理解也没关系,先往下看):

    1. # 定义基础镜像
    2. FROM openjdk:8
    3. # 定义工作目录
    4. WORKDIR app
    5. # 将 docker-web-demo.jar 添加到镜像中
    6. ADD docker-web-demo.jar .
    7. ENTRYPOINT ["/bin/bash", "-c", "java -jar docker-web-demo.jar"]

    在 Docekrfile 中,我们第一步定义了一个基础镜像 openjdk:8,需要说明的一点是 FROM 指令一定要是 Dockerfile 文件中的第一行。

    之后定义了一个工作目录 app,这个 app 在 Dockerfile 构建时会自动创建一个 app 目录,后面会进行说明。

    下一个指令 ADDWORKDIR 指令具体应用的重点,我们仅仅是使用 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
    

    看下截图:

    image.png

    看下截图的最后一行信息,也就是执行交互命令之后的一行,即:

    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 是在 ADDCOPY 指令之前,也就是说 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
    

    完结,撒花~