Las 10 Instrucciones Imprescindibles para Crear un Dockerfile
En este artículo vas a aprender las 10 instrucciones de Dockerfile que necesitas saber para crear tus propias imágenes de Docker.
¿Qué contiene este artículo?
- Qué diferencia una imagen de Docker de un contenedor.
- Verás la lista COMPLETA de instrucciones que acepta Dockerfile.
- Entraremos en detalle para ver cómo usar las 10 instrucciones más importantes de Dockerfile.
- Finalmente entenderás qué diferencia CMD de ENTRYPOINT (especialmente interesante).
- Cuál es la diferencia entre COPY y ADD.
¿Cómo se crean las imágenes de Docker?
Cuando queremos “Contenedorizar” un proyecto, lo primero que necesitamos en un Dockerfile, un documento de texto que contiene una serie de instrucciones que podemos ejecutar para crear una imagen de Docker.
Lo que nos lleva a la siguiente pregunta…
¿Qué es exactamente una imagen de Docker?
Una imagen de Docker es la plantilla que usamos para crear un contenedor de Docker.
Las imágenes son EL concepto central en el mundo de los contenedores. Escribimos Dockerfiles para crearlas, las guardamos en registros y las importamos para crear contenedores con ellas.
Una imagen de Docker consiste en una serie de capas de “solo-lectura”, cada una de las cuales representa una instrucción del Dockerfile. Las capas se amontonan unas sobre otras y cada una añade algo sobre la anterior.
Cuando creamos un contenedor, estamos añadiendo una capa escribible encima de todas las demás capas de solo-lectura:
Usaremos el comando “docker build” dentro del directorio del proyecto para crear la imagen a partir de un Dockerfile:
docker build .
Podemos especificar un archivo de texto diferente al Dockerfile del directorio actual con el “flag -f”:
docker build -f ../algundirectorio/Dockerfile.debug .
La Sintaxis de Dockerfile
La sintaxis de Dockerfile es sencilla de aprender y con ella podemos empaquetar cualquier clase de aplicación. Además es bastante flexible, cada tarea tiene su propio comando pero si necesitamos de alguna funcionalidad que no tenga un comando propio siempre podemos usar comandos de terminal (Bash en Linux o PowerShell en Windows).
Una instrucción de Dockerfile es una palabra en mayúsculas seguida de uno o más argumentos. Cada línea de un archivo Dockerfile contiene una instrucción que se ejecuta de manera independiente a las demás, y provoca la creación de una nueva imagen (cada una de las capas de la imagen final).
Cuando ejecutamos un comando build de docker, las instrucciones son procesadas de arriba a abajo, línea a línea.
Veamos un ejemplo sencillo de Dockerfile:
FROM ubuntu:18.04COPY . /appRUN make /appCMD python /app/app.py
Cada una de las instrucciones crea una capa de la imagen:
- FROM nos proporciona una imagen base con ubuntu:18.04.
- COPY añade archivos desde nuestro directorio local.
- RUN crea la aplicación con make.ubuntu:18.04
- CMD specifica que comandos queremos ejecutar en el contenedor.
En el ejemplo anterior hemos podido crear una imagen con solo 4 instrucciones.
Cada proyecto es diferente y necesitará de una configuración diferente. Por eso es importante conocer las opciones disponibles, nunca sabemos cuándo nos van a ser útiles.
Todas las Instrucciones de Docker
A continuación puedes ver la lista completa de instrucciones que podemos usar para crear un Dockerfile:
FROM — Especifica la base para la imagen.
ENV — Establece una variable de entorno persistente.
ARG — Permite definir una variable usable en el resto del Dockerfile con la sintaxis ${NOMBRE_DEL_ARG}
RUN — Ejecuta el comando especificado. Se usa para instalar paquetes en el contenedor.
COPY — Copia archivos y directorios al contenedor.
ADD — Lo mismo que COPY pero con la funcionalidad añadida de descomprimir archivos .tar y la capacidad de añadir archivos vía URL.
WORKDIR — Indica el directorio sobre el que se van a aplicar las instrucciones siguientes.
ENTRYPOINT — Docker tiene un Entrypoint por defecto, /bin/sh -c, que se ejecuta cuando el contenedor está listo. Este comando permite sobreescribirlo.
CMD — Especifica el comando y argumentos que se van a pasar al contenedor. Se ejecutarán junto con lo indicado en el Entrypoint.
EXPOSE — Indica que puerto del contenedor se debe exponer al ejecutarlo. No lo expone directamente.
LABEL — Nos permite aportar meta-datos a la imagen.
VOLUME — Crea un directorio sobre el que se va a montar un volumen para persistir datos más allá de la vida del contenedor.
USER — Establece el usuario que se va a usar cuando se ejecute cualquier operación posterior con RUN, CMD y ENTRYPOINT.
SHELL — Permite especificar el uso de otra terminal, como zsh, csh, tcsh, powershell, u otras..
STOPSIGNAL — Indica una señal que va a finalizar el contenedor.
HEALTHCHECK — Indica a Docker una manera de testear el contenedor para verificar que sigue funcionando correctamente.
ONBUILD — Cuando la imagen donde se encuentra se use como base de otra imagen, va a actuar de trigger y va a ejecutar el comando que le indiquemos.
Las Principales Instrucciones de Docker EXPLICADAS
FROM
Un archivo Dockerfile solo puede empezar por dos tipos de instrucción, una es ARG y la otra es FROM.
Todas las imágenes Docker necesitan de otra imagen que les sirva de base. FROM sirve para indicar que imagen queremos usar.
Modo de empleo:
FROM ubuntu:18.04
En el ejemplo anterior usamos la imagen Ubuntu, disponible en el registro oficial de Docker.
Normalmente las imágenes oficiales solo indican un nombre. Si quisiéramos usar una imagen no oficial deberíamos especificar a qué usuario pertenece:
nginx/nginx-ingress
En el ejemplo de Ubuntu, además, especificamos que queremos la imagen con el Tag 18.04. En caso de no indicar ningún tag, la versión que Docker va a descargar será la “latest”.
ENV
Se usa para declarar variables de entorno que van a ser visibles para las siguientes instrucciones y para el contenedor resultante.
Modo de empleo:
ENV <key> <value>ENV <key>=<value>
Las dos formas son completamente equivalentes.
RUN, CMD y ENTRYPOINT
La sintaxis de Dockerfile a menudo nos permite conseguir el mismo resultado de muchas formas diferentes. Este es el caso con RUN, CMD y ENTRYPOINT, todas estas instrucciones permiten ejecutar comandos. Aunque cada uno de ellos sirve para una situación diferente.
Explicación corta:
RUN — Se usa para ejecutar comandos relacionados con la instalación de paquetes.
CMD — Indica el comando y argumentos que va a ejecutar el entrypoint.
ENTRYPOINT — Se encarga de ejecutar un comando cuando el contenedor arranca. Por defecto el entrypoint es “/bin/sh -c”.
Ejemplo:
FROM ubuntuENTRYPOINT ["sleep"]CMD ["10"]
En este caso, cambiamos el entrypoint por defecto para que se ejecute el comando sleep y usamos CMD para indicar el argumento “10”.
Explicación larga:
RUN no solo ejecuta una instrucción, sino que además crea una imagen después de haberse ejecutado.
Docker internamente cachea todas las imágenes con las que trata, una vez crea una imagen y la tiene almacenada, nunca más la va a crear.
Cuando creamos un contenedor, y se generan todas sus capas, es probable que la primera ejecución tarde algo de tiempo. La segunda vez, Docker ya tendrá todas las imágenes que necesita para crear el contenedor en un instante.
Si usamos RUN, aprovechamos este mecanismo para guardar el resultado de su ejecución. Por lo que cuando creemos un contenedor por segunda vez ya no se tendrá que calcular más.
Esto significa que RUN no solo se puede usar para instalar paquetes, pero es especialmente útil para eso.
RUN pip install pip
Ahora lo importante, ¿cuál es la diferencia entre ENTRYPOINT y CMD?
Cuando iniciamos un contenedor, podemos indicar una lista de argumentos que le queremos proporcionar:
docker run tu_imagen arg1 arg2
Los argumentos que usemos sobrescribirán el valor de CMD en caso de haberlo y serán ejecutados junto con el comando del ENTRYPOINT.
Lo que se ejecute en el contenedor será el resultado de los valores que tenga ENTRYPOINT + los valores que tenga CMD, que pueden ser sustituidos con los argumentos con los que ejecutemos el contenedor.
Como el ENTRYPOINT por defecto es “/bin/sh -c”, nos permite ejecutar cualquier comando de terminal directamente con CMD. En la práctica solemos ver lo siguiente:
CMD ["ejecutable", "param1", "param2"]Es equivalente a:CMD ejecutble param1 param2
Aunque si cambiamos el ENTRYPOINT, el modo de empleo de CMD pasa a ser:
CMD ["param1","param2"]
ENTRYPOINT permite indicarle argumentos, aunque estos no van a ser sobreescribibles vía terminal:
ENTRYPOINT [“/bin/echo”, “Hello”]
COPY y ADD
La instrucción COPY, indica a Docker que coja los archivos de nuestro equipo y los añada a la imagen con la que estamos trabajando. COPY crea un directorio en caso de que no exista.
Modo de empleo:
COPY . ./appCOPY <fuente> <destino>
Indica que queremos coger todos los archivos del directorio actual (.) y trasladarlos a al directorio ./app de la imagen.
ADD hace exactamente lo mismo que COPY, pero además añade dos funcionalidades. La primera es que permite indicar contenidos vía URL, y la segunda es la extracción de archivos TAR.
ADD http://example.com/directorio /usr/local/remote/ADD recursos/jdk-7u79-linux-x64.tar.gz /usr/local/tar/
WORKDIR
WORKDIR se usa para cambiar el directorio actual para las siguientes instrucciones: COPY, ADD, RUN, CMD y ENTRYPOINT.
Modo de empleo:
Para dar claridad a tus archivos Dockerfile, es preferible usar WORKDIR a otra instrucción para cambiar el directorio actual de la imagen. Si el directorio especificado no existe, se creará automáticamente. Además, podemos usar este comando varias veces en el mismo archivo, si tenemos que trabajar con varios directorios a la vez.
FROM ubuntu:18.04WORKDIR /proyectoRUN npm installWORKDIR ../proyecto2RUN touch archivo1.cpp
EXPOSE
La instrucción EXPOSE muestra que puerto queremos publicar para acceder al contenedor y opcionalmente el protocolo. Es muy importante tener claro que esta instrucción NO publica directamente el puerto. Su función es comunicar a la persona que va a usar la imagen qué puerto se deben usar.
Modo de empleo:
EXPOSE 80/udp
VOLUME
La instrucción VOLUME crea un punto en el sistema de archivos de la imagen que se va a usar para montar un volumen externo. Eso significa que la información que guardemos en el directorio indicado va a perdurar en caso de que el contenedor deje de ejecutarse.
Modo de empleo:
FROM ubuntuRUN mkdir /mivolRUN echo “hello world” > /mivol/holaVOLUME /mivol
Para terminar
Saber crear un Dockerfile es imprescindible si realmente queremos dominar Docker. Espero que este artículo te haya ayudado a entender mejor las opciones que tenemos disponibles a la hora de crear imágenes con este archivo.
En el futuro voy a revisitar con más detalles algunos de los conceptos que he tratado aquí. Sígueme para asegurarte que no te lo pierdes!
Si este artículo te ha parecido útil, por favor, ayuda a otros a econtrarlo. ¿Crees que le puede servir a alguien que conozcas?
En Medium escribo sobre las tecnologías punteras que me apasionan. Puedes seguirme aquí y leer otros artículos que he publicado aquí.