以下是我的Dockerfile的内容

FROM node:boron

# Create app directory
RUN mkdir -p /usr/src/app

# Change working dir to /usr/src/app
WORKDIR /usr/src/app

VOLUME . /usr/src/app

RUN npm install

EXPOSE 8080

CMD ["node" , "server" ]

在这个文件中,我期待VOLUME。/usr/src/app命令挂载 将主机当前工作目录的内容挂载到/usr/src/app上 容器文件夹。

请告诉我这是正确的方式吗?


当前回答

为了更好地理解dockerfile中的volume指令,让我们学习一下mysql官方docker文件实现中典型的volume用法。

VOLUME /var/lib/mysql

参考: https://github.com/docker-library/mysql/blob/3362baccb4352bcf0022014f67c1ec7e6808b8c5/8.0/Dockerfile

/var/lib/mysql是MySQL存储数据文件的默认位置。

当您仅为测试目的运行测试容器时,您可以不指定它的挂载点,例如。

docker run mysql:8

然后mysql容器实例将使用dockerfile中的volume指令指定的默认挂载路径。卷是在Docker根目录中使用一个非常长的类似id的名称创建的,这被称为“未命名”或“匿名”卷。在底层主机系统/var/lib/docker/volumes文件夹下

/var/lib/docker/volumes/320752e0e70d1590e905b02d484c22689e69adcbd764a69e39b17bc330b984e4

这对于快速测试非常方便,不需要指定挂载点,但仍然可以通过将Volume用于数据存储而不是容器层来获得最佳性能。

对于正式的使用,您将需要通过使用命名卷或绑定挂载来指定挂载路径。

docker run  -v /my/own/datadir:/var/lib/mysql mysql:8

该命令从底层主机系统中将/my/own/datadir目录作为/var/lib/mysql挂载到容器中。数据目录/my/own/datadir不会被自动删除,甚至容器也会被删除。

mysql官方镜像的使用(请检查“Where to storage Data”部分):

参考: https://hub.docker.com/_/mysql/

其他回答

为了更好地理解dockerfile中的volume指令,让我们学习一下mysql官方docker文件实现中典型的volume用法。

VOLUME /var/lib/mysql

参考: https://github.com/docker-library/mysql/blob/3362baccb4352bcf0022014f67c1ec7e6808b8c5/8.0/Dockerfile

/var/lib/mysql是MySQL存储数据文件的默认位置。

当您仅为测试目的运行测试容器时,您可以不指定它的挂载点,例如。

docker run mysql:8

然后mysql容器实例将使用dockerfile中的volume指令指定的默认挂载路径。卷是在Docker根目录中使用一个非常长的类似id的名称创建的,这被称为“未命名”或“匿名”卷。在底层主机系统/var/lib/docker/volumes文件夹下

/var/lib/docker/volumes/320752e0e70d1590e905b02d484c22689e69adcbd764a69e39b17bc330b984e4

这对于快速测试非常方便,不需要指定挂载点,但仍然可以通过将Volume用于数据存储而不是容器层来获得最佳性能。

对于正式的使用,您将需要通过使用命名卷或绑定挂载来指定挂载路径。

docker run  -v /my/own/datadir:/var/lib/mysql mysql:8

该命令从底层主机系统中将/my/own/datadir目录作为/var/lib/mysql挂载到容器中。数据目录/my/own/datadir不会被自动删除,甚至容器也会被删除。

mysql官方镜像的使用(请检查“Where to storage Data”部分):

参考: https://hub.docker.com/_/mysql/

在Dockerfile中指定VOLUME行会在映像上配置一些元数据,但是如何使用这些元数据是很重要的。

首先,这两行做了什么:

WORKDIR /usr/src/app
VOLUME . /usr/src/app

如果目录不存在,那么WORKDIR行将创建该目录,并更新一些图像元数据以指定所有相对路径,同时,RUN等命令的当前目录将位于该位置。这里的VOLUME行指定了两个卷,一个是相对路径,另一个是/usr/src/app,两者恰好是相同的目录。大多数情况下,VOLUME行只包含一个目录,但它可以像您所做的那样包含多个目录,或者它可以是一个json格式的数组。

