Cat
Cat

Reputation: 483

How to mount volume from container to host in Docker?

I have a question regarding the whole data volume process in Docker. Basically here are two Dockerfiles and their respective run commands:

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"]

Command 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;"]

Command 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

So, the weird thing is that the first dockerfile and command works as intended. Where the docker daemon mounts a directory from the container to the host. So, I am able to edit the configuration files as I please and they will be persisted to the container on a restart.

However, as for the second dockerfile and command it doesn't seem to be working. I know if you go to the Docker Volume documentation it says that volume mounts are only intended to go one-way, from host-to-container, but how come the Transmission container works as intended, while the Nginx container doesn't?

P:S - I'm running Microsoft Windows 10 Pro Build 14393 as my host and Version 17.03.0-ce-win1 (10300) Channel: beta as my Docker version.

Edit - Just to clarify. I'm trying to get the files from inside the Nginx container to the host. The first container (Transmission) works in that regard, by using a data volume. However, for the second container (Nginx), it doesn't want to copy the files in the mounted directory from inside the container to the host. Everything else is working though, it does successfully start.

Upvotes: 36

Views: 75767

Answers (3)

Matt
Matt

Reputation: 74670

Bind mounts don't copy data from the container > host. Host volumes mount over the top of what's in the container/image, so they effectively replace what's in the container with what's on the host.

docker run -v /path/to/hostdir:/var/whatever myimage

A standard or "named" volume will copy the existing data from the container image when the volume is first created. These volumes are created by launching a container with the VOLUME command in it's Dockerfile or with the --volume or --mount docker command options specifying a name instead of a path.

docker run -v myvolume:/var/whatever myimage

By default this is data stored in a "local" volume, "local" being on the Docker host. In your case that is on the VM running Docker rather than your Windows host so might not be easily accessible to you.

You could be mistaking transmission auto generating files in a blank directory for a copy?

If you really need the keep the VM Host > container mappings then you might have to copy the data manually:

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

And then you can map the populated host directories into the container and they will have the default data the image came with.

Upvotes: 36

Egeshi
Egeshi

Reputation: 114

I faced the same issue and bugs while trying to expose the container directory which is being generated in Dockerfile. Following Docker behavior, it is overwritten by the contents of the folder mounted from the host system. Since an empty folder is mounted, its content replaces the one the folder had when an image was built. From logging inside the Dockerfile I know that files were definitely generated upon image build.

Other authors probably explained in detail how it all works so my advice is just a practical approach. I see an advantage is that both solutions are programmatic and can be executed at some runtime.

  1. Comment/remove your mount in docker-compose.yml.
  2. Run the container to generate files.
  3. Get the container ID from docker ps.
$ 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}')
  1. Create a symlink with <symlink_path>, replacing <volume_folder> with the path of the volume folder inside the container:
# 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>

This did not work for me in fact on WSL2 :). I cannot find any of my container files or directories under /var/lib/docker/overlays2/ or anywhere else specified by docker inspect.

So I tried another dumb approach. "The dumber the better" often works with Docker as I notice from my practice.

  1. Use Dockerfile to generate files in some temporary directory in your container that will exist after the restart.
  2. Add an entrypoint.sh script (or a line to it if exists) with the command to copy files on container start. cp -rf /temp/stored/files/ /path/to/mount/.
  3. After the container is started, directory /path/to/mount will be available for the host system.

Also on WSL, you can find bind mounts under /mnt/wsl/docker-desktop-bind-mounts/Ubuntu-22.04/

Upvotes: 1

BMitch
BMitch

Reputation: 263617

The host volume will not copy data like a named volume will. However, you can create a named volume that performs a bind mount, which will then have the data initialization properties of any other named volume. The only prerequisite of a bind mount over a host volume is that the directory must exist in advance, docker will not create it for you like it does with a host volume. Here are three different examples of how to create a bind mount volume:

      # 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
      ...

So in your example with a docker run command, you can use the mount syntax:

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

The only part that may need adjusting is the windows path names inside the Linux VM that docker runs in HyperV.

Upvotes: 32

Related Questions