Saltar al contenido principal

Referencia de Dockerfile

Docker puede construir imágenes automáticamente leyendo las instrucciones de un Dockerfile. Un Dockerfile es un documento de texto que contiene todos los comandos que un usuario puede llamar desde la línea de comandos para ensamblar una imagen. Esta página describe los comandos que puede usar en un Dockerfile.

Vista general

El Dockerfile admite las siguientes instrucciones:

InstrucciónDescripción
ADDAñadir archivos y directorios locales o remotos.
ARGUtilizar variables de tiempo de construcción.
CMDEspecificar comandos predeterminados.
COPYCopiar archivos y directorios.
ENTRYPOINTEspecificar el ejecutable predeterminado.
ENVEstablecer variables de entorno.
EXPOSEDescribir en qué puertos está escuchando su aplicación.
FROMCrear una nueva etapa de construcción desde una imagen base.
HEALTHCHECKVerificar la salud de un contenedor en el arranque.
LABELAñadir metadatos a una imagen.
MAINTAINEREspecificar el autor de una imagen.
ONBUILDEspecificar instrucciones para cuando la imagen se usa en una construcción.
RUNEjecutar comandos de construcción.
SHELLEstablecer la shell predeterminada de una imagen.
STOPSIGNALEspecificar la señal de llamada al sistema para salir de un contenedor.
USEREstablecer la ID de usuario y grupo.
VOLUMECrear montajes de volúmenes.
WORKDIRCambiar el directorio de trabajo.

ADD

La instrucción ADD y COPY tienen básicamente el mismo formato y naturaleza. Sin embargo, ADD agrega algunas funcionalidades adicionales encima de COPY.

Por ejemplo, la <ruta fuente> puede ser una URL, en cuyo caso el motor de Docker intentará descargar el archivo de este enlace y colocarlo en la <ruta destino>. Los permisos del archivo descargado se establecerán automáticamente en 600. Si estos no son los permisos deseados, se necesita una capa de RUN adicional para ajustar los permisos. Además, si el archivo descargado es un archivo comprimido, también se necesita una capa de RUN adicional para extraerlo. Por lo tanto, es más razonable utilizar directamente la instrucción RUN, luego usar la herramienta wget o curl para descargar, manejar permisos, extraer y luego limpiar archivos innecesarios. Por lo tanto, esta funcionalidad no es realmente práctica, y no se recomienda su uso.

Si la <ruta fuente> es un archivo comprimido tar, y el formato de compresión es gzip, bzip2 o xz, la instrucción ADD descomprimirá automáticamente este archivo comprimido en la <ruta destino>.

En algunos casos, esta característica de descompresión automática es muy útil, como en la imagen oficial de ubuntu:

FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
...

Pero en algunos casos, si realmente queremos copiar un archivo comprimido sin extraerlo, entonces no podemos usar el comando ADD.

En la documentación de las mejores prácticas de Dockerfile oficial de Docker, se requiere usar COPY siempre que sea posible, porque la semántica de COPY es muy clara: simplemente copia archivos, mientras que ADD incluye funcionalidades más complejas y su comportamiento puede no ser tan claro. El caso de uso más apropiado para ADD es la necesidad mencionada de descompresión automática.

Además, se debe señalar que la instrucción ADD hará que la caché de construcción de imágenes se invalide, lo que puede ralentizar el proceso de construcción de imágenes.

Por lo tanto, al elegir entre las instrucciones COPY y ADD, puede seguir este principio: use la instrucción COPY para todas las operaciones de copiado de archivos y solo use ADD cuando se necesite descompresión automática.

Al usar esta instrucción, también puede agregar la opción --chown=<usuario>:<grupo> para cambiar el usuario y grupo propietario del archivo.

ADD --chown=55:mygroup files* /mydir/
ADD --chown=bin files* /mydir/
ADD --chown=1 files* /mydir/
ADD --chown=10:11 files* /mydir/

ARG

Formato: ARG <nombre_del_parametro>[=<valor_por_defecto>]

Los argumentos de construcción tienen el mismo efecto que ENV, ambos establecen variables de entorno. La diferencia es que las variables de entorno establecidas por ARG durante el entorno de construcción no existirán cuando el contenedor esté en ejecución. Sin embargo, no utilices ARG para almacenar contraseñas u otra información sensible solo por esto, ya que docker history aún puede mostrar todos los valores.

La instrucción ARG en el Dockerfile define nombres de parámetros y sus valores por defecto. Este valor por defecto se puede sobrescribir en el comando docker build con --build-arg <nombre_del_parametro>=<valor>.

