今天,同事问到我一个问题,就是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
。