Cómo montar volúmenes de host en contenedores docker en Dockerfile durante la compilación

236

Pregunta original: ¿Cómo usar la instrucción VOLUME en Dockerfile?

La pregunta real que quiero resolver es: cómo montar volúmenes de host en contenedores docker en Dockerfile durante la compilación, es decir, tener la docker run -v /export:/exportcapacidad durante docker build.

La razón detrás de esto, para mí, es cuando construyo cosas en Docker, no quiero esas apt-get installcachés ( ) encerradas en un solo docker, sino compartirlas / reutilizarlas. Esa es la razón principal por la que hago esta pregunta.

Última actualización:

Antes de docker v18.09, la respuesta correcta debería ser la que comienza con:

Hay una manera de montar un volumen durante una compilación, pero no involucra Dockerfiles.

Sin embargo, esa fue una respuesta mal planteada, organizada y respaldada. Cuando estaba reinstalando mi docker contiene, me encontré con el siguiente artículo:

Dockerize un servicio apt-cacher-ng
https://docs.docker.com/engine/examples/apt-cacher-ng/

Esa es la solución de Docker para esta / mi pregunta, no directa sino indirectamente. Es la forma ortodoxa que Docker sugiere que hagamos. Y admito que es mejor que el que estaba tratando de preguntar aquí.

Otra forma es la respuesta recién aceptada , por ejemplo, el Buildkit en v18.09.

Elige lo que más te convenga.


Era: había habido una solución: rocker, que no era de Docker, pero ahora que el rocker ha sido descontinuado, revierto la respuesta a "No es posible" nuevamente.


Actualización anterior: Entonces la respuesta es "No es posible". Puedo aceptarlo como respuesta, ya que sé que el problema se ha discutido ampliamente en https://github.com/docker/docker/issues/3156 . Puedo entender que la portabilidad es un problema primordial para el desarrollador de Docker; pero como usuario de Docker, debo decir que estoy muy decepcionado con esta característica que falta. Permítanme cerrar mi argumento con una cita de la discusión antes mencionada: " Me gustaría usar Gentoo como imagen base, pero definitivamente no quiero que> 1 GB de datos del árbol de Portage estén en ninguna de las capas una vez que la imagen ha sido construida. podría tener algunos buenos contenedores compactos si no fuera por el gigantesco árbol de portage que tiene que aparecer en la imagen durante la instalación."Sí, puedo usar wget o curl para descargar lo que necesito, pero el hecho de que solo una consideración de portabilidad me está obligando a descargar> 1 GB de árbol de Portage cada vez que construyo una imagen base de Gentoo no es eficiente ni fácil de usar. Además más aún, el repositorio de paquetes SIEMPRE estará bajo / usr / portage, por lo tanto SIEMPRE PORTÁTIL bajo Gentoo. Nuevamente, respeto la decisión, pero permítame expresar mi decepción también mientras tanto. Gracias.


Pregunta original en detalles:

De

Compartir directorios a través de volúmenes
http://docker.readthedocs.org/en/v0.7.3/use/working_with_volumes/

dice que la función de volúmenes de datos "ha estado disponible desde la versión 1 de la API remota de Docker". Mi docker es de la versión 1.2.0, pero encontré que el ejemplo dado en el artículo anterior no funciona:

# BUILD-USING:        docker build -t data .
# RUN-USING:          docker run -name DATA data
FROM          busybox
VOLUME        ["/var/volume1", "/var/volume2"]
CMD           ["/usr/bin/true"]

¿Cuál es la forma correcta en Dockerfile de montar volúmenes montados en host en contenedores de docker, a través del comando VOLUME?

$ apt-cache policy lxc-docker
lxc-docker:
  Installed: 1.2.0
  Candidate: 1.2.0
  Version table:
 *** 1.2.0 0
        500 https://get.docker.io/ubuntu/ docker/main amd64 Packages
        100 /var/lib/dpkg/status

$ cat Dockerfile 
FROM          debian:sid

VOLUME        ["/export"]
RUN ls -l /export
CMD ls -l /export

$ docker build -t data .
Sending build context to Docker daemon  2.56 kB
Sending build context to Docker daemon 
Step 0 : FROM          debian:sid
 ---> 77e97a48ce6a
Step 1 : VOLUME        ["/export"]
 ---> Using cache
 ---> 59b69b65a074
Step 2 : RUN ls -l /export
 ---> Running in df43c78d74be
total 0
 ---> 9d29a6eb263f
Removing intermediate container df43c78d74be
Step 3 : CMD ls -l /export
 ---> Running in 8e4916d3e390
 ---> d6e7e1c52551
Removing intermediate container 8e4916d3e390
Successfully built d6e7e1c52551

$ docker run data
total 0

$ ls -l /export | wc 
     20     162    1131

