How to mount host volumes into docker containers in Dockerfile during build

和自甴很熟 提交于 2019-11-26 11:32:14

First, to answer "why doesn't VOLUME work?" When you define a VOLUME in the Dockerfile, you can only define the target, not the source of the volume. During the build, you will only get an anonymous volume from this. That anonymous volume will be mounted at every RUN command, prepopulated with the contents of the image, and then discarded at the end of the RUN command. Only changes to the container are saved, not changes to the volume.


Since this question has been asked, a few features have been released that may help. First is multistage builds allowing you to build a disk space inefficient first stage, and copy just the needed output to the final stage that you ship. And the second feature is Buildkit which is dramatically changing how images are built and new capabilities are being added to the build.

For a multi-stage build, you would have multiple FROM lines, each one starting the creation of a separate image. Only the last image is tagged by default, but you can copy files from previous stages. The standard use is to have a compiler environment to build a binary or other application artifact, and a runtime environment as the second stage that copies over that artifact. You could have:

FROM debian:sid as builder
COPY export /export
RUN compile command here >/result.bin

FROM debian:sid
COPY --from=builder /result.bin /result.bin
CMD ["/result.bin"]

That would result in a build that only contains the resulting binary, and not the full /export directory.


Buildkit is coming out of experimental in 18.09. It's a complete redesign of the build process, including the ability to change the frontend parser. One of those parser changes has has implemented the RUN --mount option which lets you mount a cache directory for your run commands. E.g. here's one that mounts some of the debian directories (with a reconfigure of the debian image, this could speed up reinstalls of packages):

# syntax = docker/dockerfile:experimental
FROM debian:latest
RUN --mount=target=/var/lib/apt/lists,type=cache \
    --mount=target=/var/cache/apt,type=cache \
    apt-get update \
 && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
      git

You would adjust the cache directory for whatever application cache you have, e.g. $HOME/.m2 for maven, or /root/.cache for golang.


TL;DR: Answer is here: With that RUN --mount syntax, you can also bind mount read-only directories from the build-context. The folder must exist in the build context, and it is not mapped back to the host or the build client:

# syntax = docker/dockerfile:experimental
FROM debian:latest
RUN --mount=target=/export,type=bind,source=export \
    process export directory here...

Note that because the directory is mounted from the context, it's also mounted read-only, and you cannot push changes back to the host or client. When you build, you'll want an 18.09 or newer install and enable build kit with export DOCKER_BUILDKIT=1.

It is not possible to use the VOLUME instruction to tell docker what to mount. That would seriously break portability. This instruction tells docker that content in those directories does not go in images and can be accessed from other containers using the --volumes-from command line parameter. You have to run the container using -v /path/on/host:/path/in/container to access directories from the host.

Mounting host volumes during build is not possible. There is no privileged build and mounting the host would also seriously degrade portability. You might want to try using wget or curl to download whatever you need for the build and put it in place.

xpt

UPDATE: Somebody just won't take no as the answer, and I like it, very much, especially to this particular question.

GOOD NEWS, There is a way now --

The solution is Rocker: https://github.com/grammarly/rocker

John Yani said, "IMO, it solves all the weak points of Dockerfile, making it suitable for development."

Rocker

https://github.com/grammarly/rocker

By introducing new commands, Rocker aims to solve the following use cases, which are painful with plain Docker:

  1. Mount reusable volumes on build stage, so dependency management tools may use cache between builds.
  2. Share ssh keys with build (for pulling private repos, etc.), while not leaving them in the resulting image.
  3. Build and run application in different images, be able to easily pass an artifact from one image to another, ideally have this logic in a single Dockerfile.
  4. Tag/Push images right from Dockerfiles.
  5. Pass variables from shell build command so they can be substituted to a Dockerfile.

And more. These are the most critical issues that were blocking our adoption of Docker at Grammarly.

Update: Rocker has been discontinued, per the official project repo on Github

As of early 2018, the container ecosystem is much more mature than it was three years ago when this project was initiated. Now, some of the critical and outstanding features of rocker can be easily covered by docker build or other well-supported tools, though some features do remain unique to rocker. See https://github.com/grammarly/rocker/issues/199 for more details.

There is a way to mount a volume during a build, but it doesn't involve Dockerfiles.

The technique would be to create a container from whatever base you wanted to use (mounting your volume(s) in the container with the -v option), run a shell script to do your image building work, then commit the container as an image when done.

Not only will this leave out the excess files you don't want (this is good for secure files as well, like SSH files), it also creates a single image. It has downsides: the commit command doesn't support all of the Dockerfile instructions, and it doesn't let you pick up when you left off if you need to edit your build script.

UPDATE:

For example,

CONTAINER_ID=$(docker run -dit ubuntu:16.04)
docker cp build.sh $CONTAINER_ID:/build.sh
docker exec -t $CONTAINER_ID /bin/sh -c '/bin/sh /build.sh'
docker commit $CONTAINER_ID $REPO:$TAG
docker stop $CONTAINER_ID

As you run the container, a directory on your host is created and mounted into the container. You can find out what directory this is with

$ docker inspect --format "{{ .Volumes }}" <ID>
map[/export:/var/lib/docker/vfs/dir/<VOLUME ID...>]

If you want to mount a directory from your host inside your container, you have to use the -v parameter and specify the directory. In your case this would be:

docker run -v /export:/export data

SO you would use the hosts folder inside your container.

I think you can do what you want to do by running the build via a docker command which itself is run inside a docker container. See Docker can now run within Docker | Docker Blog. A technique like this, but which actually accessed the outer docker from with a container, was used, e.g., while exploring how to Create the smallest possible Docker container | Xebia Blog.

Another relevant article is Optimizing Docker Images | CenturyLink Labs, which explains that if you do end up downloading stuff during a build, you can avoid having space wasted by it in the final image by downloading, building and deleting the download all in one RUN step.

It's ugly, but I achieved a semblance of this like so:

Dockerfile:

FROM foo
COPY ./m2/ /root/.m2
RUN stuff

imageBuild.sh:

docker build . -t barImage
container="$(docker run -d barImage)"
rm -rf ./m2
docker cp "$container:/root/.m2" ./m2
docker rm -f "$container"

I have a java build that downloads the universe into /root/.m2, and did so every single time. imageBuild.sh copies the contents of that folder onto the host after the build, and Dockerfile copies them back into the image for the next build.

This is something like how a volume would work (i.e. it persists between builds).

Here is a simplified version of the 2-step approach using build and commit, without shell scripts. It involves:

  1. Building the image partially, without volumes
  2. Running a container with volumes, making changes, then committing the result, replacing the original image name.

With relatively minor changes the additional step adds only a few seconds to the build time.

Basically:

docker build -t image-name . # your normal docker build

# Now run a command in a throwaway container that uses volumes and makes changes:
docker run -v /some:/volume --name temp-container image-name /some/post-configure/command

# Replace the original image with the result:
# (reverting CMD to whatever it was, otherwise it will be set to /some/post-configure/command)   
docker commit --change="CMD bash" temp-container image-name 

# Delete the temporary container:
docker rm temp-container

In my use case I want to pre-generate a maven toolchains.xml file, but my many JDK installations are on a volume that isn't available until runtime. Some of my images are not compatible with all the JDKS, so I need to test compatibility at build time and populate toolchains.xml conditionally. Note that I don't need the image to be portable, I'm not publishing it to Docker Hub.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!