今天,同事问到我一个问题,就是Dockerfile中的WORKDIR怎么工作的?说实话,刚开始我还真没思考过。我只是想当然的认为Dockerfile里面设置了WORKDIR,然后启动容器的时候当然就会进入这个目录了。
但是,我们知道docker-compose也有一个类似的配置:
1 | working_dir |
那么,当我们在Dockerfile里面设置了WORKDIR,又在docker-compose里面设置了working_dir,那么,为什么我们创建一个容器的时候,进入的就是working_dir里面配置的那个目录呢?
我们实战分析一下,我们有如下Dockerfile:
1 | FROM alpine:3.8 |
很简单,就是以alpine:3.8为基础镜像,然后配置WORKDIR为/root。然后,我们编写对应的docker-compose.yml文件:
1 | version: '3' |
我们配置working_dir为/tmp,而不是Dockerfile中的/root。
现在,我们来编译一下进行:
1 | hantaohuang@~/tmp/test docker-compose build |
首先,我理解build的过程实际上是起一个临时容器,然后在容器里面跑对应的指令,然后再把这个容器的aufs merged层commit成一个镜像,不断的重复这个过程,直到commit出最后一个镜像,即我最终需要的镜像test。
我思考到了这一步,发现我就产生了一个问题:
1 | 在之前的临时容器里面执行的指令WORKDIR为什么会作用到我最终的镜像? |
我在内网里面提了这个问题,有大佬解答说镜像里面除了基本的文件系统之外,还有一些元数据,我们可以通过inspect命令来查看。我们来看看镜像的这些数据:
1 | hantaohuang@~/tmp/test docker inspect f5ae5eef35b3 |
我们发现,在ContainerConfig中有一项WorkingDir就记录了我们在Dockerfile里面设置的工作目录。所以,当我们通过这个镜像创建容器的时候,我们会进入WorkingDir指定的路径,也就是/root。我们来测试一下:
1 | hantaohuang@~/tmp/test docker run --rm -it f5ae5eef35b3 sh |
符合预期。
然后,如果我们通过docker-compose起一个容器:
1 | ~ # exit |
我们查看一下容器的id:
1 | hantaohuang@~/tmp/test docker ps -a |
然后进入容器:
1 | hantaohuang@~/tmp/test docker exec -it test sh |
我们发现,进入的是/tmp目录而不是/root目录。为什么呢?因为我们的容器也是有一份和镜像类似的数据,我们可以查看一下test容器的信息:
1 | hantaohuang@~/tmp/test docker inspect 078a7370c21d |
记录的信息比镜像多得多(我还省略了其他很多信息)。我们会发现,容器也是有一个Config.WorkingDir来进入进入容器的时候,应该到什么目录里面。
所以,我们可以得出结论,通过命令:
1 | docker run |
起一个容器的话(如果不指定工作目录),那么容器inspect出来的信息里面的workdir是和镜像inspect出来的信息里面的workdir一致。
如果我们在容器启动的时候指定了workdir,那么容器inspect出来的信息里面的workdir就和镜像inspect出来的不一致。
然后,当我们进入一个处于运行状态的容器里面的时候,也就是执行命令docker exec的时候,就会去读取容器的那个workdir。因此,我们通过docker-compose管理的workdir实际上是容器的那个workdir而不是镜像的那个wordir。