$ docker -v
Docker version 1.2.0, build fa7b24f
xpt
fuente
Aparentemente una solicitud de características más actual (no es que espere que se implemente, pero por si acaso): docker / docker # 14080
Jesse Glick
de hecho, hay una discusión extensa de que no debería permitirse vincular un directorio de host y un directorio de contenedor durante la compilación, es decir, algo así VOLUME ~/host_dir ~/container_dir. La discusión es bastante extensa, ¿hay alguna forma breve de resumir cuál es la razón?
Charlie Parker

Respuestas:

34

Primero, para responder "¿por qué no VOLUMEfunciona?" Cuando define un VOLUMEen el Dockerfile, solo puede definir el destino, no la fuente del volumen. Durante la compilación, solo obtendrá un volumen anónimo de esto. Ese volumen anónimo se montará en cada RUNcomando, se rellenará previamente con el contenido de la imagen y luego se descartará al final del RUNcomando. Solo se guardan los cambios en el contenedor, no los cambios en el volumen.


Desde que se hizo esta pregunta, se han lanzado algunas características que pueden ayudar. Primero, las compilaciones de varias etapas que le permiten construir una primera etapa ineficiente de espacio en disco y copiar solo la salida necesaria a la etapa final que envía. Y la segunda característica es Buildkit, que está cambiando drásticamente cómo se crean las imágenes y se agregan nuevas capacidades a la construcción.

Para una compilación de varias etapas, tendría varias FROMlíneas, cada una de las cuales comenzaría la creación de una imagen separada. Solo la última imagen está etiquetada de forma predeterminada, pero puede copiar archivos de etapas anteriores. El uso estándar es tener un entorno de compilador para construir un artefacto de aplicación binario u otro, y un entorno de tiempo de ejecución como la segunda etapa que copia sobre ese artefacto. Podrías tener:

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

Eso daría como resultado una compilación que solo contiene el binario resultante, y no el directorio completo / exportar.


Buildkit está saliendo de experimental en 18.09. Es un rediseño completo del proceso de compilación, incluida la capacidad de cambiar el analizador frontend. Uno de esos cambios en el analizador ha implementado la RUN --mountopción que le permite montar un directorio de caché para sus comandos de ejecución. Por ejemplo, aquí hay uno que monta algunos de los directorios de Debian (con una reconfiguración de la imagen de Debian, esto podría acelerar la reinstalación de paquetes):

# 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

Debería ajustar el directorio de caché para cualquier caché de aplicación que tenga, por ejemplo, $ HOME / .m2 para maven o /root/.cache para golang.


TL; DR: la respuesta está aquí: con esa RUN --mountsintaxis, también puede vincular directorios de solo lectura de montaje desde el contexto de compilación. La carpeta debe existir en el contexto de compilación y no está asignada de nuevo al host o al cliente de compilación:

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

Tenga en cuenta que debido a que el directorio está montado desde el contexto, también está montado como de solo lectura, y no puede enviar los cambios al host o al cliente. Cuando construyas, querrás una instalación 18.09 o posterior y habilitar el kit de compilación con export DOCKER_BUILDKIT=1.

Si recibe un error de que el indicador de montaje no es compatible, eso indica que no activó el kit de compilación con la variable anterior o que no activó la sintaxis experimental con la línea de sintaxis en la parte superior del Dockerfile antes cualquier otra línea, incluidos los comentarios. Tenga en cuenta que la variable para alternar buildkit solo funcionará si su instalación de Docker tiene incorporado el soporte de buildkit, que requiere la versión 18.09 o posterior de Docker, tanto en el cliente como en el servidor.

BMitch
fuente
2
Desafortunadamente, en Windows Buildkit aún no es compatible con la versión 18.09
Wesley
1
Parece que "armhf" tampoco admite "montaje".
Mike
2
Recibo "Respuesta de error del daemon: Dockerfile analiza la línea de error xx: Bandera desconocida: monte" en OSX
ChristoKiwi
1
El soporte para docker-compose aún no existe, pero no es necesario componer para crear imágenes. Problema a seguir: github.com/moby/buildkit/issues/685
BMitch
2
Documentación sobre esto: github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/…
Drew LeSueur
116

No es posible usar las VOLUMEinstrucciones para decirle a Docker qué montar. Eso seriamente rompería la portabilidad. Esta instrucción le dice a Docker que el contenido de esos directorios no va en imágenes y se puede acceder desde otros contenedores utilizando el --volumes-fromparámetro de línea de comando. Debe ejecutar el contenedor -v /path/on/host:/path/in/containerpara acceder a los directorios desde el host.

No es posible montar volúmenes de host durante la compilación. No hay una construcción privilegiada y el montaje del host también degradaría seriamente la portabilidad. Es posible que desee intentar usar wget o curl para descargar lo que necesite para la compilación y ponerlo en su lugar.

