Compartir comentarios
Las respuestas se generan en base a la documentación.

Mejores prácticas de compilación

Usa compilaciones multi-etapa (multi-stage builds)

Las compilaciones multi-etapa te permiten reducir el tamaño de tu imagen final al crear una separación más limpia entre el proceso de compilación de tu imagen y el resultado final. Divide las instrucciones de tu Dockerfile en etapas diferenciadas para asegurarte de que el resultado final solo contenga los archivos necesarios para ejecutar la aplicación.

El uso de múltiples etapas también te permite compilar de manera más eficiente al ejecutar los pasos de compilación en paralelo.

Consulta Compilaciones multi-etapa para obtener más información.

Crear etapas reutilizables

Si tienes varias imágenes que comparten muchos elementos en común, considera la posibilidad de crear una etapa reutilizable que incluya dichos componentes compartidos y basar tus etapas específicas en ella. Docker solo necesitará compilar la etapa común una vez. Esto significa que tus imágenes derivadas utilizarán la memoria del host de Docker de manera más eficiente y se cargarán más rápidamente.

También es más sencillo mantener una etapa base común (aplicando el principio "Don't repeat yourself" o "No te repitas"), que tener múltiples etapas diferentes realizando tareas similares.

Elige la imagen base adecuada

El primer paso para lograr una imagen segura es elegir la imagen base correcta. Al seleccionar una imagen, asegúrate de que provenga de una fuente de confianza y mantén su tamaño al mínimo.

Cuando elijas tu imagen base, presta atención a las insignias que indican que la imagen forma parte de estos programas.

Imágenes Oficiales y de Editores Verificados en Docker Hub

Al compilar tu propia imagen a partir de un Dockerfile, asegúrate de elegir una imagen base mínima que se ajuste a tus requisitos. Una imagen base más pequeña no solo ofrece portabilidad y descargas rápidas, sino que también reduce el tamaño de tu imagen y minimiza el número de vulnerabilidades introducidas a través de las dependencias.

También deberías considerar el uso de dos tipos de imagen base: una para la compilación y pruebas unitarias, y otra (normalmente más ligera) para producción. En las etapas finales del desarrollo, es posible que tu imagen no requiera herramientas de compilación como compiladores, sistemas de construcción y herramientas de depuración. Una imagen pequeña con dependencias mínimas puede reducir considerablemente la superficie de ataque.

Recompila tus imágenes a menudo

Las imágenes de Docker son inmutables. Compilar una imagen consiste en tomar una instantánea (snapshot) de esa imagen en ese momento preciso. Eso incluye las imágenes base, bibliotecas o cualquier otro software que utilices en tu compilación. Para mantener tus imágenes actualizadas y seguras, recompílalas con regularidad con dependencias actualizadas.

Usa --pull para obtener imágenes base nuevas

El siguiente Dockerfile utiliza la etiqueta 24.04 de la imagen ubuntu. Con el tiempo, esa etiqueta puede resolverse en una versión subyacente diferente de la imagen ubuntu, a medida que el editor la recompila con nuevos parches de seguridad y bibliotecas actualizadas.

# syntax=docker/dockerfile:1
FROM ubuntu:24.04
RUN apt-get -y update && apt-get install -y --no-install-recommends python3

Para obtener la versión más reciente de la imagen base, utiliza la bandera --pull:

$ docker build --pull -t my-image:my-tag .

La bandera --pull obliga a Docker a buscar y descargar una versión más reciente de la imagen base, incluso si tienes una versión almacenada localmente en caché.

Usa --no-cache para compilaciones limpias

La bandera --no-cache desactiva la caché de compilación, lo que obliga a Docker a recompilar todas las capas desde cero:

$ docker build --no-cache -t my-image:my-tag .

Esto obtiene las últimas versiones disponibles de las dependencias a partir de los gestores de paquetes como apt-get o npm. Sin embargo, --no-cache no descarga una imagen base nueva; solo evita la reutilización de capas en caché. Para una compilación completamente limpia con la última imagen base, combina ambas banderas:

