Moby: Dockerfile COPY con archivos globs copiará archivos de subdirectorios al directorio de destino

Creado en 26 ago. 2015  ·  54Comentarios  ·  Fuente: moby/moby

Descripción del problema:
Cuando se usa COPY en un Dockerfile y se usan globos para copiar archivos y carpetas, la ventana acoplable (¿a veces?) también copiará archivos de las subcarpetas a la carpeta de destino.

$ docker version
Client:
 Version:      1.8.1
 API version:  1.20
 Go version:   go1.4.2
 Git commit:   d12ea79
 Built:        Thu Aug 13 19:47:52 UTC 2015
 OS/Arch:      darwin/amd64

Server:
 Version:      1.8.0
 API version:  1.20
 Go version:   go1.4.2
 Git commit:   0d03096
 Built:        Tue Aug 11 17:17:40 UTC 2015
 OS/Arch:      linux/amd64

$ docker info
Containers: 26
Images: 152
Storage Driver: aufs
 Root Dir: /mnt/sda1/var/lib/docker/aufs
 Backing Filesystem: extfs
 Dirs: 204
 Dirperm1 Supported: true
Execution Driver: native-0.2
Logging Driver: json-file
Kernel Version: 4.0.9-boot2docker
Operating System: Boot2Docker 1.8.0 (TCL 6.3); master : 7f12e95 - Tue Aug 11 17:55:16 UTC 2015
CPUs: 4
Total Memory: 3.858 GiB
Name: dev
ID: 7EON:IEHP:Z5QW:KG4Z:PG5J:DV4W:77S4:MJPX:2C5P:Z5UY:O22A:SYNK
Debug mode (server): true
File Descriptors: 42
Goroutines: 95
System Time: 2015-08-26T17:17:34.772268259Z
EventsListeners: 1
Init SHA1:
Init Path: /usr/local/bin/docker
Docker Root Dir: /mnt/sda1/var/lib/docker
Username: jfchevrette
Registry: https://index.docker.io/v1/
Labels:
 provider=vmwarefusion

$ uname -a
Darwin cerberus.local 14.5.0 Darwin Kernel Version 14.5.0: Wed Jul 29 02:26:53 PDT 2015; root:xnu-2782.40.9~1/RELEASE_X86_64 x86_64

Detalles del entorno:
Configuración local en OSX /w boot2docker construido con docker-machine

Cómo reproducir:

Contexto

$ tree
.
├── Dockerfile
└── files
    ├── dir
    │   ├── dirfile1
    │   ├── dirfile2
    │   └── dirfile3
    ├── file1
    ├── file2
    └── file3

Dockerfile

FROM busybox