Usar la instrucción ARG de manera flexible te permite construir diferentes imágenes sin modificar el Dockerfile.

La instrucción ARG tiene un alcance de efecto. Si se especifica antes de la instrucción FROM, solo se puede usar en la instrucción FROM.

ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

RUN set -x ; echo ${DOCKER_USERNAME}

Usando el Dockerfile anterior, encontrarás que el valor de la variable ${DOCKER_USERNAME} no se puede salir. Para que se salga correctamente, debes especificar ARG nuevamente después de FROM.

# Solo efectivo en FROM
ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

# Para usarlo después de FROM, debes especificarlo de nuevo
ARG DOCKER_USERNAME=library

RUN set -x ; echo ${DOCKER_USERNAME}

Para construcciones de múltiples etapas, presta especial atención a este problema.

# Esta variable es efectiva en cada FROM
ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

RUN set -x ; echo 1

FROM ${DOCKER_USERNAME}/alpine

RUN set -x ; echo 2

Para el Dockerfile anterior, ambas instrucciones FROM pueden usar ${DOCKER_USERNAME}. Para las variables utilizadas en cada etapa, deben especificarse por separado en cada etapa:

ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

# Para usar la variable después de FROM, debe especificarse por separado en cada etapa
ARG DOCKER_USERNAME=library

RUN set -x ; echo ${DOCKER_USERNAME}

FROM ${DOCKER_USERNAME}/alpine

# Para usar la variable después de FROM, debe especificarse por separado en cada etapa
ARG DOCKER_USERNAME=library

RUN set -x ; echo ${DOCKER_USERNAME}

CMD

La instrucción CMD tiene un formato similar a RUN, con dos formatos:

  • Formato shell: CMD <comando>
  • Formato exec: CMD ["ejecutable", "param1", "param2"...]
  • Formato de lista de parámetros: CMD ["param1", "param2"...]. Se utiliza para especificar los parámetros reales después de que se ha especificado la instrucción ENTRYPOINT.

Cuando se introdujeron los contenedores anteriormente, se mencionó que Docker no es una máquina virtual, y los contenedores son procesos. Dado que son procesos, es necesario especificar un programa y sus parámetros cuando se inicia un contenedor. La instrucción CMD se utiliza para especificar el comando de inicio predeterminado para el proceso principal del contenedor.

Durante el tiempo de ejecución, se puede especificar un nuevo comando para reemplazar este comando predeterminado establecido en la imagen. Por ejemplo, el CMD predeterminado para la imagen ubuntu es /bin/bash. Si ejecutamos directamente docker run -it ubuntu, entraremos en bash. También podemos especificar un comando diferente para ejecutar durante el tiempo de ejecución, como docker run -it ubuntu cat /etc/os-release. Esto reemplaza el comando predeterminado /bin/bash con cat /etc/os-release, mostrando la información de la versión del sistema.

En términos de formato de instrucción, generalmente se recomienda el formato exec, ya que se analizará como un arreglo JSON durante el análisis, por lo tanto se deben usar comillas dobles " y no comillas simples.

Si se utiliza el formato shell, el comando real se envolverá como un argumento para sh -c para su ejecución. Por ejemplo:

CMD echo $HOME

Durante la ejecución real, se cambiará a:

CMD [ "sh", "-c", "echo $HOME" ]

Esta es la razón por la que podemos usar variables de entorno, ya que estas variables de entorno serán analizadas y procesadas por el shell.

Mencionar CMD inevitablemente plantea la cuestión de si las aplicaciones en el contenedor deben ejecutarse en primer plano o en segundo plano. Esta es una confusión común para los principiantes.

Docker no es una máquina virtual, y las aplicaciones en contenedores deben ejecutarse en primer plano, no como en máquinas virtuales o máquinas físicas donde se utiliza systemd para iniciar servicios en segundo plano. No hay un concepto de servicios en segundo plano dentro de un contenedor.

Algunos principiantes escriben el CMD como:

CMD service nginx start

Luego descubren que el contenedor sale inmediatamente después de la ejecución. Incluso cuando usan el comando systemctl dentro del contenedor, descubren que no se puede ejecutar en absoluto. Esto se debe a que no han entendido los conceptos de primer plano y segundo plano, y no han distinguido la diferencia entre contenedores y máquinas virtuales, aún tratando de entender los contenedores desde la perspectiva de máquinas virtuales tradicionales.