Andreas Steffan
fuente
2
Gracias. Pregunta revisada. La pregunta real que quiero resolver es: cómo montar volúmenes de host en contenedores docker en Dockerfile durante la compilación. Gracias.
xpt
2
Imposible. Ver respuesta revisada.
Andreas Steffan
3
Puedo apreciar los efectos secundarios "potenciales" de la portabilidad, pero también existe un caso de uso válido para tener esta opción. En mi caso, me encantaría poder decirles a los usuarios que "se muevan al directorio y ejecuten el comando 'docker run'" con $ (PWD) montado en algún directorio contenedor. $ (PWD) asegura que se mantenga la portabilidad. Si bien esto puede ser un caso arrinconado, me ayudaría inmensamente donde estoy distribuyendo entornos de tiempo de ejecución para scripts proporcionados por el usuario.
ntwrkguru
64

ACTUALIZACIÓN: Alguien simplemente no tomará un no como respuesta, y me gusta mucho, especialmente a esta pregunta en particular.

BUENAS NOTICIAS, ahora hay un camino:

La solución es Rocker: https://github.com/grammarly/rocker

John Yani dijo : "OMI, resuelve todos los puntos débiles de Dockerfile, lo que lo hace adecuado para el desarrollo".

Balancín

https://github.com/grammarly/rocker

Al introducir nuevos comandos, Rocker tiene como objetivo resolver los siguientes casos de uso, que son dolorosos con Docker:

  1. Monte volúmenes reutilizables en la etapa de compilación, por lo que las herramientas de administración de dependencias pueden usar caché entre compilaciones.
  2. Comparta claves ssh con build (para extraer repositorios privados, etc.), sin dejarlas en la imagen resultante.
  3. Cree y ejecute aplicaciones en diferentes imágenes, pueda pasar fácilmente un artefacto de una imagen a otra, idealmente tenga esta lógica en un único Dockerfile.
  4. Etiquete / empuje imágenes directamente desde Dockerfiles.
  5. Pase las variables del comando de compilación de shell para que puedan sustituirse por un Dockerfile.

Y más. Estos son los problemas más críticos que estaban bloqueando nuestra adopción de Docker en Grammarly.

Actualización: Rocker ha sido descontinuado, según el repositorio oficial del proyecto en Github

A principios de 2018, el ecosistema de contenedores es mucho más maduro que hace tres años cuando se inició este proyecto. Ahora, algunas de las características críticas y sobresalientes de rocker se pueden cubrir fácilmente con Docker Build u otras herramientas bien compatibles, aunque algunas características siguen siendo exclusivas de Rocker. Consulte https://github.com/grammarly/rocker/issues/199 para obtener más detalles.

xpt
fuente
Estoy tratando de usar Rocker para resolver el problema número 1, pero el comando de montaje no funcionará y la imagen creada no contiene la carpeta del host. Mi comando de montaje Dockerfile se ve así, MOUNT ~/code/docker-app-dev/new-editor/:/src/y mi comando de construcción Rocker es este rocker build -f Dockerfile .. ¿Qué estoy haciendo mal?
Yaron Idan
Tal vez intente usar una ruta de host real? ~es un metacarácter de shell Bourne.
Jesse Glick
Rocker buildno permite docker runopciones de línea de comandos, por lo que actualmente no permite cosas como --privileged.
Monty Wild
Hola @xpt, ¿podemos obtener otra actualización ya que el rocker ahora está descontinuado
Shardj
Ahora que se ha descontinuado el rockero, vuelvo a la respuesta a "No es posible" nuevamente. Ver OP y la respuesta seleccionada.
xpt
14

Hay una manera de montar un volumen durante una compilación, pero no involucra Dockerfiles.

La técnica sería crear un contenedor a partir de la base que quisiera usar (montar sus volúmenes en el contenedor con la -vopción), ejecutar un script de shell para hacer su trabajo de creación de imágenes, y luego confirmar el contenedor como una imagen cuando haya terminado .

Esto no solo dejará de lado el exceso de archivos que no desea (también es bueno para archivos seguros, como los archivos SSH), sino que también crea una sola imagen. Tiene inconvenientes: el comando commit no admite todas las instrucciones de Dockerfile, y no le permite retomarlo cuando lo dejó si necesita editar su script de compilación.

ACTUALIZAR:

Por ejemplo,

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
Keith Mason
fuente
66
+1 ¿Podría por favor elaborar un poco más sobre las instrucciones en el segundo párrafo? Por ejemplo, si la base es debian:wheezyy el script de shell es build.sh, ¿qué instrucciones específicas se usarían?
Drux
6