RUN mkdir /test
COPY files/* /test/

Resultados actuales

$ docker run -it copybug ls -1 /test/
dirfile1
dirfile2
dirfile3
file1
file2
file3

Resultados previstos
La imagen resultante debe tener la misma estructura de directorios del contexto.

arebuilder

Comentario más útil

Haga un nuevo comando CP y hágalo bien esta vez, por favor.

Todos 54 comentarios

Mensaje original actualizado con salida de docker info y uname -a y reformateado para que esté de acuerdo con la plantilla de informes de problemas.

He tenido esto en 1.6.2 y 1.8
https://gist.github.com/jrabbit/e4f864ca1664ec0dd288 ¿los directorios de segundo nivel se tratan como deberían ser los de primer nivel por alguna razón?

para aquellos que buscan en Google: si tiene problemas con COPY * /src intente COPY / /src

@jfchevrette Creo que sé por qué sucede esto.
Tiene COPY files/* /test/ que se expande a COPY files/dir files/file1 files/file2 files/file /test/ . Si divide esto en comandos COPY individuales (por ejemplo COPY files/dir /test/ ), verá que (para bien o para mal) COPY copiará el contenido de cada directorio arg en el directorio de destino. No el directorio arg en sí, sino el contenido. Si agregó un tercer nivel de directorios, apuesto a que se mantendrán.

No estoy encantado con el hecho de que COPY no conserva el directorio de nivel superior, pero ha sido así desde hace un tiempo.

Puede intentar que esto sea menos doloroso copiando un nivel más alto en el árbol src, si es posible.

Estoy bastante seguro de que @duglin tiene razón y podría ser muy arriesgado cambiar ese comportamiento. muchos dockerfiles pueden romperse o simplemente copiar cosas no deseadas.

Sin embargo, diría que, a largo plazo, sería mejor si COPY siguiera la forma en que herramientas como cp o rsync manejan globos y barras inclinadas en las carpetas. Definitivamente no se espera que COPY copie archivos de una subcarpeta que coincida con dir/* en el destino IMO

@jfchevrette sí: en la primera oportunidad que tengamos, deberíamos "arreglar" esto.
Cierro por ahora...

@duglin entonces, ¿cerrar significa que no se arreglará?

@tugberkugurlu sí , al menos por ahora. Hay trabajo en marcha para rehacer toda la infraestructura de compilación y cuando lo hacemos es cuando podemos hacer que COPY (o su nuevo equivalente) actúe como debería.

@duglin gracias. ¿Es posible mantener este problema abierto y actualizar el estado aquí? ¿O hay algún otro problema al que me pueda suscribir?

@tugberkugurlu Pensé que teníamos un problema con el "soporte del constructor del lado del cliente", pero parece que no puedo encontrarlo. Entonces, todo lo que podemos tener es lo que dice ROADMAP (https://github.com/docker/docker/blob/master/ROADMAP.md#22-dockerfile-syntax).

En cuanto a mantener el tema abierto, no creo que podamos hacerlo. La regla general que Docker ha estado siguiendo es cerrar cualquier problema que no sea procesable de inmediato. Los problemas para el trabajo futuro generalmente se cierran y luego se vuelven a abrir una vez que el estado de las cosas cambia, de modo que se pueda tomar alguna acción (PR) para el problema.

@duglin Este es un problema muy serio, no debe simplemente cerrarlo porque el problema se introdujo en la versión 0.1. Sería más apropiado apuntar a esto para la versión 2.0 (los hitos también están en github).

Supongo que la mayoría de la gente usa:

COPY . /app

e incluya en la lista negra todas las demás carpetas en .gitignore o tenga una estructura de directorio de un solo nivel y use COPY que en realidad tiene una semántica mv :

COPY src /myapp

Es bastante difícil para mí imaginar que alguien realmente usaría COPY para aplanar la estructura de directorios. La otra solución para esto es usar tar -cf .. & ADD tarfile.tar.gz . Cambiar al menos esto sería realmente útil. La otra cosa es respetar las barras en los nombres de los directorios COPY src /src frente a COPY src/ /src (que actualmente se ignoran por completo).

duglin cerró esto el 1 de septiembre de 2015

@duglin Este es un problema ridículo y exasperante y no debe cerrarse. El comando COPY se comporta específicamente en desacuerdo con el uso y los ejemplos documentados.

@tjwebb todavía hay un problema abierto https://github.com/docker/docker/issues/29211. Esto solo se puede analizar si hay una forma de solucionarlo que sea totalmente compatible con versiones anteriores. Estamos abiertos a sugerencias si tiene una propuesta _cómo_ esto podría implementarse (si _la tiene_, siéntase libre de escribir esto y abrir una propuesta, vinculando a este problema). Tenga en cuenta que ya existe una diferencia entre (por ejemplo), OS X y Linux en la forma en que se maneja cp ;

mkdir -p repro-15858 \
  && cd repro-15858 \
  && mkdir -p source/dir1 source/dir2 \
  && touch source/file1 source/dir1/dir1-file1 \
  && mkdir -p target1 target2 target3 target4 target5 target6

cp -r source target1 \
&& cp -r source/ target2 \
&& cp -r source/ target3/ \
&& cp -r source/* target4/ \
&& cp -r source/dir* target5/ \
&& cp -r source/dir*/ target6/ \
&& tree

sistema operativo X:

