Introducción a Dockerfile
May 2, 2021 ‧ 30 min estimados ‧ #docker #dockerfile #custom-image |
Contenido ¿Cuándo conviene utilizar un Dockerfile? ¿Cómo se utiliza un Dockerfile? Ejemplo de aplicación de Dockerfile |
Lo que vas a ver en este documento son los siguientes temas:
Docker es una herramienta que permite automatizar la creación y el despliegue de aplicaciones utilizando contenedores de software. A la hora de trabajar con Docker podés utilizar imágenes de registros públicos como Dockerhub y también podés crear tus propias imágenes con las necesidades de cada proyecto.
Para crear una imagen personalizada es necesario que utilices Dockerfile, un archivo que contiene las instrucciones necesarias para la creación de la imagen, para que luego de compilarla puedas ejecutar sus contenedores. En la figura siguiente podés ver un diagrama.
Una imagen es un snapshot (captura) de un sistema de archivos creado a partir de distintas capas. Internamente Docker utiliza el sistema de archivos UnionFS para unir las capas y crear las imágenes.
Lo más común a la hora de trabajar con imágenes personalizadas de Docker es partir de una imagen base que posea la mayor cantidad de funcionalidades que necesitas, y luego ir agregando capas adicionales con tus necesidades, a las capas que ya existen en la imagen base. Este mecanismo resulta bastante eficiente, ya que una imágen personalizada de Docker sólo ocupa la diferencia con la imagen inicial.
También es posible crear tu propia imagen base. Este es un paso avanzado, y podés encontrar más información al respecto en este link de documentación oficial de Docker. |
Para que tengas una idea general, el proceso de crear una imagen de Docker a medida se compone de los siguientes pasos:
Los proveedores y desarrolladores de tecnología (bases de datos, servidores, herramientas, etc.) proporcionan sus imágenes genéricas.
Si bien para varios proyectos estas imágenes por defecto cumplen con lo que necesitas, cuando querés personalizar algún funcionamiento es necesario que armes una imagen a medida.
Esto sucede bastante seguido a la hora de trabajar con Docker, por lo que aprender a utilizar Dockerfile es una parte esencial del proceso.
Docker provee un conjunto de instrucciones que deben ser utilizadas dentro de los archivos Dockerfile, para que se pueda crear una imagen.
Básicamente, dentro del Dockerfile tenés que realizar una descripción de los pasos y comandos necesarios para construir la imagen. Una vez que tenés listo el archivo Dockerfile con los comandos necesarios, lo podés compilar y luego crear contenedores de esa imagen con el comando docker run.
Además de utilizar los comandos de Docker dentro de un Dockerfile, es necesario que conozcas el contexto que necesitan las diferentes tecnologías o herramientas que utilices, y eso depende de cada proyecto en particular.
Por ejemplo, para crear una imagen de PHP para un proyecto web, además de configurar el Dockerfile, es necesario que conozcas los detalles específicos como métodos de configuración, extensiones, variables de entorno, entre otros.
Yendo a lo pragmático, antes de crear una imagen a medida es conveniente que primero busques en Dockerhub si ya no existe eso que estás buscando, o al menos ver si hay una imagen lo más parecida posible a lo que necesitas. Si tenés suerte comenzá descargandola a tu máquina con docker pull. También en caso que la imagen tenga un buen README leelo bien para informarte de todos los detalles que necesitás saber. Cuando ya estés más o menos en tema empezá a experimentar ejecutando la imagen de distintas maneras, probando puertos, volúmenes, redes y demás. Esto te permite comenzar con una tecnología en poco tiempo ya que la podés utilizar sin entender todos los detalles. A medida que la vas usando vas a entender los detalles más específicos.
Si no tuviste suerte a la hora de encontrar una imagen parecida, podés seguir la guía de pasos a continuación que te van a llevar por el proceso de crear una imagen a medida, que puede ser un buen punto de partida para que tengas un mayor entendimiento del ecosistema Docker.
El ejemplo de uso de Dockerfile que vamos a ver consiste en partir de una imagen base de Alpine Linux obtenida de Dockerhub, a la cual le vamos a instalar las herramientas Vim, Curl y Git.
La imagen a crear no cumple ninguna función real en una aplicación, pero muestra todos los pasos necesarios para crear una imagen a medida de manera sencilla. |
Una buena forma de entender lo que vamos a hacer es que primero descargues la imagen base y veas si las herramientas Vim, Curl o Git se encuentran en la imagen base. Para ello comenzá descargando la imagen base.
docker pull alpine:3.4 |
Una vez que se haya descargado, ejecuta este comando para correr el shell dentro del contenedor.
docker run --rm --interactive alpine:3.4 /bin/sh |
Una vez que ejecutes el comando ya vas a estar dentro del contenedor. Ahora lo que vamos a probar es chequear las versiones de Vim, Curl y Git. Para cada uno de los comandos deberías ver una salida como la siguiente.
/ # vim --version sh: vim: not found / # curl --version sh: curl: not found / # git --version sh: git: not found |
Como podrás notar, la imagen base de Alpine no cuenta con ninguna de estas herramientas. Ahora sí podemos comenzar con el proceso.
Como primer paso crea una carpeta llamada dockerfile_test y dentro un archivo llamado Dockerfile (sin extensión). Desde Linux podés utilizar estos comandos.
mkdir dockerfile_test && cd dockerfile_test/ |
Todo Dockerfile debe comenzar con la instrucción FROM, que indica el punto de partida para comenzar a construirse. Generalmente se comienza la construcción a partir de otras imágenes aunque también es posible comenzar de una imagen vacía.
Las imágenes las podés descargar de cualquier registry, ya sea público o privado. La imagen desde donde se comienza a construir se llama “imagen base” y para este ejemplo es alpine:3.4. Agregá la instrucción FROM con la imagen base al Dockerfile.
FROM alpine:3.4 |
Ahora que ya tenemos la base es necesario instalar los paquetes que queremos. Agreguemos las líneas para descargar los paquetes. En este punto, el archivo debería verse de la siguiente manera.
FROM alpine:3.4 |
Toda imagen de Docker debe realizar alguna acción al iniciar si no se le especifica ninguna acción en particular al momento de ejecutar el comando docker run. Para este caso vamos a hacer que se corra la shell al iniciar el contenedor a través de la instrucción CMD. Agregando esta línea, el archivo Dockerfile debería verse así.
FROM alpine:3.4 |
Con el archivo Dockerfile listo ya podemos construir la imagen. Abrí una terminal dentro de la carpeta dockerfile_test y corré el siguiente comando para construir la imagen.
docker build --tag local/alpine-iot:latest . |
El comando se estructura de la siguiente manera:
En la terminal deberías ver una salida similar a la siguiente.
Sending build context to Docker daemon 2.048kB ---> 9d8ed3dbe13b Successfully built 9d8ed3dbe13b Successfully tagged local/alpine-iot:latest |
En el paso anterior Docker creó una imagen a partir del Dockerfile. En la lista de imágenes del sistema debería aparecer la imagen que recién creaste. Ejecuta en la terminal el comando docker images para ver sus detalles.
REPOSITORY TAG IMAGE ID CREATED SIZE |
La imagen ya está creada. Ahora vamos a correr el contenedor para ver lo que hay dentro con este comando.
docker run --rm --interactive local/alpine-iot:latest |
Si no hubo errores te debería aparecer un prompt (#) en la terminal que indica que estás dentro del contenedor (en algunos casos puede que el # no aparezca pero de todas maneras estás dentro del contenedor). Chequeá las versiones de vim y curl ejecutando los comandos vim --version y curl --version. En la terminal debería verse una salida como esta:
/ # vim --version |
Como podés ver, dentro de la imagen que recién creamos existen estas dos herramientas nuevas que no existían en la imagen base (cuando ejecutamos el paso 0).
Hasta ahora instalamos Vim y Curl dentro de la imagen y aún queda pendiente instalar Git. La razón por la que no lo instalamos todavía fue para demostrar el funcionamiento del caché de Docker.
Como podés ver en el proceso de construcción (punto 5), los pasos necesarios para construir la imagen en ese punto eran 3 (uno para cada comando especificado en el Dockerfile). Si miras en detalle, para cada paso se ejecuta un nuevo contenedor como capa intermedia que queda guardada en caché.
Podés ver esto cuando se muestra en la salida "---> Running in e2dd06c49534". Por ejemplo, la primera capa es descargar la imagen base de Alpine, luego se actualiza el repositorio de aplicaciones y se instalan Vim y Curl, y finalmente se crea una capa para CMD.
Si corrieras nuevamente el comando verías que el tiempo de ejecución es prácticamente instantáneo, ya que los pasos intermedios están guardados en la memoria caché. Para corroborarlo, corré el comando docker images -a, y chequeá que las imágenes <none> tienen los mismos IDs que los mostrados en el proceso de construcción.
REPOSITORY TAG IMAGE ID CREATED SIZE |
Finalmente, agregá la línea para instalar Git al Dockerfile. Debería quedarte así:
FROM alpine:3.4 apk add vim && \ apk add curl && \ apk add git
|
Ahora corré nuevamente el comando para construir la imagen.
docker build --tag local/alpine-iot:latest . |
Como podés ver en la salida, Docker hace uso del caché para las capas ya existentes (fijate que en la salida aparece Using cache para los pasos intermedios).
En caso que no quieras utilizar el caché de imágenes de Docker, podés especificar la opción --no-cache=True al momento de ejecutar el comando docker build. |
Una vez compilado accedé nuevamente al contenedor para ver lo que hay dentro.
docker run --rm --interactive local/alpine-iot:latest |
Chequeá la versión de Git con el comando a continuación, que te va a demostrar que la herramienta está instalada dentro de la nueva imagen construida.
/ # git --version |
Si listás nuevamente las imágenes con docker images -a vas a ver que hay una nueva imagen sin nombre ni tag (<none>), que es la imagen que se construyó previamente al instalar Git. Docker llama a estas imágenes Dangling images, y lo mejor es borrarlas una vez que terminaste de construir la imagen ya que no cumplen ninguna función. Corré el siguiente comando para eliminarlas todas juntas en un mismo paso.
docker rmi -f $(docker images -f "dangling=true" -q) |
A través de los pasos que fuimos realizando pudiste ver cómo crear una imagen de Docker a medida. Como nombramos anteriormente, saber crear imágenes es una parte esencial a la hora de trabajar con el ecosistema Docker.
En esta sección vamos a ver algunas recomendaciones generales a la hora de utilizar Dockerfiles y crear imágenes a medida. Si querés ampliar en el tema podés encontrar más información en este link.
Escribir Dockerfiles es relativamente simple. La parte más compleja es saber los detalles necesarios para crear correctamente un entorno de trabajo. Esto depende fuertemente de la tecnología que estés utilizando, por lo que es necesario leer su documentación.
Más allá de la tecnología, una buena metodología de trabajo es la siguiente:
Idealmente un contenedor debería ejecutar únicamente un solo proceso. Deberías diseñarlos como si se tratase de un binario ejecutable, esto facilita su portabilidad y despliegue.
También tenés que tener en cuenta lo que se instala dentro de la imagen (NO ES UNA MÁQUINA VIRTUAL). Cualquier herramienta que no sea indispensable para la ejecución del proceso no deberías incluirla.
Durante el proceso de creación de la imagen todo el directorio donde se encuentre el Dockerfile es cargado en el Build Context, aunque como mencionamos en el punto anterior, deberías incluir solo lo necesario para la correcta ejecución del contenedor. Si hay más archivos o directorios no indispensables es conveniente utilizar un archivo .dockerignore (análogo a .gitignore) para evitar incluir archivos o directorios innecesarios.
Si utilizas el menor número de instrucciones posible la imagen tendrá menos capas y será más liviana. Una opción es encadenar comandos en una sola instrucción.
Internamente Docker crea cachés de cada capa que utiliza para la creación de una imagen específica. Si utilizas esta técnica, podés lograr mejores tiempos de compilación y menos espacio para construir una imagen. Por ejemplo, en lugar de realizar estas instrucciones dentro del Dockerfile.
RUN apk update RUN apk add git |
Podés reescribirlas de la siguiente manera, generando así una única capa y optimizando así el tiempo de ejecución.
RUN apk update && \ apk add vim && \ apk add curl && \ apk add git |
Todas las imágenes deben indicar siempre una acción por defecto al iniciar un contenedor. En caso que la imagen esté diseñada para ejecutarse como un binario, la instrucción a utilizar debe ser ENTRYPOINT. De otro modo debes utilizar la instrucción CMD.
Para que puedas subir imágenes personalizadas y que otras personas puedan descargar tus imágenes creadas, podés crear una cuenta en el registry público Dockerhub (opción Sign Up). Es necesario que verifiques tu email, y luego podes seguir esta secuencia de pasos:
docker login --username=yourhubusername |
Aparecerá un warning que la contraseña está alojada sin encriptar. Si querés podes crear una key para securizar el proceso. |
docker tag local/alpine-iot:latest username/repository:latest |
docker tag local/alpine-iot:latest username/repository:latest |
Con los pasos anteriores, la imagen estará disponible en Dockerhub para ser descargada. Intentá ahora descargarla con el siguiente comando.
docker pull username/repository:latest |
Una vez que termine el proceso, en el listado de imágenes del sistema debería aparecer la nueva imagen. Corré el comando docker images para chequearlo.
En este artículo vimos una parte muy importante a la hora de trabajar con el ecosistema Docker que se puede resumir en los siguientes temas:
Este material es distribuido bajo licencia Creative Commons BY-SA 4.0. Podés encontrar detalles sobre el uso del material en este link.