Para un contenedor, su programa de inicio es el proceso de aplicación del contenedor. El contenedor existe para el proceso principal, y cuando el proceso principal termina, el contenedor pierde su propósito y también termina. Otros procesos auxiliares no son algo de lo que deba preocuparse.

Usar el comando service nginx start es un intento de hacer que el sistema init inicie el servicio nginx como un proceso demonio en segundo plano. Como se mencionó anteriormente, CMD service nginx start se interpretará como CMD ["sh", "-c", "service nginx start"], por lo que el proceso principal es en realidad sh. Cuando el comando service nginx start termina, sh también termina, provocando que el proceso principal salga y el contenedor salga de forma natural.

El enfoque correcto es ejecutar directamente el archivo ejecutable nginx y requerir que se ejecute en primer plano. Por ejemplo:

CMD ["nginx", "-g", "daemon off;"]
COPY [--chown=<usuario>:<grupo>] <ruta_origen>... <ruta_destino>
COPY [--chown=<usuario>:<grupo>] ["<ruta_origen1>",... "<ruta_destino>"]

Al igual que la instrucción `RUN`, hay dos formatos, uno similar a la línea de comandos y otro similar a una llamada de función.

La instrucción `COPY` copia archivos/directorios desde la `<ruta_origen>` en el contexto de construcción hasta la `<ruta_destino>` en la nueva capa de la imagen. Por ejemplo:

