如何在Docker中将容器卷挂载到主机上?

28

关于Docker中完整数据卷流程,我有一个问题。基本上这里有两个Dockerfiles和它们各自的运行命令:

Dockerfile 1 -

# Transmission over Debian
#
# Version 2.92

FROM debian:testing

RUN apt-get update \
    && apt-get -y install nano \
    && apt-get -y install transmission-daemon transmission-common transmission-cli \
    && mkdir -p /transmission/config /transmission/watch /transmission/download

ENTRYPOINT ["transmission-daemon", "--foreground"]
CMD ["--config-dir", "/transmission/config", "--watch-dir", "/transmission/watch", "--download-dir", "/transmission/download", "--allowed", "*", "--no-blocklist", "--no-auth", "--no-dht", "--no-lpd", "--encryption-preferred"]

命令1 -

docker run --name transmission -d -p 9091:9091 -v C:\path\to\config:/transmission/config -v C:\path\to\watch:/transmission/watch -v C:\path\to\download:/transmission/download transmission  

Dockerfile 2 -

# Nginx over Debian
#
# Version 1.10.3

FROM debian:testing

RUN apt-get update \
    && apt-get -y install nano \
    && apt-get -y install nginx

EXPOSE 80 443

CMD ["nginx", "-g", "daemon off;"]

命令2 -

docker run --name nginx -d -p 80:80 -v C:\path\to\config:/etc/nginx -v C:\path\to\html:/var/www/html nginx

奇怪的是,第一个Dockerfile和命令按预期工作。其中Docker守护进程将容器中的目录挂载到主机上。所以我可以随意编辑配置文件,并在重新启动时保存到容器中。

然而,对于第二个Dockerfile和命令,它似乎没有正常工作。我知道如果去查看Docker卷文档,会发现卷挂载只能从主机到容器单向传输,但为什么Transmission容器可以按预期工作,而Nginx容器不能呢?

P.S. - 我正在运行Microsoft Windows 10 Pro Build 14393作为我的主机,以及Version 17.03.0-ce-win1 (10300) Channel: beta作为我的Docker版本。

编辑 - 仅澄清一下。我正在尝试将Nginx容器内部的文件复制到主机。第一个容器(Transmission)在这方面起作用,使用数据卷。然而,对于第二个容器(Nginx),它不想将挂载目录中的文件从容器内部复制到主机。但其他所有东西都有效,它成功启动了。


“不起作用”指的是什么?容器无法启动吗?本地文件出现在挂载目录中但没有更新吗?挂载目录中是否有任何内容? - Matt
@Matt 我试图将Nginx容器内的文件获取到主机上。第一个容器(Transmission)在这方面是有效的,使用了数据卷。然而,对于第二个容器(Nginx),它不想将容器内挂载目录中的文件复制到主机上。虽然其他所有东西都在工作,但它成功地启动了。 - Cat
请更新您的问题并提供这些信息。 - Klaus D.
3个回答

32
绑定挂载不会从容器>主机复制数据。主机卷会覆盖容器/镜像中的内容,因此它们实际上用主机上的内容替换了容器中的内容。
docker run -v /path/to/hostdir:/var/whatever myimage

标准或“命名”卷 在创建卷时从容器镜像中复制现有数据。这些卷是通过在其Dockerfile中使用VOLUME命令,或者使用--volume--mount Docker命令选项指定名称而不是路径来启动容器创建的。

docker run -v myvolume:/var/whatever myimage

默认情况下,这些数据存储在“本地”卷中,“本地”指的是Docker主机上。在您的情况下,它位于运行Docker的虚拟机上,而不是您的Windows主机,因此可能不容易访问。
您可能会错误地将传输自动生成的文件误认为是副本中的空目录?
如果您确实需要保留虚拟机主机 > 容器映射,则可能需要手动复制数据:
docker create --name nginxcopy nginx
docker cp nginxcopy:/etc/nginx C:\path\to\config
docker cp nginxcopy:/var/www/html C:\path\to\html
docker rm nginxcopy

然后,您可以将已填充的主机目录映射到容器中,它们将具有镜像默认的数据。

