Saltar al contenido principal

Dockerfiles y Docker Build

En este apartado vamos a ver cómo se construyen las imágenes utilizando docker build y las instrucciones esenciales de un Dockerfile para empaquetar tu aplicación o servicio a un contenedor.

Dentro vídeo: TODO

Dockerfile

Repasamos brevemente qué es un Dockerfile y cómo se estructura. Si quieres refrescar, puedes ver la sección de Conceptos básicos de Docker

Un Dockerfile es un archivo de texto que contiene una serie de instrucciones que Docker leerá y ejecutará para construir una imagen de contenedor. Cada instrucción en un Dockerfile crea una capa en la imagen.

Un Dockerfile se compone de una serie de instrucciones, cada una de ellas en una línea diferente. Las instrucciones más comunes son:

  • FROM: Indica la imagen base que se utilizará para construir la nueva imagen.
  • RUN: Ejecuta comandos en la imagen.
  • COPY: Copia archivos o directorios desde el host a la imagen.
  • WORKDIR: Establece el directorio de trabajo por defecto para el resto de instrucciones.
  • CMD: Define el comando que se ejecutará cuando se inicie un contenedor a partir de la imagen.
  • ENTRYPOINT: Define el comando que se ejecutará cuando se inicie un contenedor a partir de la imagen, pero no se puede sobreescribir.

Hay más instrucciones que iremos viendo a lo largo del curso, pero para empezar, estas son las más importantes.

Vamos a ver dos ejemplos de Dockerfile, el primero para publicar nuestra web en un servidor web Nginx (usando la imagen de anteriores vídeos) y el segundo para ejecutar un script de Python. Así tendremos tanto la visión de un servicio final al que le simplemente le copiamos archivos, como la de una aplicación que necesita ser construida y ejecutada.

Dockerfile para Nginx

Supongamos que tenemos un fichero index.html en nuestro directorio actual y queremos publicarlo en un servidor web Nginx. Voy a poner algo simple de ejemplo:

<!DOCTYPE html>
<html>
<head>
<title>¡Hola Dockermaníaco!</title>
</head>
<body>
<h1>¡Hola Dockermaníaco!</h1>
<p>No olvides suscribirte a mi canal de Youtube y darle una estrellita al repositorio de Github.</p>
</body>
</html>

Y el Dockerfile sería algo así:

# Utilizamos la imagen oficial de Nginx
FROM nginx:alpine

# Copiamos el fichero index.html al directorio /usr/share/nginx/html
COPY index.html /usr/share/nginx/html/index.html

Ya ves lo simple que es consumir imágenes oficiales de productos finales como Nginx. Solo necesitas copiar tus archivos al directorio adecuado y listo.

Dockerfile para Python

Supongamos que tenemos un script de Python que queremos ejecutar en un contenedor. Por ejemplo, un script que imprime "¡Hola Dockermaníaco!" cada segundo. El script sería algo así:

import time

while True:
print("¡Hola Dockermaníaco!")
time.sleep(1)

Y el Dockerfile sería algo así:

# Utilizamos la imagen oficial de Python
FROM python:alpine

# Establecemos el directorio de trabajo
WORKDIR /app

# Copiamos el script de Python al directorio de trabajo
COPY script.py .

# Ejecutamos el script de Python
CMD ["python", "script.py"]

En este caso, utilizamos la imagen oficial de Python y copiamos nuestro script al directorio de trabajo. Luego, ejecutamos el script con el comando python script.py. Pero vamos a complicarlo un poco más.

Normalmente, cuando programamos en Python o en cualquier otro lenguaje, necesitamos instalar dependencias. En Python, lo hacemos con el comando pip install y un fichero requirements.txt. Vamos a ver cómo sería el Dockerfile si tuviéramos dependencias en nuestro script.

Lo primero, definimos el fichero requirements.txt con las dependencias de nuestro script (vamos a suponer que necesitamos la librería requests aunque ni siquiera la usamos en el script):

requests

Ahora, nuestro Dockerfile sería algo así:

# Utilizamos la imagen oficial de Python
FROM python:alpine

# Establecemos el directorio de trabajo
WORKDIR /app

# Copiamos el script de Python y el fichero requirements.txt al directorio de trabajo
COPY script.py .
COPY requirements.txt .

# Instalamos las dependencias
RUN pip install -r requirements.txt

Como ves, hemos añadido una instrucción RUN que ejecuta el comando pip install -r requirements.txt para instalar las dependencias de nuestro script.

El comando RUN nos permite ejecutar cualquier comando en la imagen, por lo que podríamos instalar cualquier paquete, compilar código, etc.

Orden de las instrucciones

Es importante tener en cuenta el orden de las instrucciones en un Dockerfile. Se ejecutan de arriba a abajo, secuencialmente, y Docker intenta reutilizar capas de imágenes anteriores para optimizar el proceso de construcción.

Por ejemplo, si cambiamos el fichero script.py y volvemos a construir, tendría que repetir el dockerfile desde la instrucción COPY script.py ., que sería la primera en sufir cambios.

De cara al ejemplo anterior, sería poco óptimo, porque no tiene sentido que instalemos las dependencias cada vez que cambiamos el script. Por lo que sería más óptimo cambiar el orden de las instrucciones para que Docker pueda reutilizar las capas de la imagen.

Veamos como podríamos optimizar el Dockerfile anterior:

# Versión optimizada del Dockerfile
FROM python:alpine

WORKDIR /app

COPY requirements.txt .

RUN pip install -r requirements.txt

COPY script.py .

CMD ["python", "script.py"]

Ahora, si cambiamos el script, Docker reutilizará la capa de la imagen que contiene las dependencias y solo tendrá que volver a ejecutar las instrucciones a partir de la copia del script. Algo más que fundamental cuando estamos continuamente construyendo y probando nuestra imagen y no queremos morir con los tiempos de construcción.

Si te interesa profundizar en el tema de optimización de Dockerfiles, te recomiendo que investigues sobre las las multi-stage builds, habrá un capítulo dedicado a ello más adelante y tengo una lista de reproducción en Youtube sobre el tema:

Docker Build

Es hora de probar nuestros Dockerfiles. Para ello, utilizaremos el comando docker build. Este comando construye una imagen a partir de un Dockerfile y un contexto. El contexto es el directorio desde el que se construye la imagen y se utiliza de referencia para el copiado de archivos y directorios.

El comando docker build tiene la siguiente sintaxis:

docker build -t <nombre_imagen> <directorio_contexto>

El parámetro -t nos permite dar un nombre y tag a la imagen que estamos construyendo. Si no lo especificamos, Docker le asignará un nombre aleatorio.

Vamos a probar a construir las imágenes de los Dockerfiles anteriores. Para ello, crea un directorio y copia los ficheros index.html y Dockerfile del primer ejemplo y otro directorio para los ficheros script.py, requirements.txt y Dockerfile del segundo ejemplo.

Construimos la imagen del servidor web Nginx:

docker build -t mi-nginx .

Y la imagen del script de Python:

docker build -t mi-python .

Si todo ha ido bien, ya deberías tener tus imágenes construidas. Puedes comprobarlo con el comando docker images.

Ahora solo nos queda probar las imágenes. Para ello, ejecutamos un contenedor a partir de cada imagen:

docker run -d --name mi-nginx -p 8080:80 mi-nginx

docker run -d --name mi-python mi-python

Como puedes ver, el nombre de la imagen sirve para referenciarla a la hora de ejecutar el contenedor, descargarla o subirla a un registro de imágenes.

Seguiremos profundizando en más instrucciones de Dockerfile y en el proceso de construcción de imágenes en los próximos episodios.


Volver al índice