Como montar volumes de host em contêineres do Dockerfile durante a compilação

236

Pergunta original: Como usar a instrução VOLUME no Dockerfile?

A questão real que quero resolver é: como montar volumes de host em contêineres do Dockerfile durante a compilação, ou seja, ter o docker run -v /export:/export capacidade durante docker build.

A razão por trás disso, para mim, é ao criar coisas no Docker, não quero isso (apt-get install caches ) sejam bloqueados em uma única janela de encaixe, mas para compartilhá-los / reutilizá-los. Essa é a principal razão pela qual estou perguntando sobre essa pergunta.

Última atualização:

Antes da janela de encaixe v18.09, a resposta correta deve ser a que começa com:

Existe uma maneira de montar um volume durante uma compilação, mas isso não envolve os Dockerfiles.

No entanto, essa foi uma resposta mal declarada, organizada e apoiada. Quando eu estava reinstalando minha janela de encaixe, encontrei o seguinte artigo:

Dockerize um serviço apt-cacher-ng
https://docs.docker.com/engine/examples/apt-cacher-ng/

Essa é a solução do docker para esta / minha pergunta, não direta mas indiretamente. É a maneira ortodoxa que o docker nos sugere. E admito que é melhor do que o que eu estava tentando perguntar aqui.

Outra maneira é a resposta recém-aceita , por exemplo, o Buildkit na v18.09.

Escolha o que mais lhe convier.


Foi: Houve uma solução - rocker, que não era da Docker, mas agora que o rocker foi descontinuado, eu reverto a resposta novamente para "Não é possível" novamente.


Atualização antiga: Portanto, a resposta é "Não é possível". Posso aceitá-lo como resposta, pois sei que o problema foi amplamente discutido em https://github.com/docker/docker/issues/3156 . Entendo que a portabilidade é um problema primordial para o desenvolvedor de docker; mas como usuário do docker, devo dizer que estou muito desapontado com esse recurso ausente. Deixe-me encerrar meu argumento com uma citação da discussão acima: " Gostaria de usar o Gentoo como uma imagem de base, mas definitivamente não quero que mais de 1 GB de dados da árvore do Portage estejam em qualquer uma das camadas depois que a imagem for criada. poderia ter alguns recipientes compactos e agradáveis, se não fosse pela gigantesca árvore do portage que aparecesse na imagem durante a instalação."Sim, eu posso usar o wget ou curl para baixar o que precisar, mas o fato de que apenas uma consideração de portabilidade está me forçando a baixar> 1 GB da árvore do Portage cada vez que eu construo uma imagem base do Gentoo não é eficiente nem amigável. além disso, o repositório de pacotes SEMPRE estará em / usr / portage, portanto SEMPRE PORTÁTIL no Gentoo. Mais uma vez, eu respeito a decisão, mas permita-me expressar minha decepção também nesse meio tempo.


Pergunta original em detalhes:

De

Compartilhar diretórios por volumes
http://docker.readthedocs.org/en/v0.7.3/use/working_with_volumes/

ele diz que o recurso de volumes de dados "está disponível desde a versão 1 da API remota do Docker". Minha janela de encaixe é da versão 1.2.0, mas achei o exemplo dado no artigo acima não funcionando:

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

Qual é a maneira correta no Dockerfile de montar volumes montados no host em contêineres do docker, através do 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
fonte
Aparentemente mais atual pedido de recurso (não que eu espero que ele seja implementado, mas apenas no caso): janela de encaixe / janela de encaixe # 14080
Jesse Glick
de fato, há uma discussão extensa de que não deve ser permitido vincular um diretório host e um diretório de contêiner durante a compilação, ou seja, algo parecido VOLUME ~/host_dir ~/container_dir. A discussão é bastante extensa, e existe uma maneira curta de resumir qual é o motivo?
Charlie Parker

Respostas:

34

Primeiro, para responder "por que não VOLUMEfunciona?" Quando você define a VOLUMEno Dockerfile, é possível definir apenas o destino, não a origem do volume. Durante a construção, você receberá apenas um volume anônimo. Esse volume anônimo será montado a cada RUNcomando, preenchido previamente com o conteúdo da imagem e descartado no final do RUNcomando. Somente alterações no contêiner são salvas, não alterações no volume.