.
├── source
│   ├── dir1
│   │   └── dir1-file1
│   ├── dir2
│   └── file1
├── target1
│   └── source
│       ├── dir1
│       │   └── dir1-file1
│       ├── dir2
│       └── file1
├── target2
│   ├── dir1
│   │   └── dir1-file1
│   ├── dir2
│   └── file1
├── target3
│   ├── dir1
│   │   └── dir1-file1
│   ├── dir2
│   └── file1
├── target4
│   ├── dir1
│   │   └── dir1-file1
│   ├── dir2
│   └── file1
├── target5
│   ├── dir1
│   │   └── dir1-file1
│   └── dir2
└── target6
    └── dir1-file1

20 directories, 12 files

En Ubuntu (/bin/sh)

.
|-- source
|   |-- dir1
|   |   `-- dir1-file1
|   |-- dir2
|   `-- file1
|-- target1
|   `-- source
|       |-- dir1
|       |   `-- dir1-file1
|       |-- dir2
|       `-- file1
|-- target2
|   `-- source
|       |-- dir1
|       |   `-- dir1-file1
|       |-- dir2
|       `-- file1
|-- target3
|   `-- source
|       |-- dir1
|       |   `-- dir1-file1
|       |-- dir2
|       `-- file1
|-- target4
|   |-- dir1
|   |   `-- dir1-file1
|   |-- dir2
|   `-- file1
|-- target5
|   |-- dir1
|   |   `-- dir1-file1
|   `-- dir2
`-- target6
    |-- dir1
    |   `-- dir1-file1
    `-- dir2

24 directories, 12 files
diff --git a/macos.txt b/ubuntu.txt
index 188d2c3..d776f19 100644
--- a/macos.txt
+++ b/ubuntu.txt
@@ -11,15 +11,17 @@
 │       ├── dir2
 │       └── file1
 ├── target2
-│   ├── dir1
-│   │   └── dir1-file1
-│   ├── dir2
-│   └── file1
+│   └── source
+│       ├── dir1
+│       │   └── dir1-file1
+│       ├── dir2
+│       └── file1
 ├── target3
-│   ├── dir1
-│   │   └── dir1-file1
-│   ├── dir2
-│   └── file1
+│   └── source
+│       ├── dir1
+│       │   └── dir1-file1
+│       ├── dir2
+│       └── file1
 ├── target4
 │   ├── dir1
 │   │   └── dir1-file1
@@ -30,6 +32,8 @@
 │   │   └── dir1-file1
 │   └── dir2
 └── target6
-    └── dir1-file1
+    ├── dir1
+    │   └── dir1-file1
+    └── dir2

-20 directories, 12 files
+24 directories, 12 files

Haga un nuevo comando CP y hágalo bien esta vez, por favor.

Me haría eco de lo anterior, esto debe haber desperdiciado innumerables horas de desarrollo, es extremadamente poco intuitivo.

+1 de mi parte. Este es un comportamiento realmente estúpido y podría remediarse fácilmente simplemente agregando un comando CP que funciona como COPY debería haberlo hecho.

La "compatibilidad con versiones anteriores" es un escape

La versión TL;DR :

No use COPY * /app , no hace lo que espera que haga.
Utilice COPY . /app en su lugar para conservar el árbol de directorios.

COPY solo puede copiar su subcarpeta.

Acabo de pasar incontables horas en esto... ¿Por qué funciona esto de esta manera?

Estoy usando Paket y quiero copiar lo siguiente en la estructura correcta:

.
├── .paket/
│   ├── paket.exe
│   ├── paket.bootstrapper.exe
├── paket.dependencies
├── paket.lock
├── projectN/

Y al hacer COPY *paket* ./ da como resultado esto dentro del contenedor:

.
├── paket.dependencies
├── paket.lock

¿Qué tal agregar una bandera --glob o --recursive para COPY y ADD ?

COPIAR . /destino conserva las subcarpetas.

Tres años y esto sigue siendo un problema :-/

¿Podemos obtener una ETA, cuando esto se solucionará?

no es un problema...
desde arriba...
COPIAR .

Cierto, ya no es un problema después de medio día y terminas aquí. Seguro :)
seamos constructivos,

image

Realmente necesitamos un nuevo comando _CP_ o un indicador --recursive para _COPY_ para preservar la compatibilidad con versiones anteriores.