You cannot specify a volume source in the Dockerfile: A common source of confusion when specifying volumes in a Dockerfile is trying to match the runtime syntax of a source and destination at image build time, this will not work. The Dockerfile can only specify the destination of the volume. It would be a trivial security exploit if someone could define the source of a volume since they could update a common image on the docker hub to mount the root directory into the container and then launch a background process inside the container as part of an entrypoint that adds logins to /etc/passwd, configures systemd to launch a bitcoin miner on next reboot, or searches the filesystem for credit cards, SSNs, and private keys to send off to a remote site.

What does the VOLUME line do? As mentioned, it sets some image metadata to say a directory inside the image is a volume. How is this metadata used? Every time you create a container from this image, docker will force that directory to be a volume. If you do not provide a volume in your run command, or compose file, the only option for docker is to create an anonymous volume. This is a local named volume with a long unique id for the name and no other indication for why it was created or what data it contains (anonymous volumes are were data goes to get lost). If you override the volume, pointing to a named or host volume, your data will go there instead.

VOLUME breaks things: You cannot disable a volume once defined in a Dockerfile. And more importantly, the RUN command in docker is implemented with temporary containers with the classic builder. Those temporary containers will get a temporary anonymous volume. That anonymous volume will be initialized with the contents of your image. Any writes inside the container from your RUN command will be made to that volume. When the RUN command finishes, changes to the image are saved, and changes to the anonymous volume are discarded. Because of this, I strongly recommend against defining a VOLUME inside the Dockerfile. It results in unexpected behavior for downstream users of your image that wish to extend the image with initial data in volume location.

如何指定卷?要指定在映像中包含卷的位置,请提供docker-compose.yml。用户可以对其进行修改,以根据本地环境调整卷的位置,它还可以捕获其他运行时设置,如发布端口和网络。

应该有人把这个记录下来!他们有。Docker在Dockerfile文档中包含了关于VOLUME使用的警告,以及在运行时指定源文件的建议:

Changing the volume from within the Dockerfile: If any build steps change the data within the volume after it has been declared, those changes will be discarded. ... The host directory is declared at container run-time: The host directory (the mountpoint) is, by its nature, host-dependent. This is to preserve image portability, since a given host directory can’t be guaranteed to be available on all hosts. For this reason, you can’t mount a host directory from within the Dockerfile. The VOLUME instruction does not support specifying a host-dir parameter. You must specify the mountpoint when you create or run the container.


随着buildkit的引入,Dockerfile中定义VOLUME和RUN步骤的行为发生了变化。这里有两个例子。首先是Dockerfile:

$ cat df.vol-run 
FROM busybox

WORKDIR /test
VOLUME /test
RUN echo "hello" >/test/hello.txt \
 && chown -R nobody:nobody /test

其次,不使用buildkit进行构建。注意RUN步骤的更改是如何丢失的:

$ DOCKER_BUILDKIT=0 docker build -t test-vol-run -f df.vol-run .
Sending build context to Docker daemon  23.04kB
Step 1/4 : FROM busybox
 ---> beae173ccac6
Step 2/4 : WORKDIR /test
 ---> Running in aaf2c2920ebd
Removing intermediate container aaf2c2920ebd
 ---> 7960bec5b546
Step 3/4 : VOLUME /test
 ---> Running in 9e2fbe3e594b
Removing intermediate container 9e2fbe3e594b
 ---> 5895ddaede1f
Step 4/4 : RUN echo "hello" >/test/hello.txt  && chown -R nobody:nobody /test
 ---> Running in 2c6adff98c70
Removing intermediate container 2c6adff98c70
 ---> ef2c30f207b6
Successfully built ef2c30f207b6
Successfully tagged test-vol-run:latest

$ docker run -it test-vol-run /bin/sh
/test # ls -al 
total 8
drwxr-xr-x    2 root     root          4096 Mar  6 14:35 .
drwxr-xr-x    1 root     root          4096 Mar  6 14:35 ..
/test # exit

然后用buildkit进行构建。注意RUN步骤的更改是如何保存的:

$ docker build -t test-vol-run -f df.vol-run .
[+] Building 0.5s (7/7) FINISHED                                                                         
 => [internal] load build definition from df.vol-run                                                0.0s
 => => transferring dockerfile: 154B                                                                0.0s
 => [internal] load .dockerignore                                                                   0.0s
 => => transferring context: 34B                                                                    0.0s
 => [internal] load metadata for docker.io/library/busybox:latest                                   0.0s
 => CACHED [1/3] FROM docker.io/library/busybox                                                     0.0s
 => [2/3] WORKDIR /test                                                                             0.0s
 => [3/3] RUN echo "hello" >/test/hello.txt  && chown -R nobody:nobody /test                        0.4s
 => exporting to image                                                                              0.0s
 => => exporting layers                                                                             0.0s
 => => writing image sha256:8cb3220e3593b033778f47e7a3cb7581235e4c6fa921c5d8ce1ab329ebd446b6        0.0s
 => => naming to docker.io/library/test-vol-run                                                     0.0s

$ docker run -it test-vol-run /bin/sh
/test # ls -al
total 12
drwxr-xr-x    2 nobody   nobody        4096 Mar  6 14:34 .
drwxr-xr-x    1 root     root          4096 Mar  6 14:34 ..
-rw-r--r--    1 nobody   nobody           6 Mar  6 14:34 hello.txt
/test # exit

Dockerfile中的VOLUME命令是非常合法的,完全常规的,绝对可以使用,无论如何它都没有被弃用。只需要理解它。

我们使用它来指向容器中应用程序将经常写入的任何目录。我们不使用VOLUME只是因为我们想要像配置文件一样在主机和容器之间共享。

The command simply needs one param; a path to a folder, relative to WORKDIR if set, from within the container. Then docker will create a volume in its graph(/var/lib/docker) and mount it to the folder in the container. Now the container will have somewhere to write to with high performance. Without the VOLUME command the write speed to the specified folder will be very slow because now the container is using it's copy on write strategy in the container itself. The copy on write strategy is a main reason why volumes exist.

如果挂载在VOLUME命令指定的文件夹上,则该命令永远不会运行,因为VOLUME只在容器启动时执行,有点像ENV。

基本上,使用VOLUME命令,无需从外部挂载任何卷即可获得性能。数据将保存跨容器运行也没有任何外部挂载。然后当准备好时,简单地安装在它上面的东西。

一些好的用例: - - - - - -日志 -临时文件夹

一些糟糕的用例: -静态文件 ——配置 ——代码

虽然这是一个非常老的帖子,我仍然希望你可以检查最新的docker官方文档,如果你在卷和绑定挂载之间有一些困惑

绑定挂载从Docker早期就已经存在了,我认为它也不应该是一个完美的设计,比如“绑定挂载允许访问敏感文件”, 你可以得到docker官方更喜欢你使用VOLUME而不是bind mount的信息。

您可以从这里获得卷的良好用例

参考

Docker卷文档 Docker存储概述

简而言之:不,你的VOLUME指令不正确。

Dockerfile的VOLUME指定给定容器侧路径的一个或多个卷。但是它不允许映像作者指定主机路径。在主机端,卷是在Docker根目录中使用一个非常长的类似id的名称创建的。在我的机器上,这是/var/lib/docker/volumes

注意:由于自动生成的名称非常长,从人类的角度来看没有任何意义,因此这些卷通常被称为“未命名的”或“匿名的”。

你的例子中使用了a '。'字符甚至不会在我的机器上运行,无论我将圆点作为第一个参数还是第二个参数。我得到这个错误消息:

docker:来自daemon的错误响应:oci运行时错误:container_linux。Go:265:正在启动容器进程,导致“process_linux. exe”。Go:368: container init导致“open /dev/ptmx: no such file or directory”。

我知道在这一点上所说的对于试图理解VOLUME和-v的人来说可能不是很有价值,它当然不能为您试图实现的目标提供解决方案。所以,希望下面的例子能更清楚地说明这些问题。

Minitutorial:指定卷