Como essa pergunta foi feita, alguns recursos foram lançados que podem ajudar. O primeiro é a compilação de vários estágios, permitindo que você crie um primeiro estágio ineficiente em espaço em disco e copie apenas a saída necessária para o estágio final que você envia. E o segundo recurso é o Buildkit, que está mudando drasticamente a maneira como as imagens são construídas e os novos recursos sendo adicionados à construção.

Para uma construção de vários estágios, você teria várias FROMlinhas, cada uma iniciando a criação de uma imagem separada. Somente a última imagem é marcada por padrão, mas você pode copiar arquivos dos estágios anteriores. O uso padrão é ter um ambiente de compilador para construir um artefato binário ou outro aplicativo e um ambiente de tempo de execução como o segundo estágio que copia esse artefato. Você pode ter:

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

Isso resultaria em uma construção que contém apenas o binário resultante, e não o diretório completo / export.


O Buildkit está saindo do experimental em 18.09. É uma reformulação completa do processo de compilação, incluindo a capacidade de alterar o analisador de front-end. Uma dessas alterações no analisador implementou a RUN --mountopção que permite montar um diretório de cache para seus comandos de execução. Por exemplo, aqui está um que monta alguns dos diretórios debian (com uma reconfiguração da imagem debian, isso pode acelerar a reinstalação de pacotes):

# 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

Você ajustaria o diretório de cache para qualquer cache de aplicativo que possua, por exemplo, $ HOME / .m2 para maven ou /root/.cache para golang.


TL; DR: A resposta está aqui: Com essa RUN --mountsintaxe, você também pode vincular diretórios somente leitura de montagem a partir do contexto de construção. A pasta deve existir no contexto de construção e não é mapeada de volta para o host ou o cliente de construção:

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

Observe que, como o diretório é montado a partir do contexto, ele também é montado como somente leitura, e você não pode enviar as alterações de volta ao host ou cliente. Ao criar, você desejará uma instalação 18.09 ou mais recente e habilitar o buildkit com export DOCKER_BUILDKIT=1.

Se você receber um erro de que o sinalizador mount não é suportado, isso indica que você não ativou o buildkit com a variável acima ou que não ativou a sintaxe experimental com a linha de sintaxe na parte superior do Dockerfile antes quaisquer outras linhas, incluindo comentários. Observe que a variável para alternar o buildkit só funcionará se a instalação do docker tiver suporte para buildkit embutido, o que requer a versão 18.09 ou mais recente do Docker, no cliente e no servidor.

BMitch
fonte
2
Infelizmente, no Windows Buildkit ainda não é suportado na versão 18.09
Wesley
1
Parece que "armhf" também não suporta "mount".
Mike
2
Estou recebendo "Resposta de erro do daemon: linha de erro de análise do Dockerfile xx: Sinalizador desconhecido: mount" no OSX
ChristoKiwi
1
O suporte para docker-compose ainda não existe, mas você não precisa de composição para criar imagens. Problema para rastrear: github.com/moby/buildkit/issues/685
BMitch 18/01/19
2
Documentação sobre isso: github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/…
Drew LeSueur
116

Não é possível usar as VOLUMEinstruções para informar ao docker o que montar. Isso quebraria seriamente a portabilidade. Esta instrução informa ao estivador que o conteúdo desses diretórios não entra em imagens e pode ser acessado de outros contêineres usando o --volumes-fromparâmetro de linha de comando. Você precisa executar o contêiner -v /path/on/host:/path/in/containerpara acessar diretórios do host.

A montagem de volumes do host durante a construção não é possível. Não há construção privilegiada e a montagem do host também degradaria seriamente a portabilidade. Você pode tentar usar o wget ou curl para baixar o que for necessário para a compilação e colocá-lo no lugar.