Puntos principales si también mostramos una advertencia en la creación de imágenes, como:
Directory structure not preserved with COPY *, use CP or COPY . More here <link>. si detectamos un posible mal uso.

Estoy buscando esto para copiar archivos lerna package.json anidados en subdirectorios para utilizar mejor el caché npm install para activar solo cuando cambian las dependencias. Actualmente, todos los archivos modificados hacen que las dependencias se instalen nuevamente.

Algo así sería genial:

COPY ["package.json", "packages/*/package.json", "/app/"]

Vayan a ver #29211 chicos. Este ha sido cerrado y a nadie le importa.

@zentby La conversación está aquí, el problema se rastrea allí (ya que este está cerrado)... Es confuso.

una solución alternativa es usar archivos COPY y el comando RUN cp -R

COPY files /tmp/
RUN cp -R /tmp/etc/* /etc/ && rm -rf /tmp/etc

Eso no funcionará en @instabledesign ya que el comando COPY destruye el caché cuando un archivo es diferente y no debería invalidar el caché (por ejemplo, solo quiero copiar archivos relacionados con la instalación de dependencias de npm, ya que eso no cambia con frecuencia)

También necesitaba copiar solo un conjunto de archivos (en mi caso, archivos *.sln y *.csproj para dotnet core) a la memoria caché perversa. Una solución consiste en crear una bola tar de solo los archivos que desea y luego AGREGAR la bola tar en el archivo Docker. Sí, ahora debe tener un script de shell además del archivo Docker...

construir.sh

#!/bin/bash

# unfortunately there's no easy way to copy just the *.sln and *.csproj (see https://github.com/moby/moby/issues/15858)
# so we generate a tar file containing the required files for the layer

find .. -name '*.csproj' -o -name 'Finomial.InternalServicesCore.sln' -o -name 'nuget.config' | sort | tar cf dotnet-restore.tar -T - 2> /dev/null

docker build -t finomial/iscore-build -f Dockerfile ..

archivo acoplable

FROM microsoft/aspnetcore-build:2.0
WORKDIR /src

# set up a layer for dotnet restore 

ADD docker/dotnet-restore.tar ./

RUN dotnet restore

# now copy all the source and do the dotnet buld
COPY . ./

RUN dotnet publish --no-restore -c Release -o bin Finomial.InternalServicesCore.sln

Puede usar múltiples comandos COPY para hacer esto, pero eso tiene la desventaja de crear múltiples capas de imágenes e inflar el tamaño final de la imagen.

Como kayjtea mencionó anteriormente, también puede envolver el comando docker build en un script de compilación auxiliar para crear tarballs que conserven la estructura del directorio y ADD en ellos, pero eso agrega complejidad y rompe cosas como docker-compose build y compilaciones automatizadas de Docker Hub.

Realmente, COPY debería funcionar como un comando compatible con POSIX /bin/cp -r , pero parece que eso no sucederá con la 'compatibilidad con versiones anteriores', aunque el comportamiento actual es completamente intuitivo para cualquier persona con experiencia en sistemas *nix.


El mejor compromiso que he encontrado es usar una compilación de varias etapas como truco:

FROM scratch as project_root
# Use COPY to move individual directories
# and WORKDIR to change directory
WORKDIR /
COPY ./file1 .
COPY ./dir1/ ./dir1/
COPY ./dir2/ .
WORKDIR /newDir
COPY ./file2 .

# The actual final build you end up using/pushing
# Node.js app as example
FROM node
WORKDIR /opt/app

COPY package.json .
RUN npm install

COPY --from=project_root / .
CMD ["npm", "start"]

Esto está contenido dentro de un Dockerfile y solo crea una capa en la imagen final, tal como funcionaría un ADD project.tar .

Tener un comando COPY completo realmente ayudaría al intentar preservar el caché de compilación de la ventana acoplable. La comunidad ROS se desarrolla utilizando un espacio de trabajo anidado de paquetes, cada uno de los cuales declara dependencias en su propio archivo package.xml . Estos archivos son utilizados por un administrador de dependencias para instalar cualquier biblioteca ascendente. Estos archivos package.xml cambian con relativa poca frecuencia y se escriben en código en los propios paquetes una vez que se establecen las bases. Si la estructura del árbol de directorios se conservó durante una copia, simplemente podríamos copiar nuestro espacio de trabajo durante la compilación de la ventana acoplable en dos etapas para maximizar el almacenamiento en caché, por ejemplo:

# copy project dependency metadata
COPY ./**/package.xml /opt/ws/