给定这个Dockerfile:

FROM openjdk:8u131-jdk-alpine
VOLUME vol1 vol2

(对于这个小教程的结果,如果我们指定vol1 vol2或/vol1 /vol2没有区别——这是因为Dockerfile中的默认工作目录是/)

构建:

docker build -t my-openjdk

Run:

docker run --rm -it my-openjdk

在容器内部,在命令行中运行ls,您将注意到存在两个目录;/vol1和/vol2。

运行容器还会在主机端创建两个目录或“卷”。

当容器运行时,在主机上执行docker volume ls,你会看到如下内容(为了简洁起见,我用三个点替换了名称的中间部分):

DRIVER    VOLUME NAME
local     c984...e4fc
local     f670...49f0

回到容器中,执行touch /vol1/weird-ass-file(在指定位置创建一个空白文件)。

这个文件现在在主机上可用,在一个未命名的卷lol中。这花了我两次尝试,因为我第一次尝试了第一个列出的卷,但最终我在第二个列出的卷中找到了我的文件,在主机上使用以下命令:

sudo ls /var/lib/docker/volumes/f670...49f0/_data

类似地,您可以尝试在主机上删除该文件,它也会在容器中被删除。

注意:_data文件夹也被称为“挂载点”。

退出容器并列出主机上的卷。他们走了。我们在运行容器时使用了——rm标志,这个选项不仅在退出时有效地清除了容器,还清除了卷。

运行一个新的容器,但是使用-v指定一个卷:

docker run --rm -it -v /vol3 my-openjdk

这增加了第三个卷,整个系统最终有三个未命名的卷。如果我们只指定-v vol3,命令就会崩溃。参数必须是容器内的绝对路径。在主机端,新的第三个卷是匿名的,与其他两个卷一起驻留在/var/lib/docker/volumes/中。

前面说过,Dockerfile不能映射到主机路径,这给我们在运行时试图将文件从主机导入容器带来了问题。不同的-v语法可以解决这个问题。

假设我的项目目录./src中有一个子文件夹,我想把它同步到容器中的/src。这个命令很有用:

docker run -it -v $(pwd)/src:/src my-openjdk

字符的两边都需要一个绝对路径。左边是主机上的绝对路径,右边是容器内的绝对路径。PWD是“打印当前/工作目录”的命令。将命令放在$()中会得到圆括号内的命令,在子shell中运行它,并返回项目目录的绝对路径。

把所有这些放在一起,假设我们在主机上的项目文件夹中有。/src/Hello.java,内容如下:

public class Hello {
    public static void main(String... ignored) {
        System.out.println("Hello, World!");
    }
}

我们构建这个Dockerfile:

FROM openjdk:8u131-jdk-alpine
WORKDIR /src
ENTRYPOINT javac Hello.java && java Hello

我们运行这个命令:

docker run -v $(pwd)/src:/src my-openjdk

这上面印着“你好,世界!”

最好的部分是,我们完全可以在第二次运行时使用新消息修改.java文件,以获得另一个输出-而不必重新构建image =)

最后的评论

我对Docker很陌生,前面提到的“教程”反映了我从3天的命令行黑客马拉松中收集到的信息。我几乎感到羞愧,我不能提供清晰的英文文档链接来支持我的声明,但我真的认为这是由于缺乏文档,而不是个人努力。我确实知道这些例子是用我目前的设置“Windows 10 -> Vagrant 2.0.0 -> Docker 17.09.0-ce”来工作的。

本教程并没有解决“如何在Dockerfile中指定容器的路径,而让run命令只指定主机路径”的问题。也许有办法,只是我还没找到。

Finally, I have a gut feeling that specifying VOLUME in the Dockerfile is not just uncommon, but it's probably a best practice to never use VOLUME. For two reasons. The first reason we have already identified: We can not specify the host path - which is a good thing because Dockerfiles should be very agnostic to the specifics of a host machine. But the second reason is people might forget to use the --rm option when running the container. One might remember to remove the container but forget to remove the volume. Plus, even with the best of human memory, it might be a daunting task to figure out which of all anonymous volumes are safe to remove.