$ docker build --pull --no-cache -t my-image:my-tag .

Considera también la posibilidad de fijar las versiones de las imágenes base.

Excluye archivos con .dockerignore

Para excluir archivos que no son relevantes para la compilación, sin tener que reestructurar tu repositorio de origen, utiliza un archivo .dockerignore. Este archivo admite patrones de exclusión similares a los archivos .gitignore.

Por ejemplo, para excluir todos los archivos con la extensión .md:

*.md

Para obtener información sobre cómo crear uno, consulta Archivo .dockerignore.

Crea contenedores efímeros

La imagen definida por tu Dockerfile debería generar contenedores que sean lo más efímeros posible. Efímero significa que el contenedor se puede detener y destruir, para luego ser recompilado y reemplazado con una configuración y preparación mínimas.

Consulta la sección Procesos (Processes) de la metodología The Twelve-Factor App para comprender las motivaciones de ejecutar contenedores de esta manera sin estado (stateless).

No instales paquetes innecesarios

Evita instalar paquetes adicionales o innecesarios solo porque "podrían ser útiles". Por ejemplo, no necesitas incluir un editor de texto en una imagen de base de datos.

Al evitar la instalación de paquetes adicionales o innecesarios, reduces la complejidad, las dependencias, el tamaño de los archivos y los tiempos de compilación de tus imágenes.

Desacopla las aplicaciones

Cada contenedor debe tener una sola responsabilidad. Desacoplar las aplicaciones en múltiples contenedores facilita el escalado horizontal y la reutilización de contenedores. Por ejemplo, una pila de aplicaciones web podría constar de tres contenedores independientes, cada uno con su propia imagen única, para gestionar la aplicación web, la base de datos y una caché en memoria de forma desacoplada.

Limitar cada contenedor a un solo proceso es una buena regla general, pero no es una norma estricta. Por ejemplo, además de que los contenedores se pueden iniciar con un proceso de inicio (init), algunos programas pueden generar procesos adicionales por su propia cuenta. Por ejemplo, Celery puede generar múltiples procesos de trabajo (workers), y Apache puede crear un proceso por cada solicitud.

Usa tu mejor criterio para mantener los contenedores lo más limpios y modulares posible. Si los contenedores dependen unos de otros, puedes usar las redes de contenedores de Docker para garantizar que puedan comunicarse entre sí.

Ordena los argumentos multilínea

Siempre que sea posible, ordena los argumentos multilínea alfabéticamente para facilitar su mantenimiento. Esto ayuda a evitar la duplicación de paquetes y hace que la lista sea mucho más fácil de actualizar. También hace que las solicitudes de extracción (PRs) sean mucho más fáciles de leer y revisar. Añadir un espacio antes de una barra invertida (\) también ayuda.

Aquí tienes un ejemplo de la imagen buildpack-deps:

RUN apt-get update && apt-get install -y --no-install-recommends \
  bzr \
  cvs \
  git \
  mercurial \
  subversion \
  && rm -rf /var/lib/apt/lists/*

Aprovecha la caché de compilación

Al compilar una imagen, Docker procesa las instrucciones de tu Dockerfile una a una, ejecutándolas en el orden especificado. Para cada instrucción, Docker comprueba si puede reutilizar la instrucción desde la caché de compilación.

Comprender cómo funciona la caché de compilación y cómo se produce su invalidación es fundamental para lograr compilaciones más rápidas. Para obtener más información sobre la caché de compilación de Docker y cómo optimizar tus compilaciones, consulta Caché de compilación de Docker.

Fijar las versiones de las imágenes base

Las etiquetas de imagen (tags) son mutables, lo que significa que un editor puede actualizar una etiqueta para que apunte a una nueva imagen. Esto es útil porque permite a los editores actualizar las etiquetas para que apunten a versiones más recientes de una imagen. Como consumidor de la imagen, esto significa que obtienes automáticamente la nueva versión cuando vuelves a compilar tu imagen.

Por ejemplo, si especificas FROM alpine:3.21 en tu Dockerfile, 3.21 se resolverá en la última versión de parche para 3.21.

# syntax=docker/dockerfile:1
FROM alpine:3.21

En un momento dado, la etiqueta 3.21 podría apuntar a la versión 3.21.1 de la imagen. Si vuelves a compilar la imagen 3 meses después, la misma etiqueta podría apuntar a una versión diferente, como la 3.21.4. Este flujo de trabajo de publicación es una mejor práctica y la mayoría de los editores utilizan esta estrategia de etiquetado, aunque no es obligatoria.

La desventaja de esto es que no tienes garantizado obtener el mismo resultado en cada compilación. Esto podría dar lugar a cambios de ruptura (breaking changes), y significa que no dispones de un registro de auditoría de las versiones exactas de las imágenes que estás utilizando.

Para asegurar por completo la integridad de tu cadena de suministro, puedes fijar la versión de la imagen a un hash de contenido (digest) específico. Al fijar tus imágenes a un digest, tienes la garantía de usar siempre la misma versión de la imagen, incluso si un editor reemplaza la etiqueta con una imagen nueva. Por ejemplo, el siguiente Dockerfile fija la imagen de Alpine a la misma etiqueta que antes, 3.21, pero esta vez con una referencia al digest.

# syntax=docker/dockerfile:1
FROM alpine:3.21@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c

Con este Dockerfile, incluso si el editor actualiza la etiqueta 3.21, tus compilaciones seguirán utilizando la versión de la imagen fijada: a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c.

Aunque esto ayuda a evitar cambios inesperados, también es más tedioso tener que buscar e incluir manualmente el digest de la imagen para las versiones de la imagen base cada vez que deseas actualizarla. Además, estarías renunciando a las correcciones de seguridad automáticas, que probablemente sea algo que desees obtener.

La política predeterminada Up-to-Date Base Images de Docker Scout comprueba si la versión de la imagen base que estás utilizando es la versión más reciente. Esta política también comprueba si los digests fijados en tu Dockerfile se corresponden con la versión correcta. Si un editor actualiza una imagen que has fijado, la evaluación de la política devolverá un estado de no cumplimiento, indicando que debes actualizar tu imagen.

Docker Scout también admite un flujo de trabajo de remediación automatizado para mantener tus imágenes base actualizadas. Cuando hay un nuevo digest de imagen disponible, Docker Scout puede abrir automáticamente una solicitud de extracción (pull request) en tu repositorio para actualizar tus Dockerfiles y que utilicen la versión más reciente. Esto es mejor que usar una etiqueta que cambie la versión automáticamente, porque tú mantienes el control y dispones de un registro de auditoría de cuándo y cómo ocurrió el cambio.

Para obtener más información sobre la actualización automática de tus imágenes base con Docker Scout, consulta Remediación.

Compila y prueba tus imágenes en CI

Cuando guardes un cambio en el control de código fuente o crees una solicitud de extracción (pull request), utiliza GitHub Actions u otro pipeline de CI/CD para compilar y etiquetar automáticamente una imagen de Docker y realizar pruebas sobre ella.

Instrucciones de Dockerfile

Sigue estas recomendaciones sobre cómo utilizar correctamente las instrucciones de Dockerfile para crear un Dockerfile eficiente y fácil de mantener.

Tip

Para mejorar el análisis estático (linting), la navegación de código y el escaneo de vulnerabilidades de tus Dockerfiles en Visual Studio Code, consulta la extensión Docker DX.

FROM

Siempre que sea posible, utiliza imágenes oficiales actuales como base para tus imágenes. Docker recomienda la imagen de Alpine, ya que está muy controlada y tiene un tamaño reducido (menos de 6 MB), al mismo tiempo que sigue siendo una distribución de Linux completa.

Para obtener más información sobre la instrucción FROM, consulta la referencia de Dockerfile para la instrucción FROM.

LABEL

Puedes añadir etiquetas (labels) a tu imagen para organizar las imágenes por proyecto, registrar información de licencias, facilitar la automatización u otras razones. Para cada etiqueta, añade una línea que comience con LABEL seguida de uno o más pares clave-valor. Los siguientes ejemplos muestran los diferentes formatos aceptables. Se incluyen comentarios explicativos de forma alineada.

Las cadenas con espacios deben estar entre comillas o los espacios deben estar escapados. Las comillas internas (") también deben escaparse. Por ejemplo:

# Configurar una o más etiquetas individuales
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH\ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""

Una imagen puede tener más de una etiqueta. Antes de Docker 1.10, se recomendaba combinar todas las etiquetas en una única instrucción LABEL para evitar la creación de capas adicionales. Esto ya no es necesario, pero la combinación de etiquetas sigue estando admitida. Por ejemplo:

# Configurar múltiples etiquetas en una sola línea
LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"

El ejemplo anterior también se puede escribir como:

# Configurar múltiples etiquetas a la vez, utilizando caracteres de continuación de línea para dividir líneas largas
LABEL vendor=ACME\ Incorporated \
      com.example.is-beta= \
      com.example.is-production="" \
      com.example.version="0.0.1-beta" \
      com.example.release-date="2015-02-12"

Consulta Comprender las etiquetas de objeto para obtener pautas sobre las claves y valores de etiqueta aceptables. Para obtener información sobre cómo consultar etiquetas, consulta los temas relacionados con el filtrado en Gestión de etiquetas en objetos. Consulta también LABEL en la referencia de Dockerfile.

RUN

Divide las sentencias RUN largas o complejas en múltiples líneas separadas por barras invertidas para que tu Dockerfile sea más legible, comprensible y fácil de mantener.

Por ejemplo, puedes encadenar comandos con el operador && y utilizar caracteres de escape para dividir comandos largos en varias líneas.

RUN apt-get update && apt-get install -y --no-install-recommends \
    package-bar \
    package-baz \
    package-foo

De forma predeterminada, la barra invertida escapa un carácter de nueva línea, pero puedes cambiarlo con la directiva escape.

También puedes usar here documents para ejecutar múltiples comandos sin encadenarlos con un operador de pipeline:

RUN <<EOF
apt-get update
apt-get install -y --no-install-recommends \
    package-bar \
    package-baz \
    package-foo
EOF

Para obtener más información sobre RUN, consulta la referencia de Dockerfile para la instrucción RUN.

apt-get

Un caso de uso común para las instrucciones RUN en imágenes basadas en Debian es instalar software usando apt-get. Debido a que apt-get instala paquetes, el comando RUN apt-get tiene varios comportamientos que debes tener en cuenta.

Combina siempre RUN apt-get update con apt-get install en la misma sentencia RUN. Por ejemplo:

RUN apt-get update && apt-get install -y --no-install-recommends \
    package-bar \
    package-baz \
    package-foo

Utilizar apt-get update por separado en una sentencia RUN provoca problemas con la caché y hace que las instrucciones apt-get install posteriores fallen. Por ejemplo, este problema ocurrirá en el siguiente Dockerfile:

# syntax=docker/dockerfile:1

FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends curl

Después de compilar la imagen, todas las capas quedan en la caché de Docker. Supón que más tarde modificas apt-get install añadiendo un paquete adicional como se muestra en el siguiente Dockerfile:

# syntax=docker/dockerfile:1

FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends curl nginx

Docker interpreta las instrucciones inicial y modificada como idénticas y reutiliza la caché de los pasos anteriores. Como resultado, apt-get update no se ejecuta porque la compilación utiliza la versión almacenada en caché. Debido a que no se ejecuta apt-get update, tu compilación podría obtener una versión obsoleta de los paquetes curl y nginx.

El uso de RUN apt-get update && apt-get install -y --no-install-recommends garantiza que tu Dockerfile instale las versiones más recientes de los paquetes sin necesidad de codificación adicional o intervención manual. Esta técnica se conoce como anulación de caché (cache busting). También puedes lograr la anulación de la caché especificando la versión de un paquete. Esto se conoce como fijación de versión (version pinning). Por ejemplo:

RUN apt-get update && apt-get install -y --no-install-recommends \
    package-bar \
    package-baz \
    package-foo=1.3.*

La fijación de versiones obliga a la compilación a recuperar una versión en particular independientemente de lo que haya en la caché. Esta técnica también puede reducir los fallos provocados por cambios imprevistos en los paquetes requeridos.

A continuación se muestra una instrucción RUN bien estructurada que demuestra todas las recomendaciones de apt-get.

RUN apt-get update && apt-get install -y --no-install-recommends \
    aufs-tools \
    automake \
    build-essential \
    curl \
    dpkg-sig \
    libcap-dev \
    libsqlite3-dev \
    mercurial \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \
    && rm -rf /var/lib/apt/lists/*

El argumento de s3cmd especifica una versión 1.1.*. Si la imagen anteriormente utilizaba una versión más antigua, especificar la nueva provoca una anulación de caché de apt-get update y garantiza la instalación de la nueva versión. Enumerar los paquetes en líneas individuales también ayuda a evitar errores de duplicación de paquetes.

Además, cuando limpias la caché de apt eliminando /var/lib/apt/lists, reduces el tamaño de la imagen, ya que la caché de apt no se almacena en una capa. Dado que la sentencia RUN comienza con apt-get update, la caché de paquetes siempre se actualiza antes de apt-get install.

Las imágenes oficiales de Debian y Ubuntu ejecutan automáticamente apt-get clean, por lo que no es necesario realizar una invocación explícita.

Uso de tuberías (pipes)

Algunos comandos RUN dependen de la capacidad de redirigir la salida de un comando a otro mediante el carácter de tubería (|), como en el siguiente ejemplo:

RUN wget -O - https://some.site | wc -l > /number

Docker ejecuta estos comandos utilizando el intérprete /bin/sh -c, que solo evalúa el código de salida de la última operación en la tubería para determinar el éxito. En el ejemplo anterior, este paso de compilación tendrá éxito y producirá una nueva imagen siempre que el comando wc -l finalice correctamente, incluso si el comando wget falla.

Si deseas que el comando falle debido a un error en cualquier etapa de la tubería, antepón set -o pipefail && para asegurarte de que un error inesperado evite que la compilación tenga éxito de manera inadvertida. Por ejemplo:

RUN set -o pipefail && wget -O - https://some.site | wc -l > /number
Note

No todos los shells admiten la opción -o pipefail.

En casos como el shell dash en imágenes basadas en Debian, considera usar la forma de ejecución (exec) de RUN para elegir explícitamente un shell que sí admita la opción pipefail. Por ejemplo:

RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"]

CMD

La instrucción CMD debe utilizarse para ejecutar el software contenido en tu imagen, junto con cualquier argumento. CMD debe utilizarse casi siempre en la forma CMD ["ejecutable", "param1", "param2"]. De este modo, si la imagen es para un servicio como Apache y Rails, ejecutarías algo como CMD ["apache2","-DFOREGROUND"]. De hecho, esta forma de la instrucción se recomienda para cualquier imagen basada en servicios.

En la mayoría de los demás casos, a CMD se le debe proporcionar un shell interactivo, como bash, Python y perl. Por ejemplo, CMD ["perl", "-de0"], CMD ["python"] o CMD ["php", "-a"]. Utilizar esta forma significa que cuando ejecutes algo como docker run -it python, entrarás directamente en un shell utilizable y listo para trabajar.

CMD rara vez debe utilizarse de la forma CMD ["param", "param"] en combinación con ENTRYPOINT, a menos que tú y los usuarios previstos estéis muy familiarizados con el funcionamiento de ENTRYPOINT.

Para obtener más información sobre CMD, consulta la referencia de Dockerfile para la instrucción CMD.

EXPOSE

La instrucción EXPOSE indica los puertos en los que un contenedor escucha las conexiones. Por lo tanto, debes utilizar el puerto habitual y tradicional para tu aplicación. Por ejemplo, una imagen que contenga el servidor web Apache utilizaría EXPOSE 80, mientras que una imagen con MongoDB utilizaría EXPOSE 27017, y así sucesivamente.

Para el acceso externo, los usuarios pueden ejecutar docker run con una bandera que indique cómo asignar el puerto especificado al puerto de su elección. Para el enlace de contenedores, Docker proporciona variables de entorno para la ruta desde el contenedor receptor de vuelta al origen (por ejemplo, MYSQL_PORT_3306_TCP).

Para obtener más información sobre EXPOSE, consulta la referencia de Dockerfile para la instrucción EXPOSE.

ENV

Para facilitar la ejecución de nuevo software, puedes utilizar ENV para actualizar la variable de entorno PATH para el software que instala tu contenedor. Por ejemplo, ENV PATH=/usr/local/nginx/bin:$PATH garantiza que CMD ["nginx"] funcione directamente.

La instrucción ENV también es útil para proporcionar las variables de entorno necesarias específicas para los servicios que deseas contenedorizar, como PGDATA de Postgres.

Por último, ENV también se puede utilizar para configurar números de versión de uso común, de modo que las actualizaciones de versión sean más fáciles de mantener, como se muestra en el siguiente ejemplo:

ENV PG_MAJOR=9.3
ENV PG_VERSION=9.3.4
RUN curl -SL https://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgres &&
ENV PATH=/usr/local/postgres-$PG_MAJOR/bin:$PATH

De manera similar al uso de variables constantes en un programa, en lugar de codificar los valores a fuego, este enfoque te permite cambiar una sola instrucción ENV para actualizar automáticamente la versión del software en tu contenedor.

Cada línea ENV crea una nueva capa intermedia, al igual que los comandos RUN. Esto significa que incluso si eliminas la variable de entorno en una capa posterior, esta sigue persistiendo en esta capa y su valor se puede recuperar. Puedes probar esto creando un Dockerfile como el siguiente y luego compilándolo.

# syntax=docker/dockerfile:1
FROM alpine
ENV ADMIN_USER="mark"
RUN echo $ADMIN_USER > ./mark
RUN unset ADMIN_USER
$ docker run --rm test sh -c 'echo $ADMIN_USER'

mark

Para evitar esto y eliminar la variable de entorno, utiliza un comando RUN con comandos de shell para configurar, usar y eliminar la variable en una sola capa. Puedes separar tus comandos con ; o &&. Si utilizas el segundo método y uno de los comandos falla, la compilación de Docker también fallará. Esto suele ser una buena idea. El uso de \ como carácter de continuación de línea para los Dockerfiles de Linux mejora la legibilidad. También puedes colocar todos los comandos en un script de shell y hacer que el comando RUN simplemente ejecute ese script.

# syntax=docker/dockerfile:1
FROM alpine
RUN export ADMIN_USER="mark" \
    && echo $ADMIN_USER > ./mark \
    && unset ADMIN_USER
CMD sh
$ docker run --rm test sh -c 'echo $ADMIN_USER'

Para obtener más información sobre ENV, consulta la referencia de Dockerfile para la instrucción ENV.

ADD o COPY

ADD y COPY son funcionalmente similares. COPY admite la copia básica de archivos en el contenedor, desde el contexto de compilación o desde una etapa en una compilación multi-etapa. ADD admite funciones para recuperar archivos de URLs remotas HTTPS y Git, y extraer archivos tar automáticamente al añadir archivos desde el contexto de compilación.

Principalmente querrás usar COPY para copiar archivos de una etapa a otra en una compilación multi-etapa. Si necesitas añadir archivos desde el contexto de compilación al contenedor de forma temporal para ejecutar una instrucción RUN, a menudo puedes sustituir la instrucción COPY por un montaje de tipo bind (bind mount). Por ejemplo, para añadir temporalmente un archivo requirements.txt para una instrucción RUN pip install:

RUN --mount=type=bind,source=requirements.txt,target=/tmp/requirements.txt \
    pip install --requirement /tmp/requirements.txt

Los montajes bind son más eficientes que COPY para incluir archivos del contexto de compilación en el contenedor. Ten en cuenta que los archivos montados por bind solo se añaden temporalmente para una única instrucción RUN y no persisten en la imagen final. Si necesitas incluir archivos del contexto de compilación en la imagen final, utiliza COPY.

La instrucción ADD es ideal para cuando necesitas descargar un artefacto remoto como parte de tu compilación. ADD es mejor que añadir archivos manualmente usando comandos como wget y tar, porque garantiza una caché de compilación más precisa. ADD también cuenta con soporte integrado para la validación de sumas de comprobación (checksum) de recursos remotos y un protocolo para analizar ramas, etiquetas y subdirectorios a partir de URLs de Git.

El siguiente ejemplo utiliza ADD para descargar un instalador de .NET. Combinado con compilaciones multi-etapa, solo el runtime de .NET permanece en la etapa final, sin archivos intermedios.

# syntax=docker/dockerfile:1

FROM scratch AS src
ARG DOTNET_VERSION=8.0.0-preview.6.23329.7
ADD --checksum=sha256:270d731bd08040c6a3228115de1f74b91cf441c584139ff8f8f6503447cebdbb \
    https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-runtime-$DOTNET_VERSION-linux-arm64.tar.gz /dotnet.tar.gz

FROM mcr.microsoft.com/dotnet/runtime-deps:8.0.0-preview.6-bookworm-slim-arm64v8 AS installer

# Obtener .NET Runtime
RUN --mount=from=src,target=/src <<EOF
mkdir -p /dotnet
tar -oxzf /src/dotnet.tar.gz -C /dotnet
EOF

FROM mcr.microsoft.com/dotnet/runtime-deps:8.0.0-preview.6-bookworm-slim-arm64v8

COPY --from=installer /dotnet /usr/share/dotnet
RUN ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet

Para obtener más información sobre ADD o COPY, consulta lo siguiente:

ENTRYPOINT

El mejor uso de ENTRYPOINT es establecer el comando principal de la imagen, lo que permite que la imagen se ejecute como si fuera ese comando, y luego utilizar CMD como banderas predeterminadas.

El siguiente es un ejemplo de una imagen para la herramienta de línea de comandos s3cmd:

ENTRYPOINT ["s3cmd"]
CMD ["--help"]

Puedes utilizar el siguiente comando para ejecutar la imagen y mostrar la ayuda de la herramienta:

$ docker run s3cmd

O bien, puedes pasar los parámetros adecuados para ejecutar un comando, como en el siguiente ejemplo:

$ docker run s3cmd ls s3://mybucket

Esto es útil porque el nombre de la imagen puede funcionar como referencia al binario, como se muestra en el comando anterior.

La instrucción ENTRYPOINT también se puede utilizar en combinación con un script auxiliar (helper script), lo que le permite funcionar de manera similar al comando anterior, incluso cuando iniciar la herramienta requiera más de un paso.

Por ejemplo, la Imagen Oficial de Postgres utiliza el siguiente script como su ENTRYPOINT:

#!/bin/sh
set -e

if [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"

    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fi

    exec gosu postgres "$@"
fi

exec "$@"

Este script utiliza el comando integrado exec para que la aplicación final en ejecución se convierta en el PID 1 del contenedor. Esto permite a la aplicación recibir cualquier señal de Unix enviada al contenedor. Para obtener más información, consulta la referencia de ENTRYPOINT.

En el siguiente ejemplo, se copia un script auxiliar en el contenedor y se ejecuta a través de ENTRYPOINT al iniciar el contenedor:

COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["postgres"]

Este script te permite interactuar con Postgres de varias maneras.

Simplemente puede iniciar Postgres:

$ docker run postgres

O bien, puedes usarlo para ejecutar Postgres y pasar parámetros al servidor:

$ docker run postgres postgres --help

Por último, puedes usarlo para iniciar una herramienta totalmente diferente, como Bash:

$ docker run --rm -it postgres bash

Para obtener más información sobre ENTRYPOINT, consulta la referencia de Dockerfile para la instrucción ENTRYPOINT.

VOLUME

Debes utilizar la instrucción VOLUME para exponer cualquier área de almacenamiento de bases de datos, almacenamiento de configuración o archivos y carpetas creados por tu contenedor de Docker. Se recomienda encarecidamente utilizar VOLUME para cualquier combinación de partes mutables o que el usuario pueda gestionar directamente de tu imagen.

Para obtener más información sobre VOLUME, consulta la referencia de Dockerfile para la instrucción VOLUME.

USER

Si un servicio se puede ejecutar sin privilegios, utiliza USER para cambiar a un usuario sin privilegios root. Comienza creando el usuario y el grupo en el Dockerfile con algo como el siguiente ejemplo:

RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
Note

Considera usar un UID/GID explícito.

A los usuarios y grupos en una imagen se les asigna un UID/GID no determinista en el sentido de que el "siguiente" UID/GID disponible se asigna independientemente de las recompilaciones de la imagen. Por lo tanto, si es crítico, debes asignar un UID/GID explícito.

Note

Debido a un fallo no resuelto en el manejo de archivos dispersos (sparse files) del paquete archive/tar de Go, intentar crear un usuario con un UID significativamente grande dentro de un contenedor de Docker puede provocar el agotamiento del disco debido a que /var/log/faillog en la capa del contenedor se llena con caracteres NULL (\0). Una solución consiste en pasar la bandera --no-log-init a useradd. El envoltorio adduser de Debian/Ubuntu no admite esta bandera.

Evita instalar o utilizar sudo, ya que tiene un comportamiento impredecible de TTY y reenvío de señales que puede causar problemas. Si necesitas imperiosamente una funcionalidad similar a sudo, como iniciar el demonio como root pero ejecutarlo como un usuario sin privilegios root, considera utilizar “gosu”.

Por último, para reducir las capas y la complejidad, evita alternar de usuario con USER con demasiada frecuencia.

Para obtener más información sobre USER, consulta la referencia de Dockerfile para la instrucción USER.

WORKDIR

Para mayor claridad y confiabilidad, debes utilizar siempre rutas absolutas para tu WORKDIR. Asimismo, debes utilizar WORKDIR en lugar de propagar instrucciones como RUN cd … && do-something, las cuales son difíciles de leer, depurar y mantener.

Para obtener más información sobre WORKDIR, consulta la referencia de Dockerfile para la instrucción WORKDIR.

ONBUILD

Un comando ONBUILD se ejecuta después de que finalice la compilación del Dockerfile actual. ONBUILD se ejecuta en cualquier imagen hija derivada con FROM a partir de la imagen actual. Piensa en el comando ONBUILD como una instrucción que el Dockerfile padre le da al Dockerfile hijo.

Una compilación de Docker ejecuta los comandos ONBUILD antes de cualquier comando en un Dockerfile hijo.

ONBUILD es útil para imágenes que van a ser compiladas FROM a partir de una imagen determinada. Por ejemplo, utilizarías ONBUILD para una imagen de pila de lenguaje que compila software de usuario arbitrario escrito en ese lenguaje dentro del Dockerfile, como puedes ver en las variantes ONBUILD de Ruby.

Las imágenes compiladas con ONBUILD deberían recibir una etiqueta independiente. Por ejemplo, ruby:1.9-onbuild o ruby:2.0-onbuild.

Ten cuidado al incluir ADD o COPY en ONBUILD. La compilación fallará catastróficamente si en el contexto de la nueva compilación falta el recurso que se está añadiendo. Añadir una etiqueta independiente, como se recomienda anteriormente, ayuda a mitigar esto al permitir que el autor del Dockerfile tome una decisión informada.

Para obtener más información sobre ONBUILD, consulta la referencia de Dockerfile para la instrucción ONBUILD.