Andreas Steffan
fonte
2
Obrigado. Pergunta revisada. A questão real que quero resolver é: como montar volumes de host em contêineres do Dockerfile durante a compilação. THX.
xpt 26/09/14
2
Não é possivel. Veja a resposta revisada.
Andreas Steffan
3
Posso apreciar os efeitos colaterais "potenciais" prejudiciais à portabilidade, mas também há um caso de uso válido para ter essa opção. No meu caso, eu adoraria poder dizer aos usuários "Mover para o diretório e executar o comando 'docker run'" tendo $ (PWD) montado em algum diretório de contêiner. $ (PWD) garante que a portabilidade seja mantida. Embora isso possa ser um caso de canto, isso me ajudaria imensamente onde eu estou distribuindo ambientes de tempo de execução para scripts fornecidos pelo usuário.
Ntwrkguru
64

ATUALIZAÇÃO: Alguém simplesmente não aceita o não como resposta e eu gosto muito, especialmente para essa pergunta em particular.

BOAS NOTÍCIAS, Existe um caminho agora -

A solução é Rocker: https://github.com/grammarly/rocker

John Yani disse : "Na IMO, ele resolve todos os pontos fracos do Dockerfile, tornando-o adequado para o desenvolvimento".

Rocker

https://github.com/grammarly/rocker

Ao introduzir novos comandos, o Rocker visa solucionar os seguintes casos de uso, que são dolorosos com o Docker simples:

  1. Monte volumes reutilizáveis ​​no estágio de construção, para que as ferramentas de gerenciamento de dependência possam usar o cache entre as construções.
  2. Compartilhe chaves ssh com build (para obter repositórios particulares, etc.), sem deixá-las na imagem resultante.
  3. Crie e execute aplicativos em diferentes imagens, seja capaz de passar facilmente um artefato de uma imagem para outra, idealmente, tenha essa lógica em um único Dockerfile.
  4. Identifique / envie imagens diretamente do Dockerfiles.
  5. Passe variáveis ​​do comando shell build para que elas possam ser substituídas por um Dockerfile.

E mais. Esses são os problemas mais críticos que estavam impedindo nossa adoção do Docker na Grammarly.

Atualização: Rocker foi descontinuado, de acordo com o repositório oficial do projeto no Github

No início de 2018, o ecossistema de contêineres está muito mais maduro do que há três anos atrás, quando este projeto foi iniciado. Agora, alguns dos recursos críticos e destacados do rocker podem ser facilmente cobertos pela construção do docker ou por outras ferramentas bem suportadas, embora alguns recursos permaneçam exclusivos do rocker. Consulte https://github.com/grammarly/rocker/issues/199 para obter mais detalhes.

xpt
fonte
Estou tentando usar o Rocker para resolver o problema número 1, mas o comando mount não funcionará e a imagem criada não contém a pasta host. Meu comando de montagem do Dockerfile se parece com isso - MOUNT ~/code/docker-app-dev/new-editor/:/src/e meu comando de montagem do Rocker é este - rocker build -f Dockerfile .. O que estou fazendo de errado?
Yaron Idan
Talvez tente usar um caminho de host real? ~é um metacaractere da casca de Bourne.
perfil completo de Jesse Glick
Rocker buildnão permite docker runopções de linha de comando; portanto, atualmente não permite coisas como --privileged.
monty wild.
Olá @xpt, podemos obter outra atualização, já que o roqueiro está descontinuado
Shardj
Agora que o roqueiro foi descontinuado, reverto a resposta novamente para "Não é possível" novamente. Veja OP e a resposta selecionada.
xpt 17/02/19
14

Existe uma maneira de montar um volume durante uma compilação, mas isso não envolve os Dockerfiles.

A técnica seria criar um contêiner a partir de qualquer base que você desejasse usar (montar o (s) seu (s) volume (s) no contêiner com a -vopção), executar um script de shell para fazer o trabalho de criação da imagem e confirmar o contêiner como uma imagem quando terminar .

Isso não apenas deixará de fora os arquivos em excesso que você não deseja (isso também é bom para arquivos seguros, como arquivos SSH), mas também cria uma única imagem. Ele tem desvantagens: o comando commit não suporta todas as instruções do Dockerfile e não permite que você escolha quando parou se precisar editar seu script de construção.

ATUALIZAR:

Por exemplo,

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
fonte
6
+1 Você poderia elaborar um pouco mais sobre as instruções do segundo parágrafo. Por exemplo, se a base é debian:wheezye o shell script build.sh, que instruções específicas você usaria?
Drux
6

Conforme você executa o contêiner, um diretório em seu host é criado e montado no contêiner. Você pode descobrir em que diretório está esse

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