```bash
COPY package.json /usr/src/app/

<ruta_origen> puede ser múltiples rutas, e incluso puede ser un comodín, donde las reglas del comodín deben satisfacer las reglas de filepath.Match de Go, como por ejemplo:

COPY hom* /mydir/
COPY hom?.txt /mydir/

<ruta_destino> puede ser una ruta absoluta dentro del contenedor, o una ruta relativa relativa al directorio de trabajo (el directorio de trabajo se puede especificar usando la instrucción WORKDIR). La ruta de destino no necesita ser creada de antemano. Si el directorio no existe, se creará antes de copiar los archivos.

Adicionalmente, se debe notar que al usar la instrucción COPY, varios metadatos de los archivos de origen se conservarán, como permisos de lectura, escritura y ejecución, y tiempos de modificación de archivo. Esta característica es útil para la personalización de la imagen, especialmente cuando todos los archivos relevantes se gestionan utilizando Git.

Al utilizar esta instrucción, también puedes añadir la opción --chown=<usuario>:<grupo> para cambiar el propietario y el grupo de los archivos.

COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/

Si la ruta de origen es una carpeta, al copiar, no copia directamente la carpeta, sino que copia los contenidos de la carpeta a la ruta de destino.

ENTRYPOINT

El formato de ENTRYPOINT es el mismo que el de la instrucción RUN, dividido en formato exec y formato shell.

El propósito de ENTRYPOINT es el mismo que CMD, ambos se utilizan para especificar el programa de inicio del contenedor y sus parámetros. ENTRYPOINT también se puede reemplazar en tiempo de ejecución, pero es un poco más engorroso que CMD, y requiere el uso del parámetro --entrypoint de docker run para especificarlo.

Una vez que se especifica ENTRYPOINT, el significado de CMD cambia. Ya no ejecuta directamente su comando, sino que trata los contenidos de CMD como argumentos pasados a la instrucción ENTRYPOINT. En otras palabras, cuando se ejecuta, se convertirá en:

<ENTRYPOINT> "<CMD>"

Entonces, con CMD, ¿por qué necesitamos ENTRYPOINT? ¿Cuáles son los beneficios de este <ENTRYPOINT> "<CMD>"? Veamos algunos escenarios.

Escenario 1: Hacer que la imagen se comporte como un comando

Supongamos que necesitamos una imagen para conocer nuestra dirección IP pública actual, podemos primero implementarlo con CMD:

FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://myip.ipip.net" ]

Si usamos docker build -t myip . para construir la imagen, si necesitamos consultar la IP pública actual, solo necesitamos ejecutar:

$ docker run myip
IP actual: 8.8.8.8 desde California, USA

Bueno, parece que podemos usar la imagen como un comando ahora, pero los comandos generalmente tienen argumentos. ¿Qué pasa si queremos agregar argumentos? Desde el CMD anterior, podemos ver que el comando real es curl, por lo que si queremos mostrar la información del encabezado HTTP, necesitamos agregar el parámetro -i. ¿Podemos agregar directamente el parámetro -i a docker run myip?

$ docker run myip -i
docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".

Podemos ver un error que el archivo ejecutable no se encuentra, executable file not found. Como se mencionó anteriormente, lo que viene después del nombre de la imagen es el comando, que reemplazará el valor predeterminado de CMD en tiempo de ejecución. Por lo tanto, aquí -i reemplaza el CMD original en lugar de ser agregado al final del original curl -s http://myip.ipip.net. Sin embargo, -i no es un comando en absoluto, por lo que naturalmente no se puede encontrar.

Entonces, si queremos agregar el parámetro -i, tenemos que volver a ingresar el comando completo:

$ docker run myip curl -s http://myip.ipip.net -i

Esta es obviamente no es una buena solución, pero usar ENTRYPOINT puede resolver este problema. Ahora reimplementamos esta imagen usando ENTRYPOINT:

FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ]

Ahora intentemos usar docker run myip -i nuevamente:

$ docker run myip
IP actual: 8.8.8.8 desde: California, USA

$ docker run myip -i
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 22 Nov 2016 05:12:40 GMT
Content-Type: text/html; charset=UTF-8
Vary: Accept-Encoding
X-Powered-By: PHP/5.6.24-1~dotdeb+7.1
X-Cache: MISS from cache-2
X-Cache-Lookup: MISS from cache-2:80
X-Cache: MISS from proxy-2_6
Transfer-Encoding: chunked
Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006
Connection: keep-alive

IP actual: 8.8.8.8 desde: California, USA

Podemos ver que esta vez funcionó. Esto se debe a que cuando existe ENTRYPOINT, los contenidos de CMD se pasarán como argumentos a ENTRYPOINT, y aquí -i es el nuevo CMD, por lo que se pasará como un argumento a curl, logrando el efecto deseado.

Escenario 2: Trabajo de preparación antes del inicio de la aplicación

Iniciar un contenedor es iniciar el proceso principal, pero a veces, se necesita algún trabajo de preparación antes de iniciar el proceso principal.

Por ejemplo, para tipos de bases de datos como mysql, puede ser necesario realizar algún trabajo de configuración e inicialización de la base de datos, y estas tareas deben resolverse antes de que se ejecute el servidor de mysql final.

Además, es posible que desee evitar iniciar el servicio como el usuario root, mejorando así la seguridad, pero antes de iniciar el servicio, aún necesita realizar algún trabajo de preparación necesario como el usuario root, y finalmente cambiar a la identidad del usuario del servicio para iniciar el servicio. Alternativamente, los comandos que no son el servicio aún se pueden ejecutar como el usuario root por conveniencia, como depuración.

Estas tareas de preparación no están relacionadas con el CMD del contenedor y deben realizarse como un paso de preprocesamiento, independientemente de cuál sea el CMD. En este caso, puede escribir un script y ponerlo en ENTRYPOINT para ejecutar, y este script tomará los argumentos recibidos (es decir, <CMD>) como el comando para ejecutar al final. Por ejemplo, la imagen oficial de redis hace esto:

FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 6379
CMD [ "redis-server" ]

Puede ver que crea el usuario redis para el servicio redis, y finalmente especifica ENTRYPOINT como el script docker-entrypoint.sh.

#!/bin/sh
# permitir que el contenedor se inicie con `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
find . \! -user redis -exec chown redis '{}' +
exec gosu redis "$0" "$@"
fi

exec "$@"

El contenido de este script es juzgar basado en el contenido de CMD. Si es redis-server, cambiará a la identidad de usuario de redis para iniciar el servidor. De lo contrario, seguirá funcionando como el usuario root. Por ejemplo:

$ docker run -it redis id
uid=0(root) gid=0(root) grupos=0(root)

ENV

Hay dos formatos:

  • ENV <clave> <valor>
  • ENV <clave1>=<valor1> <clave2>=<valor2>...

Esta instrucción es simple, solo establece variables de entorno. Instrucciones subsecuentes como RUN, o aplicaciones que corren en tiempo de ejecución, pueden utilizar directamente las variables de entorno definidas aquí.

ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"

Este ejemplo demuestra cómo hacer saltos de línea y cómo usar comillas dobles para encerrar valores que contienen espacios, lo cual es consistente con el comportamiento en Shell.

Una vez que las variables de entorno están definidas, pueden ser usadas en instrucciones subsecuentes. Por ejemplo, en el Dockerfile de la imagen oficial de node, hay código como este:

ENV NODE_VERSION 7.2.0

RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs

Las siguientes instrucciones soportan la expansión de variables de entorno: ADD, COPY, ENV, EXPOSE, FROM, LABEL, USER, WORKDIR, VOLUME, STOPSIGNAL, ONBUILD, RUN.

Puedes sentir de esta lista de instrucciones que las variables de entorno se pueden usar en muchos lugares, lo cual es muy poderoso. Con variables de entorno, podemos hacer más imágenes desde un solo Dockerfile, simplemente usando diferentes variables de entorno.

EXPOSE

El formato es EXPOSE <puerto1> [<puerto2>...].

La instrucción EXPOSE declara los puertos que el contenedor ofrecerá servicios cuando esté en ejecución. Esto es solo una declaración, y la aplicación no abrirá realmente estos puertos cuando el contenedor esté corriendo debido a esta declaración. Hay dos beneficios al escribir esta declaración en el Dockerfile: uno es ayudar al usuario de la imagen a entender los puertos de demonio de este servicio de imagen, lo cual es conveniente para configurar el mapeo de puertos; el otro es que cuando se usa mapeo de puertos aleatorio en tiempo de ejecución, es decir, docker run -P, mapeará automáticamente de manera aleatoria los puertos expuestos por EXPOSE.

La instrucción EXPOSE debe distinguirse de usar -p <puerto host>:<puerto contenedor> en tiempo de ejecución. -p mapea el puerto del host y el puerto del contenedor, en otras palabras, expone el servicio de puerto correspondiente del contenedor para el acceso externo, mientras que EXPOSE solo declara qué puertos tiene la intención de usar el contenedor, y no realiza automáticamente el mapeo de puertos en el host.

FROM

En un Dockerfile, la instrucción FROM especifica la imagen base, que es el punto de partida para construir una nueva imagen. Es la primera instrucción en el Dockerfile, utilizada para definir el entorno base para el proceso de construcción.

Usos comunes de la instrucción FROM

  1. Construcción a partir de una imagen oficial:

    FROM image:tag

    Este uso especifica una imagen oficial existente como la imagen base. image es el nombre de la imagen, y tag es la etiqueta de versión. Por ejemplo, puedes usar ubuntu:18.04 como imagen base.

  2. Construcción a partir de una imagen personalizada:

    FROM <nombre_usuario>/<nombre_imagen>:<tag>

    Este uso especifica una imagen personalizada como la imagen base. <nombre_usuario> es el nombre de usuario en Docker Hub, <nombre_imagen> es el nombre de la imagen, y <tag> es la etiqueta de versión.

  3. Construcción multi-etapa:

    FROM <imagen_base> AS <nombre_etapa>

    Este uso permite definir múltiples etapas de construcción en un solo Dockerfile y usar diferentes imágenes base para cada etapa. <imagen_base> es la imagen base, y <nombre_etapa> es el nombre de la etapa.

    Las construcciones multi-etapa son típicamente utilizadas para aprovechar diferentes herramientas y dependencias durante el proceso de construcción, y luego copiar los archivos o ejecutables requeridos de una etapa a la siguiente, reduciendo el tamaño de la imagen final.

  4. Construcción desde el sistema de archivos local:

    FROM scratch

    Este uso indica que la construcción comienza desde una imagen vacía, sin usar ninguna imagen base existente. En este caso, necesitas añadir tú mismo los archivos y configuraciones requeridas.

La instrucción FROM solo puede aparecer una vez en un Dockerfile y debe ser la primera instrucción. Define el punto de partida para el proceso de construcción, y las instrucciones subsecuentes se basarán en este punto de inicio.

HEALTHCHECK

Formato:

  • HEALTHCHECK [opciones] CMD <comando>: Establece el comando para verificar el estado de salud del contenedor.
  • HEALTHCHECK NONE: Si la imagen base tiene una instrucción de chequeo de salud, usa esta línea para anular su instrucción de chequeo de salud.

La instrucción HEALTHCHECK le dice a Docker cómo determinar si el estado del contenedor es normal. Esta es una instrucción nueva introducida en Docker 1.12.

Antes de la instrucción HEALTHCHECK, el motor de Docker solo podía determinar si un contenedor estaba en un estado anormal comprobando si el proceso principal dentro del contenedor había salido. En muchos casos, esto no es un problema, pero si el programa entra en un estado de interbloqueo o bucle infinito, el proceso de la aplicación no se cierra, pero el contenedor ya no puede proveer servicios. Antes de 1.12, Docker no detectaría tal estado en el contenedor y no lo reprogramaría, lo que resultaría en algunos contenedores que ya no podrían proveer servicios pero seguían aceptando solicitudes de usuarios.