# install step that fetches unsatisfied dependency
RUN dependency_manager install --workspace /opt/ws/

# copy the rest of the project's code
COPY ./ /opt/ws/

# compile code with cached dependencies
RUN build_tool build --workspace /opt/ws/

Por lo tanto, la memoria caché para la capa de instalación de dependencia anterior solo fallaría si el desarrollador cambiara una dependencia declarada, mientras que un cambio en el código del paquete solo dañaría la capa de compilación.

Actualmente, todos los archivos package.xml coincidentes se copian uno encima del otro en la raíz del directorio de destino, siendo el último archivo global el único package.xml que persiste en la imagen. ¡Lo cual es bastante poco intuitivo para los usuarios! ¿Por qué los archivos copiados se sobrescriben uno encima del otro, además del comportamiento indefinido que finalmente persiste en la imagen?

Esto es un dolor de cabeza básicamente en todas las pilas que tienen administración de paquetes, por lo que nos afecta a muchos de nosotros. ¿Se puede arreglar? Sheesh. ¡Ha sido un problema desde 2015! La sugerencia de usar un nuevo comando de CP es buena.

¿Podemos reabrir esto? Es un comportamiento muy tedioso que el comando COPY use una función interna golang para la coincidencia de rutas, en lugar de un estándar muy adoptado, como glob

Para aquellos que deseen copiar a través de globing utilizando una solución alternativa con sintaxis de kit de compilación experimental, incluso si el almacenamiento en caché no es tan preciso o sólido, pueden consultar los comentarios aquí: https://github.com/moby/moby/issues /39530#emisorcomentario -530606189

Todavía me gustaría ver este problema reabierto para que podamos almacenar en caché en copias selectivas de estilo glob.

Me di cuenta de una solución relativamente simple para mi ejemplo en https://github.com/moby/moby/issues/15858#issuecomment -462017830 a través de compilaciones de varias etapas , y pensé que muchos de ustedes aquí con necesidades similares pueden apreciar el almacenamiento en caché arbitrario en copiado artefactos del contexto de compilación. Usando compilaciones de varias etapas, es posible filtrar/preprocesar el directorio para almacenar en caché:

# Add prior stage to cache/copy from
FROM ubuntu AS package_cache

# Copy from build context
WORKDIR /tmp
COPY ./ ./src

# Filter or glob files to cache upon
RUN mkdir ./cache && cd ./src && \
    find ./ -name "package.xml" | \
      xargs cp --parents -t ../cache

# Continue with primary stage
FROM ubuntu

# copy project dependency metadata
COPY --from=package_cache /tmp/cache /opt/ws/

# install step that fetches unsatisfied dependency
RUN dependency_manager install --workspace /opt/ws/

# copy the rest of the project's code
COPY ./ /opt/ws/

# compile code with cached dependencies
RUN build_tool build --workspace /opt/ws/

Para ver un ejemplo de trabajo del mundo real, también puede echar un vistazo aquí: https://github.com/ros-planning/navigation2/pull/1122

Estoy buscando esto para copiar archivos lerna package.json anidados en subdirectorios para utilizar mejor el caché npm install para activar solo cuando cambian las dependencias. Actualmente, todos los archivos modificados hacen que las dependencias se instalen nuevamente.

Algo así sería genial:

COPY ["package.json", "packages/*/package.json", "/app/"]

Estoy teniendo exactamente el mismo caso de uso.

Estoy buscando esto para copiar archivos lerna package.json anidados en subdirectorios para utilizar mejor el caché npm install para activar solo cuando cambian las dependencias. Actualmente, todos los archivos modificados hacen que las dependencias se instalen nuevamente.

Algo así sería genial:

COPY ["package.json", "packages/*/package.json", "/app/"]

