Creacion de un Dockerfile: Las instrucciones ADD y COPY

Tanto ADD como COPY sirven para copiar archivos o directorios a un path de la imagen que queremos construir. La diferencia es que ADD admite URLs remotas y archivos comprimidos.

La sintaxis de la instrucción es:

ADD o COPY <origen>... <destino>
ADD o COPY ["<origen>",... "<destino>"]   <<< Para paths con espacios

Precisiones sobre el origen a copiar

En cuanto a la especificación de lo que queremos copiar (origen), su path debe estar en el contexto de la build. No se puede hacer ADD o COPY ../loquesea /loquesea

También se pueden especificar varios orígenes a copiar pero, si son archivos o directorios, se deberá hacer de forma relativa al contexto o WORKDIR

Si el origen es un archivo remoto por URL en destino tendrá permisos de 600.

Ademas, si tiene un encabezado HTTP Last-Modified, se usara el timestamp de ese encabezado para configurar la fecha de modificación del archivo destino, lo que por otra parte no se tiene en cuenta para ver si el archivo ha cambiado y se debe actualizar la cache.

Ademas, si tiene autenticacion, habrá que hacer un RUN wget, RUN curl o similar porque ADD URL no soporta autenticacion.

La URL debe permitir descargarse un archivo. No sirve http://example.com

Si origen es un directorio se copia todo el contenido del mismo, incluido el metadata del sistema de ficheros, pero no el directorio como tal.

Si origen es un tar se tiene en cuenta el contenido del archivo, no el nombre. Por ejemplo, si es un archivo vacío null.tar.gz no se cuenta como comprimido y se le tratara como archivo normal.

Si es un tar en un formato de compresión reconocido (identity, gzip, bzip2 or xz) se descomprime como un directorio. (URLs no se descomprimen)

Cuando un directorio es copiado o desempaquetado, tiene el mismo comportamiento que un tar -x y el resultado sera la mezcla de lo que exista en la ruta de destino y el contenido del arbol de origen, con preferencia para la segunda opción.

El destino de la instrucción también puede ser un path absoluto o relativo a WORKDIR

Eso si, todos los nuevos archivos y directorios se crean con un UID y GID 0 y, si no existe el destino, se creara incluyendo los directorios que falten de su ruta.

Construir la imagen usando STDIN y el contexto

Si se quiere hacer la imagen mediante STDIN (docker build - < somefile), se pierde el contexto, asi que unicamente se podria usar ADD con URL.

Sin embargo, se se podría pasarle un tar.gz (docker build - < archive.tar.gz), poniendo el Dockerfile en la raíz del archivo. El resto del tar.gz seria usado como el contexto de la build.

Best practices

Docker recomienda como norma general el uso de COPY por encima de ADD, salvo en los caso de archivos comprimidos.

La razón es que aunque ADD es mas versátil, también tiene comportamientos mas impredecibles. De hecho es la razón por la que se creó la instrucción COPY. Ver mas al respecto aqui.

Ni siquiera docker recomienda en el uso de ADD con URL remotas sino RUN wget, RUN curl o similar.

Por ejemplo, en vez de

ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all

usar

RUN mkdir -p /usr/src/things \
    && curl -SL http://example.com/big.tar.xz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all

Por otra parte, si en una instrucción ADD el origen ha cambiado, esto invalida la cache de las siguientes instrucciones, incluido RUN.
Por ello Docker recomienda que si tienes varios pasos en tu Dockerfile que requieren cada uno la copia de archivos diferentes desde tu contexto, los copies individualmente en vez de todos a la vez. Con esto conseguimos menos invalidaciones de la cache.

COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/

También derivado de ello se podría decir que otra buena practica seria usar COPY o ADD en los orígenes mas susceptibles de cambiar (código,...), en la parte final del Dockerfile, para que se invalide la cache de menos capas posteriores.