Desde 1.12, Docker ha proporcionado la instrucción HEALTHCHECK, que especifica un comando para determinar si el estado del servicio del proceso principal sigue siendo normal, reflejando así más precisamente el estado real del contenedor.

Cuando se especifica una instrucción HEALTHCHECK en una imagen y se inicia un contenedor a partir de ella, el estado inicial será starting. Después de que la instrucción HEALTHCHECK pase, el estado cambia a healthy. Si la verificación falla consecutivamente un cierto número de veces, el estado cambia a unhealthy.

HEALTHCHECK soporta las siguientes opciones:

  • --interval=<intervalo>: El intervalo entre dos chequeos de salud, por defecto es de 30 segundos.
  • --timeout=<duración>: El tiempo de espera para que se ejecute el comando de chequeo de salud. Si supera este tiempo, el chequeo de salud actual se considera un fallo, por defecto es de 30 segundos.
  • --retries=<número>: Después de fallar consecutivamente el número especificado de veces, el estado del contenedor se considera unhealthy, por defecto es de 3 veces.

Al igual que CMD y ENTRYPOINT, HEALTHCHECK solo puede aparecer una vez. Si se escriben múltiples instancias, solo la última toma efecto.

El comando después de HEALTHCHECK [opciones] CMD tiene el mismo formato que ENTRYPOINT, con ambos formatos shell y exec. El valor de retorno del comando determina el éxito o fracaso del chequeo de salud: 0: éxito; 1: fallo; 2: reservado, no uses este valor.

Supongamos que tenemos una imagen que es un servicio web simple, y queremos agregar un chequeo de salud para determinar si su servicio web está funcionando correctamente. Podemos usar curl para ayudar con la determinación. El HEALTHCHECK en el Dockerfile se puede escribir así:

FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1

Aquí, establecemos el chequeo para que se ejecute cada 5 segundos (este intervalo es muy corto con fines de prueba y debería ser relativamente más largo en la práctica), y si el comando de chequeo de salud no responde dentro de 3 segundos, se considera un fallo. Usamos curl -fs http://localhost/ || exit 1 como el comando de chequeo de salud.

Usa docker build para construir esta imagen:

$ docker build -t myweb:v1 .

Después de construir, iniciamos un contenedor:

$ docker run -d --name web -p 80:80 myweb:v1

Después de ejecutar esta imagen, puedes ver el estado inicial es (health: starting) usando docker container ls:

$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 3 seconds ago Up 2 seconds (health: starting) 80/tcp, 443/tcp web

Después de esperar unos segundos y ejecutar docker container ls de nuevo, el estado de salud habrá cambiado a (healthy):

$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 18 seconds ago Up 16 seconds (healthy) 80/tcp, 443/tcp web

Si el chequeo de salud falla consecutivamente más veces que el recuento de reintentos, el estado cambiará a (unhealthy).

Para ayudar con la solución de problemas, la salida del comando de chequeo de salud (incluyendo stdout y stderr) se almacena en el estado de salud y se puede ver usando docker inspect.

$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool
{
"FailingStreak": 0,
"Log": [
{
"End": "2016-11-25T14:35:37.940957051Z",
"ExitCode": 0,
"Output": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\n body {\n width: 35em;\n margin: 0 auto;\n font-family: Tahoma, Verdana, Arial, sans-serif;\n }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCommercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n",
"Start": "2016-11-25T14:35:37.780192565Z"
}
],
"Status": "healthy"
}

LABEL

La instrucción LABEL se utiliza para agregar metadatos de pares clave-valor a una imagen.

LABEL <clave>=<valor> <clave>=<valor> <clave>=<valor> ...

También podemos usar algunas etiquetas para declarar el autor de la imagen, dirección de documentación, etc.:

LABEL org.opencontainers.image.authors="yeasy"

LABEL org.opencontainers.image.documentation="https://www.ubitools.com"

RUN

En un Dockerfile, la instrucción RUN se utiliza para ejecutar comandos dentro del contenedor. Puede ejecutar cualquier comando válido y script de shell.