Este caso, pero para espacios de trabajo de Yarn.

Es 2020 y esto todavía no está arreglado.

Si alguien tiene problemas con esto en una configuración de dotnet, lo resolví escribiendo una herramienta global central de dotnet que restaura la estructura del directorio para los archivos *.csproj, lo que permite que siga una restauración. Ver documentación sobre cómo hacerlo aquí .

FYI, en teoría, se podría usar un enfoque similar en otras configuraciones, pero esencialmente la herramienta es la ingeniería inversa de la estructura de carpetas, por lo que no estoy seguro de cuán fácil o incluso posible sería, por ejemplo, una configuración de espacios de trabajo de lerna o yarn. Feliz de investigarlo si hay interés. Incluso podría ser posible en la misma herramienta si la gente estuviera feliz de instalar el tiempo de ejecución de dotnet core para que funcione, de lo contrario, el mismo enfoque que he hecho debería construirse en un lenguaje que no requiera una nueva dependencia, como nodo Creo.

Es sorprendente que implementar un comando de copia solía ser una tarea para un estudiante en el primer año, y ahora es demasiado complejo para programadores expertos con muchos años de experiencia...

Probablemente no sea el error más vergonzoso de la historia, pero teniendo en cuenta que es seguido por muchos años de discusión sin ningún resultado, ciertamente lo compensa.

@benmccallum
FYI, en teoría, se podría usar un enfoque similar en otras configuraciones, pero esencialmente la herramienta es la ingeniería inversa de la estructura de carpetas,

¿No es más fácil para la mayoría de las ocasiones simplemente hacer lo que https://github.com/moby/moby/issues/15858#issuecomment -532016362 sugirió y usar una compilación de varias etapas para prefiltrar?

También para el caso de dotnet restore es un patrón relativamente fácil:

# Prefiltering stage using find -exec and cp --parents to copy out
# the project files in their proper directory structure.
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS dotnet-prep
COPY . ./src/
RUN mkdir ./proj && cd ./src && \
  find . -type f -a \( -iname "*.sln" -o -iname "*.csproj" \) \
    -exec cp --parents "{}" ../proj/ \;

# New build stage, independent cache
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS dotnet-build

# Copy only the project files with correct directory structure
# then restore packages
COPY --from=dotnet-prep ./proj ./src/
RUN dotnet restore

# Copy everything else
COPY --from=dotnet-prep ./src ./src/
# etc.

Todavía no es una excusa apropiada para que Docker nunca haya implementado una variante decente en el comando COPY que solo sigue la semántica de sincronización normal y sensata.
Quiero decir; ¡vamos!

@rjgotten , ¡me gusta! Ciertamente mucho más fácil que lo que hice y no puedo ver por qué esto no funcionaría para mis necesidades. Lo intentaré mañana y, si funciona, cambiaré mis documentos para recomendarlo como un mejor enfoque.

Creo que mi problema inicial fue que estaba en Windows, así que probablemente descarté esa sugerencia. Ya no lo soy, pero ¿tienes una versión equivalente de Windows para completar? Me pregunto si PowerShell está preinstalado en las imágenes de dotnet core...

Sin embargo, ¿realmente hay una necesidad adicional/repetida DE...? Cada vez que haces una EJECUCIÓN, se crea una nueva capa para el almacenamiento en caché, ¿verdad? Tal vez me estoy perdiendo algo, ¡ha pasado un tiempo desde que tuve que pensar en esto!

Me pregunto si PowerShell está preinstalado en las imágenes de dotnet core...

Creo que en realidad lo es. Lo que haría un poco más fácil hacer esto de una manera multiplataforma.

Sin embargo, ¿realmente hay una necesidad adicional/repetida DE...?

Las etapas de compilación aisladas obtienen un almacenamiento en caché de capa independiente.
La primera etapa hace el trabajo de preparación. Dado que inicialmente tiene que copiar todo, siempre invalida el caché de su primera capa y, por lo tanto, las capas posteriores, cuando cambia _cualquier_ archivo. Pero eso solo se aplica a las capas _dentro de esa etapa de construcción_.