你能更详细地解释一下标准卷吗?“将容器镜像中的现有数据复制到新卷中”是什么意思? - Cat
我扩展了这一部分。基本上,当Docker为Dockerfile VOLUMEdocker run -v volume:/wherever创建新卷时,它将使用容器映像中目标挂载点(/wherever)中已经存在的任何内容填充卷。这是您要寻找的行为,但您无法在主机上指定位置,并且它不映射到Windows。 - Matt
是的,没错。简单来说,当您使用Transmission Dockerfile创建一个新的容器时,它会在映射到主机目录中的新配置中创建一个新的配置。我完全理解您复制文件和挂载主机卷的逻辑。然而,Transmission容器确实让我很困扰。总之,Transmission容器表现出这种行为的特殊原因是什么?我能否将其复制到Nginx容器中呢? - Cat
你提到的"主机卷"是指与绑定挂载相同吗? - Andrei
1
@Andrei 是的,已更新。 - Matt
显示剩余5条评论

31

宿主机卷不会像命名卷一样复制数据。但是,您可以创建一个执行绑定挂载的命名卷,这将具有任何其他命名卷的数据初始化属性。绑定挂载比宿主机卷的唯一先决条件是该目录必须事先存在,Docker不会像它在宿主机卷上所做的那样为您创建它。以下是创建绑定挂载卷的三个不同示例:

      # create the volume in advance
      $ docker volume create --driver local \
          --opt type=none \
          --opt device=/home/user/test \
          --opt o=bind \
          test_vol
    
      # create on the fly with --mount
      $ docker run -it --rm \
        --mount type=volume,dst=/container/path,volume-driver=local,volume-opt=type=none,volume-opt=o=bind,volume-opt=device=/home/user/test \
        foo
      # inside a docker-compose file
      ...
      volumes:
        bind-test:
          driver: local
          driver_opts:
            type: none
            o: bind
            device: /home/user/test
      ...

所以在您举的docker run命令示例中,可以使用挂载语法:

docker run --name nginx -d -p 80:80 \
  --mount type=volume,dst=/etc/nginx,volume-driver=local,volume-opt=type=none,volume-opt=o=bind,volume-opt=device=/c/path/to/config \
  --mount type=volume,dst=/var/www/html,volume-driver=local,volume-opt=type=none,volume-opt=o=bind,volume-opt=device=/c/path/to/html \
  nginx

唯一需要调整的部分是Linux虚拟机中窗口路径名称,因为Docker在HyperV中运行。


0
我在尝试暴露在Dockerfile中生成的容器目录时遇到了相同的问题和错误。根据Docker的行为,它会被来自主机系统挂载的文件夹的内容覆盖。由于挂载了一个空文件夹,当构建镜像时,它的内容会替换掉原本文件夹中的内容。通过在Dockerfile内部记录日志,我知道文件确实是在构建镜像时生成的。
其他作者可能已经详细解释了所有的工作原理,所以我的建议只是一种实用的方法。我认为两种解决方案都具有编程性质,并且可以在运行时执行。
  1. docker-compose.yml中注释或删除挂载。
  2. 运行容器以生成文件。
  3. docker ps获取容器ID。
$ container_name="<myca>" # Replace with your container name
$ container_id=$(docker ps --format '{{.ID}}\t{{.Names}}' | awk -v name="$container_name" '$2 == name {print $1}')

使用<symlink_path>创建一个符号链接,将<volume_folder>替换为容器内卷文件夹的路径。
# Get container root directory path
$ container_root=$(sudo docker inspect --format '{{.GraphDriver.Data.MergedDir}}' <container_id>)
# Create a symlink to any subdirectory or file within the container
$ sudo ln -s "$container_root/</path/to/mount>" <symlink_name>

这对我来说在WSL2上并没有起作用:)。我无法在/var/lib/docker/overlays2/docker inspect指定的任何其他位置找到我的容器文件或目录。
所以我尝试了另一种愚蠢的方法。从我的实践中,我注意到“越愚蠢越好”通常在Docker中起作用。
  1. 使用Dockerfile在容器中的某个临时目录生成文件。
  2. 添加一个entrypoint.sh脚本(如果存在,则添加一行),其中包含在容器启动时复制文件的命令:cp -rf /temp/stored/files/ /path/to/mount/
  3. 容器启动后,主机系统将可以访问/path/to/mount目录。
在WSL上,您还可以在/mnt/wsl/docker-desktop-bind-mounts/Ubuntu-22.04/下找到绑定挂载点。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接