Docker es una herramienta que nos provee un sin fin de posibilidades a la hora de realizar nuestros despliegues de aplicaciones, emular ambientes productivos, y proveernos de un repositorio con imágenes listas para crear contenedores que nos solucionan la vida a los programadores. Pero que pasa si no es suficiente con las imágenes pre-construidas en Docker Hub para emular el ambiente que necesito...
Aquí es donde entra en juego Dockerfile, que es simplemente un archivo que contiene las instrucciones propias de dockerfile y del sistema operativo sobre el que se va a basar nuestra imagen (Por ejemplo Linux), dandonos la posibilidad de customizar nuestras imágenes para que nuestro ambiente tenga las características que necesitamos.
A continuación se presentaran los comandos más comunes que se utilizan al crear un Dockerfile (No quiere decir que sean los únicos).
Comandos comunes
FROM: Determina la imagen base a partir de la cual se construirá la nueva imagen. Esta instrucción se debe encontrar en la primera linea del documento.
MAINTAINER: Es una instrucción opcional, y nos indica quien es el que creo y va a mantener actualizada la imagen.
RUN: Es usada para la ejecución de un comando del sistema operativo durante el proceso de construcción de la imagen.
ADD: Copia un archivo de la máquina anfitriona a la nueva imagen.
ENV: Define una variable de entorno
CMD: Usada para ejecutar comandos una vez que se cree un contenedor a partir de la nueva imagen.
ENTRYPOINT: Define el comando por defecto que se ejecutará en el contenedor una vez que este corriendo.
WORKDIR: Es la directiva para la ejecución del comando CMD.
USER: Establece el usuario o UID para el contenedor creado a partir de la imagen.
VOLUME: Habilita el enlace de directorios entre el contenedor y la maquina anfitriona.
EXPOSE: Expone un puerto por defecto al crear nuestro contenedor.
Creación del Dockerfile
Para la parte práctica vamos a crear un archivo Dockerfile de ejemplo muy básico. Para ello vamos a nuestra consola y creamos un archivo:
touch Dockerfile
Puede nombrar sus Dockerfiles como quiera. El nombre de archivo predeterminado es Dockerfile (sin una extensión), y usar el predeterminado puede facilitar varias tareas al trabajar con contenedores.
Luego de crearlo vamos a editarlo:
vim Dockerfile
Dentro del archivo vamos a escribir lo siguiente:
#Imagen base a partir de la cual se creará nuestra nueva imagen
FROM debian
#Se ejecuta una actualizacion del sistema operativo, se instala apache, se eliminan dependencias innesesarias y se remueve todo el contenido /var/lib/apt/list/ forzando e ignorando todos los mensajes de confirmacion
RUN apt-get update && apt-get install -y apache2 && apt-get clean && rm -rf /var/lib/apt/lists/*
#Se definen variables de entorno
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
#Se expone el puerto 80
EXPOSE 80
#Se define el comando por defecto que se ejecutará una vez que esté corriendo el contenedor
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]
¿Cómo se crea la imagen?
Para crear la imagen a partir del Dockerfile no se necesita mas que la siguiente instrucción: "docker build -t <nombre que le queremos poner a la imagen> <ruta donde se encuentra el Dockerfile>". Si el Dockerfile se encuentra en el mismo directorio donde nos encontramos el comando se simplifica a "docker build -t <nombre que le queremos poner a la imagen> . ".
Ejecutaremos:
docker build -t apache-ejemplo .
Se realizará la ejecución de los comandos definidos en el dockerfile.
Como se ve en la imagen, cada instrucción del archivo es una capa, en la que se crea un contenedor intermedio para continuar con la siguiente instrucción. Esto tiene de ventaja que al crear la imagen nuevamente el tiempo de ejecución se reduzca ya que se guarda esas capas intermedias, por lo tanto no necesita crearlas nuevamente. La desventaja de esto es que la imagen final,si posee muchas capas, pesará demasiado. Es por ello que se trata de reducir la cantidad de instrucciones lo mas que se pueda, utilizando buenas prácticas, como por ejemplo en nuestro primer RUN concatenar instrucciones del sistema operativo con && en vez de utilizar un RUN por cada instrucción.
Una vez creada nuestra imagen podremos verla con el comando:
docker images
Ahora para crear nuestro contenedor ejecutaremos la siguiente instrucción:
docker run --name apache-ejemplo -p 8889:80 -d apache-ejemplo
Como se definió en el Dockerfile, el puerto por defecto que se expone en el container es el puerto 80, al cual lo linkeamos al puerto 8889 de la máquina anfitriona (O en su defecto un puerto que en nuestra máquina no este en uso).
Luego de esto accedemos a "localhost:8889" y veremos la siguiente página.
Sin mas que agregar, espero que este ejemplo sea de ayuda para comprender las ventajas, desventajas y la facilidad que nos provee la creación de imágenes customizadas a partir de un Dockerfile.
Muchas Gracias!