A medida que ejecuta el contenedor, se crea un directorio en su host y se monta en el contenedor. Puedes averiguar con qué directorio es esto

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

Si desea montar un directorio desde su host dentro de su contenedor, debe usar el -vparámetro y especificar el directorio. En su caso, esto sería:

docker run -v /export:/export data

Así que usarías la carpeta hosts dentro de tu contenedor.

Behe
fuente
1
Gracias. Pregunta revisada. La pregunta real que quiero resolver es: cómo montar volúmenes de host en contenedores docker en Dockerfile durante la compilación. Gracias.
xpt
Por favor, no revise sus preguntas de manera tan drástica . Esto hace que mi pregunta no sea válida, aunque era perfectamente válida antes de sus ediciones. Considere hacer una nueva pregunta en su lugar.
Behe
11
Pregunta original : ¿Cómo usar la instrucción VOLUME en Dockerfile? Todavía está en el comienzo de la pregunta, incluso a partir de hoy. Su respuesta fue para el tiempo de ejecución , y mi pregunta siempre ha sido sobre el tiempo de compilación , que es para qué sirve Dockerfile.
xpt
4

Creo que puede hacer lo que quiere haciendo ejecutando la compilación a través de un comando docker que se ejecuta dentro de un contenedor docker. Ver Docker ahora puede ejecutarse dentro de Docker | Blog Docker . Se utilizó una técnica como esta, pero que en realidad accedía a la ventana acoplable externa desde un contenedor, por ejemplo, al explorar cómo crear el contenedor Docker más pequeño posible | Blog de Xebia .

Otro artículo relevante es Optimizing Docker Images | CenturyLink Labs , que explica que si termina descargando cosas durante una compilación, puede evitar que se desperdicie espacio en la imagen final descargando, compilando y eliminando la descarga todo en un solo paso de EJECUTAR.

nealmcb
fuente
3

Es feo, pero logré una apariencia de esto así:

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"

Tengo una compilación de Java que descarga el universo en /root/.m2, y lo hice cada vez . imageBuild.shcopia el contenido de esa carpeta en el host después de la compilación y los Dockerfilecopia nuevamente en la imagen para la próxima compilación.

Esto es algo como cómo funcionaría un volumen (es decir, persiste entre compilaciones).

MatrixManAtYrService
fuente
Esta es una solución viable para la integración continua basada en Docker, también conocida como CI. Configure las bibliotecas y los compiladores y ejecute make mediante los comandos de Dockerfile, inicie la imagen trivialmente solo para crear un contenedor y, finalmente, copie el artefacto deseado como un .deb. Parece funcionar, gracias por publicar esto.
chrisinmtown
Esta solución le deja una imagen con TODOS los archivos en ./m2/, el que necesita y el que no necesita, y esto puede conducir a ENORMES imágenes de producción, ¡lo que no se desea! Con el montaje en el directorio de dependencias externas, solo los archivos necesarios se copiarán en la imagen.
Marko Krajnc
Si tiene la intención de publicar la imagen, probablemente sea mejor esperar y dejar que Maven descargue sus propias dependencias nuevamente cada vez. Este truco solo tiene sentido si está preparando una imagen para probarla, una imagen con la que los usuarios finales nunca entrarán en contacto.
MatrixManAtYrService
1

Aquí hay una versión simplificada del enfoque de 2 pasos usando build y commit, sin scripts de shell. Implica:

  1. Construyendo la imagen parcialmente, sin volúmenes
  2. Ejecutar un contenedor con volúmenes , realizar cambios, luego confirmar el resultado y reemplazar el nombre de la imagen original.

Con cambios relativamente menores, el paso adicional agrega solo unos segundos al tiempo de construcción.

Básicamente:

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

En mi caso de uso, quiero generar previamente un archivo maven toolchains.xml, pero mis muchas instalaciones JDK están en un volumen que no está disponible hasta el tiempo de ejecución. Algunas de mis imágenes no son compatibles con todos los JDKS, por lo que necesito probar la compatibilidad en el momento de la compilación y rellenar toolchains.xml condicionalmente. Tenga en cuenta que no necesito que la imagen sea portátil, no la publico en Docker Hub.

Akom
fuente
1

Como muchos ya han respondido, no es posible montar volúmenes de host durante la compilación. Solo me gustaría agregar una docker-composeforma, creo que será bueno tenerla, principalmente para uso de desarrollo / prueba

Dockerfile

FROM node:10
WORKDIR /app
COPY . .
RUN npm ci
CMD sleep 999999999

docker-compose.yml

version: '3'
services:
  test-service:
    image: test/image
    build:
      context: .
      dockerfile: Dockerfile
    container_name: test
    volumes:
      - ./export:/app/export
      - ./build:/app/build

Y corre tu contenedor por docker-compose up -d --build

Yegor Zaremba
fuente