La segunda etapa comienza _solo_ copiando los archivos relacionados con el proyecto y siempre que esos archivos sean iguales, es decir, los mismos nombres de archivo; mismo contenido; etc. - a través de compilaciones que la capa _no_ invalidará. Lo que significa que la capa dotnet restore _también_ no se invalidará a menos que esos archivos de proyecto realmente cambien.

¡Tuve algo de tiempo para sentarme con esto y ahora lo entiendo! Docker es divertido ya que, a menos que siempre pases tiempo con él, te olvidas de cómo funcionan todos los comandos. Crucialmente, había olvidado que EJECUTAR cmd solo puede operar en el sistema de archivos de la imagen de la ventana acoplable, no en los archivos de contexto de compilación. Por lo tanto, se ve obligado a COPIAR todo antes de poder ejecutar un comando EJECUTAR complejo que conserve los directorios. ¡Y es por eso que necesitamos tan desesperadamente COPY globbing decente!

ese enfoque
El cmd COPY inicial es, como mencionas, copiar _todo_ y luego extraer los archivos .sln y .csproj en una carpeta /proj separada. Cualquier cambio de código invalidará estos pasos. Crucialmente, la limitación que esto está solucionando es que el asombroso RUN linux cmd solo puede operar en archivos _en la imagen de la ventana acoplable_, traídos por el codicioso COPY anterior.

Luego se inicia una nueva etapa y se copia el contenido de la carpeta /proj sobre el cual se puede usar para dotnet restore . Dado que la "clave" de caché es esencialmente el hash del archivo, esto rara vez romperá esta capa de caché, ni la subsiguiente dotnet restore , por lo que evita la costosa restauración. ¡Bonito!

Mi acercamiento
Utiliza solo una etapa de compilación para esto a costa de algunos comandos COPY más para traer los archivos que afectan una restauración dotnet. Aplane específicamente todos los archivos .csproj en un solo directorio y luego uso mi herramienta global para reconstruir la estructura de directorio correcta a partir del archivo de entrada .sln. Es solo después de esto que COPIO sobre todos los archivos src, por lo que puedo almacenar en caché las capas hasta aquí regularmente, en lugar de tener que COPIAR siempre sobre todos los archivos src por adelantado.

comida para llevar
Creo que va a depender de la base de código de las personas en cuanto a la eficacia de cada enfoque. Para nosotros, en un repositorio mono, tenemos MUCHO código compartido que se copia en la COPIA "all src over". .dockerignore ayuda aquí, pero es difícil de mantener, por lo que somos bastante "codiciosos" en esa COPIA; así que es bastante lento. Como tal, mi enfoque, aunque un poco más complicado, probablemente sería más rápido para nosotros que esta alternativa.

Gracias por la explicación. Todavía no puedo creer que tengamos que tener esta conversación jaja. Todavía necesito investigar las cosas más nuevas de BuildKit para ver si esto es más fácil ahora. ¿Alguien más ha hecho eso?

Investigación de BuildKit
RUN --mount=type=bind - Parece que esto nos permitiría hacer el elegante cmd de Linux en el contexto de compilación (en lugar de RUN limitarse al sistema de archivos de la imagen). De hecho, aparentemente tiene como valor predeterminado el contexto de compilación.

RUN --mount=type=cache : ¿suena como algún tipo de directorio de caché reutilizable (¿entre compilaciones de la ventana acoplable?) que se conserva? Entonces, en esencia, ni siquiera tendríamos que preocuparnos demasiado por una capa de caché para las restauraciones de paquetes, porque con un caché reutilizado de paquetes previamente restaurados, ¡ya sería muchísimo más rápido!

Creo que el problema aún no está solucionado porque está "cerrado" y la gente está acostumbrada a las soluciones alternativas.

Cuando no entiende por qué la gente tiende a pasar a otro tipo de contenedor.

¿Hay otro tipo de contenedor que pueda usar? ¿No puedo creer que esto no sea compatible después de tantos años? ¿Docker es un proyecto de código abierto? ¿Alguien puede solucionarlo?

Tenemos una opción COPY --dir en Earthly para hacer que la copia se comporte más como cp -r . ¿Quizás esto también podría ser portado a Dockerfile?