Se você deseja montar um diretório a partir do seu host dentro de seu contêiner, é necessário usar o -vparâmetro e especificar o diretório No seu caso, isso seria:

docker run -v /export:/export data

Portanto, você usaria a pasta hosts dentro do seu contêiner.

Behe
fonte
1
Obrigado. Pergunta revisada. A questão real que quero resolver é: como montar volumes de host em contêineres do Dockerfile durante a compilação. THX.
xpt 26/09/14
Por favor, não revise suas perguntas de maneira tão drástica . Isso torna minha pergunta inválida, apesar de perfeitamente válida antes das suas edições. Considere fazer uma nova pergunta.
Behe
11
Pergunta original : Como usar a instrução VOLUME no Dockerfile? Ainda está no começo da questão até hoje. Sua resposta foi para o tempo de execução , e minha pergunta sempre foi sobre o tempo de compilação , e é para isso que serve o Dockerfile.
xpt
4

Eu acho que você pode fazer o que deseja, executando a compilação por meio de um comando docker, que é executado dentro de um contêiner de docker. Consulte Docker agora pode ser executado no Docker | Blog do Docker . Uma técnica como essa, mas que realmente acessou a janela de encaixe externa de um contêiner, foi usada, por exemplo, ao explorar como criar o menor contêiner de Docker possível | Xebia Blog .

Outro artigo relevante é Otimizando imagens do Docker | O CenturyLink Labs , que explica que, se você fizer o download de coisas durante uma compilação, poderá evitar o desperdício de espaço na imagem final fazendo o download, construindo e excluindo o download em uma única etapa RUN.

nealmcb
fonte
3

É feio, mas consegui uma aparência assim:

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"

Eu tenho uma compilação java que baixa o universo para /root/.m2, e o fazia todas as vezes . imageBuild.shcopia o conteúdo dessa pasta no host após a compilação e os Dockerfilecopia novamente na imagem para a próxima compilação.

É algo como um volume funcionaria (isto é, persiste entre as compilações).

MatrixManAtYrService
fonte
Esta é uma solução viável para integração contínua baseada em Docker, também conhecida como CI. Configure as bibliotecas e compiladores e execute make via comandos do Dockerfile, inicie a imagem trivialmente apenas para criar um contêiner e, finalmente, copie o artefato desejado como um .deb. Parece funcionar, obrigado por postar isso.
chrisinmtown
Essa solução deixa uma imagem com TODOS os arquivos em ./m2/ - o que você precisa e o que não precisa - e isso pode levar a ENORMES imagens de produção, o que não é desejado! Com a montagem no diretório de dependências externas, apenas os arquivos necessários seriam copiados para a imagem.
Marko Krajnc 03/07/19
Se você pretende publicar a imagem, provavelmente é melhor esperar e permitir que baixe suas próprias dependências novamente a cada vez. Esse truque só faz sentido se você estiver testando uma imagem para teste - uma imagem com a qual os usuários finais nunca entrarão em contato.
MatrixManAtYrService
1

Aqui está uma versão simplificada da abordagem em duas etapas usando build e commit, sem scripts de shell. Envolve:

  1. Construindo a imagem parcialmente, sem volumes
  2. Executar um contêiner com volumes , fazer alterações e confirmar o resultado, substituindo o nome da imagem original.

Com alterações relativamente pequenas, a etapa adicional adiciona apenas alguns segundos ao tempo de compilação.

Basicamente:

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

No meu caso de uso, quero pré-gerar um arquivo maven toolchains.xml, mas minhas muitas instalações JDK estão em um volume que não está disponível até o tempo de execução. Algumas de minhas imagens não são compatíveis com todos os JDKS, portanto, preciso testar a compatibilidade no momento da construção e preencher o toolchains.xml condicionalmente. Observe que não preciso que a imagem seja portátil, não a estou publicando no Docker Hub.

Akom
fonte
1

Como muitos já responderam, não é possível montar volumes de host durante a construção. Gostaria apenas de acrescentar uma docker-composemaneira, acho que será bom ter, principalmente para uso em desenvolvimento / teste

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

E execute seu contêiner docker-compose up -d --build

Yegor Zaremba
fonte