Usos comunes de la instrucción RUN

  1. Ejecutar un solo comando:

    RUN <comando>

    En este uso, <comando> es el único comando que se ejecutará dentro del contenedor. Por ejemplo:

    RUN apt-get update
    RUN apt-get install -y package

    Esto ejecutará los comandos para actualizar las listas de paquetes e instalar un paquete dentro del contenedor respectivamente.

  2. Ejecutar múltiples comandos:

    RUN <comando1> && <comando2>

    Este uso permite ejecutar múltiples comandos de forma consecutiva en una línea, utilizando el operador && para asegurar que cada comando tenga éxito antes de ejecutar el siguiente. Por ejemplo:

    RUN apt-get update && apt-get install -y package

    Esto ejecutará los comandos para actualizar las listas de paquetes e instalar un paquete dentro del contenedor de manera secuencial, asegurándose de que el comando anterior tenga éxito antes de ejecutar el siguiente.

  3. Ejecutar un script de shell:

    RUN /bin/bash -c "<script>"

    Este uso permite ejecutar scripts de shell complejos dentro del contenedor. El script se coloca entre comillas dobles, y se utiliza /bin/bash -c para especificar que Bash debe interpretar y ejecutar el script. Por ejemplo:

    RUN /bin/bash -c "source setup.sh && build.sh"

    Esto ejecutará los scripts setup.sh y build.sh dentro del contenedor.

La instrucción RUN se puede utilizar múltiples veces, y cada instrucción ejecutará un comando dentro del contenedor. Cada instrucción RUN creará una nueva capa de imagen encima de la anterior.

Notas:

  • Los comandos ejecutados en una instrucción RUN afectarán permanentemente al contenedor, por lo que se deben incluir comandos de limpieza para evitar archivos y datos innecesarios en la imagen.
  • Si necesita utilizar variables de entorno en una instrucción RUN, puede definirlas en el Dockerfile utilizando la instrucción ENV.

SHELL

Formato: SHELL ["ejecutable", "parámetros"]

La instrucción SHELL puede especificar la shell que se usará para las instrucciones RUN, ENTRYPOINT y CMD. El predeterminado en Linux es ["/bin/sh", "-c"].

SHELL ["/bin/sh", "-c"]

RUN ls ; ls

SHELL ["/bin/sh", "-cex"]

RUN ls ; ls

Las dos instrucciones RUN ejecutan el mismo comando, pero la segunda RUN imprimirá cada comando y saldrá en caso de error.

Cuando ENTRYPOINT y CMD se especifican en formato shell, la shell especificada por la instrucción SHELL también se convertirá en la shell para estas dos instrucciones.

SHELL ["/bin/sh", "-cex"]

# /bin/sh -cex "nginx"
ENTRYPOINT nginx
SHELL ["/bin/sh", "-cex"]

# /bin/sh -cex "nginx"
CMD nginx

STOPSIGNAL

La instrucción STOPSIGNAL se usa para establecer la señal de llamada al sistema que se utilizará al enviar una señal de parada al contenedor. Esta instrucción acepta el valor de la señal o el nombre correspondiente de la señal como argumento.

Sintaxis:

STOPSIGNAL señal

Donde:

  • señal puede ser el valor numérico o el nombre de la señal, como SIGKILL.

Si no se especifica la instrucción STOPSIGNAL, la señal SIGTERM se enviará al usar el comando docker stop. Si el contenedor no se detiene dentro del tiempo especificado, se enviará la señal SIGKILL para terminar el contenedor por la fuerza.

Ejemplo 1:

FROM ubuntu:18.04
STOPSIGNAL SIGTERM
CMD ["/usr/bin/bash", "-c", "while true; do sleep 1; done"]

En el ejemplo anterior, establecemos que la señal SIGTERM se envíe como señal de parada. Cuando se ejecute el comando docker stop, el contenedor primero recibirá la señal SIGTERM, y si no se detiene normalmente dentro del tiempo dado, se enviará la señal SIGKILL para terminar el contenedor.

Ejemplo 2:

FROM ubuntu:18.04
STOPSIGNAL 9
CMD ["/usr/bin/bash", "-c", "while true; do sleep 1; done"]

En este ejemplo, usamos el valor de señal 9 para especificar el envío de la señal SIGKILL, que terminará directamente el proceso del contenedor sin esperar a una parada normal.

Tenga en cuenta que incluso si se establece STOPSIGNAL, Docker aún puede enviar la señal SIGKILL para terminar el contenedor en ciertas situaciones, como cuando el contenedor está en un estado irrecuperable.

USER

Formato: USER <nombre_de_usuario>[:<grupo>]

La instrucción USER es similar a WORKDIR, ya que ambos cambian el estado del entorno y afectan a las capas subsiguientes. WORKDIR cambia el directorio de trabajo, mientras que USER cambia la identidad del usuario para ejecutar los comandos RUN, CMD y ENTRYPOINT subsiguientes.