Para acelerar la creación de imágenes para aplicaciones .net core, debemos crear algún contenedor intermedio que contenga todos los paquetes nuget restaurados. Para la solución de varios proyectos, utilicé esta solución alternativa. Acabo de copiar todos los archivos del proyecto en una sola carpeta y ejecuté dotnet restore para cada uno de ellos. Hay algunas advertencias sobre proyectos perdidos porque no podemos preservar la jerarquía de carpetas, pero aún así es una solución funcional.

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build

# Nugets restore
WORKDIR /src/allprojects          # just temporary storage for .csproj files
COPY */*.csproj ./
RUN for file in $(ls *.csproj); do dotnet restore ${file}; done

# Build/Publish
WORKDIR /src/solution             # actual folder with source code and full hierarchy 
COPY . .
RUN dotnet publish "MyProject/MyProject.csproj" -c Release -o /bublish/myproject

# Run Application
FROM mcr.microsoft.com/dotnet/core/runtime:3.1 AS base
WORKDIR /app
COPY --from=build /bublish/myproject .
ENTRYPOINT ["dotnet", "MyProject.dll"]

@zatuliveter
Hay algunas advertencias sobre proyectos perdidos porque no podemos preservar la jerarquía de carpetas, pero aún así es una solución funcional.

No; eso no funciona Y he aquí por qué:

.NET Core almacena la metainformación del paquete en el subdirectorio ./obj asociado con cada proyecto. Sin esa información presente, el paquete no se considerará instalado y listo para usar. (¿No me cree? Luego, deseche su carpeta ./obj y luego, por ejemplo, abra el proyecto en VSCode y mire cómo le pide que vuelva a ejecutar la restauración del paquete. Adelante, pruébelo).

Si los archivos de proyecto en los que ejecuta la restauración del paquete están en una estructura de directorio diferente a la siguiente dotnet build o dotnet publish , esos comandos no verán el paquete como restaurado.

La razón por la que su solución no falla por completo es porque dotnet publish y dotnet build implican dotnet restore . Verifican activamente los paquetes no restaurados y los restauran sobre la marcha. Para evitar que hagan esto, debe pasar activamente la bandera --no-restore , lo cual no está haciendo.

Entonces, realmente, lo que está haciendo su solución es restaurar los paquetes _DOS VECES_. La primera vez es esencialmente una gran pérdida de tiempo y espacio, porque no entra en la estructura de directorios correcta para que pueda ser reutilizado. La segunda vez, implícita como parte del comando publish , funciona; pero debido a que es parte de la misma capa que la operación de compilación y publicación, sus paquetes en realidad no se almacenan en caché separados de los cambios de código.

@rjgotten ,
Gracias por tu respuesta y aclaraciones.
De hecho, todos los paquetes nuget se almacenan en caché en la carpeta global-packages en el contenedor acoplable 'compilar'. En mi caso, es la carpeta /root/.nuget/packages/ , y la carpeta obj solo contiene archivos pequeños con referencias a este almacenamiento global, por lo que no hay desperdicio de almacenamiento (como menciona).
Segunda restauración durante la publicación al menos x10 veces más rápido (en mi caso) porque todos los nugets se almacenan en caché en el contenedor.

@zatuliveter @rjgotten gracias por la información al final aquí. Me encontré con problemas similares y se me ocurrió el siguiente archivo dockerfile para mejorar los ejemplos que diste. Bash ciertamente no es mi fuerte, ¡así que tómatelo con calma! Nuestra estructura es Project/Project.csproj para todos nuestros proyectos. Esto copia todos los archivos de proyecto, los mueve al lugar correcto y luego restaura/copia todo/publica.

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src
COPY ./*/*.csproj ./proj/
RUN for file in $(ls ./proj); do mkdir /src/${file%.*} && mv ./proj/${file} /src/${file%.*}/${file}; done
RUN dotnet restore "MyProject/MyProject.csproj"
COPY . .
WORKDIR "/src/MyProject"
RUN dotnet publish "MyProject.csproj" -c Release -o /app --no-restore
¿Fue útil esta página
0 / 5 - 0 calificaciones