Tenga en cuenta que USER solo le ayuda a cambiar al usuario especificado; este usuario debe ser pre-creado, de lo contrario, no se puede cambiar a él.

RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]

Si un script se ejecuta como root pero desea cambiar la identidad durante la ejecución, como ejecutar un proceso de servicio como un usuario pre-creado, no utilice su o sudo, ya que requieren una configuración complicada y a menudo fallan en entornos sin TTY. Se recomienda usar gosu.

# Crear el usuario de redis, y usar gosu para cambiar a otro usuario y ejecutar comandos
RUN groupadd -r redis && useradd -r -g redis redis
# Descargar gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.12/gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
# Establecer CMD, y ejecutarlo como otro usuario
CMD [ "exec", "gosu", "redis", "redis-server" ]

VOLUME

Formato:

  • VOLUME ["<path1>", "<path2>"...]
  • VOLUME <path>

Como se mencionó anteriormente, las capas de almacenamiento de contenedores deben mantenerse de solo lectura durante el tiempo de ejecución tanto como sea posible. Para las aplicaciones que necesitan almacenar datos dinámicos, como bases de datos, sus archivos de base de datos deben almacenarse en volúmenes. Presentaremos más a fondo el concepto de volúmenes de Docker en capítulos posteriores. Para evitar que los usuarios olviden montar directorios para datos dinámicos como volúmenes durante el tiempo de ejecución, podemos especificar ciertos directorios para ser montados como volúmenes anónimos en el Dockerfile. De esta manera, incluso si el usuario no especifica un montaje, la aplicación aún puede funcionar normalmente sin escribir una gran cantidad de datos en la capa de almacenamiento del contenedor.

VOLUME /data

En este ejemplo, el directorio /data se montará automáticamente como un volumen anónimo durante el tiempo de ejecución del contenedor. Cualquier dato escrito en /data no se registrará en la capa de almacenamiento del contenedor, asegurando la naturaleza sin estado de la capa de almacenamiento del contenedor. Por supuesto, esta configuración de montaje puede ser sobrescrita al ejecutar el contenedor. Por ejemplo:

$ docker run -d -v mydata:/data xxxx

En este comando, el volumen nombrado mydata se monta en la ubicación /data, sobrescribiendo la configuración de montaje del volumen anónimo definida en el Dockerfile.

WORKDIR

Formato: WORKDIR <ruta del directorio de trabajo>

La instrucción WORKDIR se puede utilizar para especificar el directorio de trabajo (o directorio actual). Después de esta instrucción, el directorio actual para las capas subsiguientes se establecerá en el directorio especificado. Si el directorio no existe, WORKDIR lo creará para ti.

Anteriormente, mencionamos un error común cometido por principiantes al tratar el Dockerfile como un script de shell. Este malentendido también podría llevar al siguiente error:

RUN cd /app
RUN echo "hello" > world.txt

Si construyes una imagen a partir de este Dockerfile y la ejecutas, descubrirás que el archivo /app/world.txt no se encuentra, o su contenido no es hello. La razón es sencilla: en la shell, dos líneas consecutivas son parte del mismo entorno de ejecución de proceso, por lo que el estado de memoria modificado por el comando anterior afectará directamente al siguiente comando. Sin embargo, en un Dockerfile, estos dos comandos RUN se ejecutan en entornos de contenedores completamente diferentes, que son dos contenedores totalmente separados. Este es un error causado por una falta de comprensión del concepto de almacenamiento por capas en las construcciones de Dockerfile.

Como se mencionó antes, cada RUN inicia un nuevo contenedor, ejec cd /app` solo cambia el directorio de trabajo del proceso actual, que es simplemente un cambio en memoria y no resulta en ningún cambio de archivo. Para cuando se alcanza la segunda capa, se inicia un contenedor completamente nuevo, completamente no relacionado con el contenedor de la primera capa, por lo que no puede heredar los cambios en memoria del proceso de construcción anterior.

Por lo tanto, si necesitas cambiar la ubicación del directorio de trabajo para las capas subsiguientes, debes utilizar la instrucción WORKDIR.

WORKDIR /app

RUN echo "hello" > world.txt

Si usas una ruta relativa en tu instrucción WORKDIR, el directorio al que se cambia es relativo al WORKDIR anterior:

WORKDIR /a
WORKDIR b
WORKDIR c

RUN pwd

El directorio de trabajo para RUN pwd será /a/b/c.