Laboratorio 05 - Contenerización de GroceryListApp con Docker
En los laboratorios anteriores trabajó con imágenes ya construidas. En este laboratorio recorrerá el proceso anterior a docker run. Partirá del código fuente de una aplicación, definirá cómo debe construirse su imagen y ejecutará un contenedor creado a partir de esa imagen.
La aplicación utilizada es GroceryListApp, una aplicación web desarrollada con Flask que almacena su información en SQLite. El ejercicio permite relacionar el código fuente con el Dockerfile, el contexto de construcción, las capas de la imagen y la persistencia de datos.
Lo que aprenderá
Sección titulada «Lo que aprenderá»Al finalizar podrá
- clonar y reconocer la estructura básica de una aplicación web Flask;
- crear un
Dockerfilepara construir una imagen reproducible; - ordenar las instrucciones del
Dockerfilepara aprovechar la caché de capas; - configurar
.dockerignorepara excluir archivos que no deben formar parte del contexto de construcción; - construir una imagen y asignarle una etiqueta de versión;
- ejecutar un contenedor con publicación de puertos y almacenamiento persistente;
- comprobar el funcionamiento de la aplicación mediante registros, una ruta de salud y la interfaz web;
- comprobar que los datos sobreviven al reinicio del contenedor.
Antes de comenzar
Sección titulada «Antes de comenzar»Compruebe que dispone de los siguientes elementos.
| Requisito | Comprobación |
|---|---|
| Docker instalado | docker --version |
| Docker en ejecución | docker info |
| Conexión a internet | Necesaria para descargar imágenes y dependencias |
Puerto 8080 disponible |
lsof -i :8080 no debería mostrar procesos escuchando en ese puerto |
| Navegador web | Necesario para acceder a la aplicación |
Verifique primero que Docker esté disponible.
docker --versionImportante
Si el comando
dockerno es reconocido o devuelve errores de permisos, resuelva primero la instalación o configuración de Docker antes de continuar.
Obtener el código fuente
Sección titulada «Obtener el código fuente»Descargue el repositorio de GroceryListApp.
git clone https://github.com/jpadillaa/grocerylistapp.gitIngrese al directorio del proyecto.
cd grocerylistappConsulte su contenido.
ls -laLa estructura debería ser similar a la siguiente.
.├── app/├── static/├── templates/├── tests/├── requirements.txt├── README.md└── LICENSEAntes de construir una imagen conviene reconocer qué archivos pertenecen a la aplicación y cuáles solo son necesarios durante desarrollo, pruebas o documentación. Esta distinción será importante cuando configure .dockerignore.
Crear el Dockerfile
Sección titulada «Crear el Dockerfile»El Dockerfile describe las instrucciones que Docker utilizará para construir la imagen de la aplicación.
Cree el archivo en la raíz del proyecto.
touch DockerfileAbra el archivo con su editor y agregue el siguiente contenido.
FROM python:3.11-slim
# Configurar directorio de trabajoWORKDIR /app
# Instalar dependenciasCOPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
# Copiar el código de la aplicaciónCOPY . .
# Variables de entorno por defectoENV PORT=8080ENV DATA_DIR=/dataENV DB_PATH=/data/shop.dbENV FLASK_CONFIG=app.config.Config
# Asegurar que el directorio de datos existeRUN mkdir -p /data
# Exponer puertoEXPOSE 8080
# El comando de arranque usa gunicorn# La inicialización de la DB ocurre dentro del create_app() en app/__init__.pyCMD ["sh", "-c", "gunicorn --bind 0.0.0.0:${PORT} \"app.main:app\""]Comprender las instrucciones
Sección titulada «Comprender las instrucciones»Cada instrucción modifica algún aspecto de la imagen que Docker está construyendo.
| Instrucción | Propósito |
|---|---|
FROM python:3.11-slim |
Utiliza Python 3.11 sobre una variante reducida de la imagen base |
WORKDIR /app |
Establece /app como directorio de trabajo para las instrucciones siguientes |
COPY requirements.txt . |
Copia primero el archivo que declara las dependencias |
RUN pip install --no-cache-dir -r requirements.txt |
Instala las dependencias de Python sin conservar la caché de pip |
COPY . . |
Copia el resto del código de la aplicación |
ENV ... |
Define valores de configuración disponibles dentro del contenedor |
RUN mkdir -p /data |
Crea el directorio utilizado para almacenar la base de datos |
EXPOSE 8080 |
Documenta el puerto utilizado por la aplicación |
CMD [...] |
Define el comando que se ejecutará cuando inicie el contenedor |
EXPOSE 8080 no publica el puerto en el sistema anfitrión. La publicación se realizará más adelante mediante docker run -p.
Por qué importa el orden
Sección titulada «Por qué importa el orden»El orden de las instrucciones influye en la reutilización de la caché de construcción.
El archivo requirements.txt se copia antes que el resto del código y las dependencias se instalan inmediatamente después. Si el código cambia pero requirements.txt permanece igual, Docker puede reutilizar la capa donde instaló las dependencias.
La secuencia relevante es
requirements.txt ↓instalación de dependencias ↓código de la aplicaciónSi COPY . . apareciera antes de instalar las dependencias, un cambio cualquiera en el código podría invalidar esa parte de la caché y obligar a repetir la instalación.
Definir el contexto con .dockerignore
Sección titulada «Definir el contexto con .dockerignore»Cuando ejecuta docker build, Docker utiliza un conjunto de archivos como contexto de construcción. .dockerignore permite excluir contenido que no debe enviarse o incorporarse durante ese proceso.
Cree el archivo.
touch .dockerignoreAgregue el siguiente contenido.
# Control de versiones.git.gitignore
# Entornos virtuales de Pythonvenv/.venv/__pycache__/*.py[cod]*.pyo
# Archivos de IDE/Editor.vscode/.idea/*.swp*.swo.DS_Store
# Tests y documentación (no necesarios en producción)tests/*.md!README.md
# Archivos de Docker (evita recursión)Dockerfiledocker-compose*.yml.dockerignore
# Datos locales (la DB debe persistir en volúmenes, no en la imagen)data/*.db
# Archivos de configuración local.env.env.*Este archivo cumple varias funciones.
- Reduce la cantidad de archivos incluidos en el contexto de construcción.
- Evita incorporar artefactos locales que no necesita la aplicación.
- Excluye archivos de datos que no deberían quedar almacenados dentro de la imagen.
- Reduce el riesgo de incluir archivos locales de configuración como
.env. - Evita que cambios en archivos irrelevantes invaliden innecesariamente la caché de construcción.
La relación entre ambos archivos puede entenderse así.
Directorio del proyecto │ ├── .dockerignore decide qué entra al contexto │ ▼Contexto de construcción │ ├── Dockerfile define qué hacer con ese contenido │ ▼Imagen DockerConstruir la imagen
Sección titulada «Construir la imagen»Con el Dockerfile y .dockerignore preparados, construya la imagen.
docker build -t grocerylistapp:1.0.0 .Los dos elementos finales del comando tienen funciones diferentes.
| Elemento | Función |
|---|---|
-t grocerylistapp:1.0.0 |
Asigna el nombre grocerylistapp y la etiqueta 1.0.0 |
. |
Utiliza el directorio actual como contexto de construcción |
La etiqueta permite identificar una versión concreta de la imagen. En este ejercicio se utiliza 1.0.0 en lugar de depender únicamente de latest.
Comprobar la imagen
Sección titulada «Comprobar la imagen»docker images | grep grocerylistappUna salida aproximada puede verse así.
grocerylistapp 1.0.0 abc123def456 2 minutes ago 180MBHasta este punto Docker ha construido una imagen. Todavía no existe un contenedor ejecutando la aplicación.
Ejecutar la imagen
Sección titulada «Ejecutar la imagen»Cree un contenedor a partir de la imagen recién construida.
docker run -d \ --name grocerylist \ -p 8080:8080 \ -v grocerylist-data:/data \ grocerylistapp:1.0.0El comando combina la ejecución de la aplicación con dos decisiones adicionales.
| Elemento | Función |
|---|---|
-d |
Ejecuta el contenedor en segundo plano |
--name grocerylist |
Asigna un nombre estable al contenedor |
-p 8080:8080 |
Publica el puerto 8080 del contenedor mediante el puerto 8080 del sistema anfitrión |
-v grocerylist-data:/data |
Monta el volumen nombrado grocerylist-data en /data |
grocerylistapp:1.0.0 |
Indica la imagen utilizada para crear el contenedor |
La aplicación utiliza /data/shop.db para almacenar la base de datos SQLite. Al montar grocerylist-data sobre /data, ese archivo queda asociado con el volumen y no únicamente con la capa escribible del contenedor.
Importante
El volumen permite conservar la base de datos más allá del ciclo de vida de un contenedor específico. Detener y volver a iniciar el mismo contenedor no elimina su capa escribible, pero eliminarlo sí hace necesario un mecanismo externo si los datos deben sobrevivir a la creación de una nueva instancia.
Comprobar la ejecución
Sección titulada «Comprobar la ejecución»docker psUna salida aproximada puede verse así.
CONTAINER ID IMAGE STATUS PORTSa1b2c3d4e5f6 grocerylistapp:1.0.0 Up 30 seconds 0.0.0.0:8080->8080/tcpComprobar la aplicación
Sección titulada «Comprobar la aplicación»No basta con confirmar que el contenedor aparece como Up. Conviene comprobar también que el proceso inició correctamente, que responde mediante HTTP y que la aplicación puede utilizar su almacenamiento.
Revisar los registros
Sección titulada «Revisar los registros»docker logs grocerylistBusque mensajes similares a los siguientes.
[INFO] Starting gunicorn 21.x.x[INFO] Listening at: http://0.0.0.0:8080Estos mensajes indican que Gunicorn inició y está escuchando en el puerto esperado.
Consultar la ruta de salud
Sección titulada «Consultar la ruta de salud»curl http://localhost:8080/healthLa respuesta esperada es
{"status":"ok"}La ruta de salud proporciona una comprobación directa de que la aplicación está respondiendo.
Acceder a la interfaz
Sección titulada «Acceder a la interfaz»Abra en el navegador
http://localhost:8080Debería aparecer la interfaz de GroceryListApp.
Comprobar la persistencia
Sección titulada «Comprobar la persistencia»Cree varios elementos desde la aplicación.
Detenga el contenedor.
docker stop grocerylistInícielo nuevamente.
docker start grocerylistAbra otra vez la aplicación y compruebe que los elementos creados continúan disponibles.
Esta prueba confirma que la aplicación recupera la información almacenada en /data/shop.db después del reinicio.
Comprender el recorrido completo
Sección titulada «Comprender el recorrido completo»El ejercicio realizado hasta este punto conecta varias etapas que conviene distinguir.
Código fuente ↓.dockerignore ↓Contexto de construcción ↓Dockerfile ↓docker build ↓Imagen grocerylistapp:1.0.0 ↓docker run ↓Contenedor grocerylist ├── puerto 8080 └── /data ↓ volumen grocerylist-dataCada elemento cumple una responsabilidad diferente.
- El código fuente contiene la aplicación.
.dockerignorecontrola qué archivos forman parte del contexto de construcción.- El
Dockerfiledescribe cómo transformar ese contexto en una imagen. docker buildmaterializa la imagen.docker runcrea un contenedor a partir de esa imagen.- La publicación de puertos permite acceder al servicio desde el sistema anfitrión.
- El volumen desacopla los datos persistentes del contenedor específico que ejecuta la aplicación.
Solución de problemas
Sección titulada «Solución de problemas»El contenedor no inicia
Sección titulada «El contenedor no inicia»Consulte primero sus registros.
docker logs grocerylist --tail 50El laboratorio original identifica los siguientes casos frecuentes.
| Síntoma | Posible causa | Acción propuesta |
|---|---|---|
ModuleNotFoundError |
Dependencias no instaladas correctamente | Reconstruir la imagen sin reutilizar caché |
Permission denied en /data |
Problema de acceso al directorio de datos | Revisar la configuración del volumen |
Address already in use |
Puerto 8080 ocupado |
Publicar la aplicación mediante otro puerto del host |
Para reconstruir la imagen sin reutilizar la caché puede ejecutar
docker build --no-cache -t grocerylistapp:1.0.0 .Si el puerto 8080 está ocupado, puede utilizar otro puerto del sistema anfitrión.
docker run -d -p 8081:8080 --name grocerylist grocerylistapp:1.0.0Los datos no persisten
Sección titulada «Los datos no persisten»Compruebe que el volumen existe.
docker volume ls | grep grocerylist-dataSi no existe, puede crearlo explícitamente.
docker volume create grocerylist-dataDespués deberá ejecutar el contenedor con el montaje correspondiente.
-v grocerylist-data:/dataLa existencia del volumen por sí sola no hace que la aplicación lo utilice. El volumen debe estar montado en la ruta donde GroceryListApp almacena la base de datos.
Lo que debe poder reconocer ahora
Sección titulada «Lo que debe poder reconocer ahora»Después de completar el laboratorio debería poder explicar la contenerización como una cadena de decisiones y no únicamente como la creación de un Dockerfile.
La imagen depende del contenido enviado durante la construcción y de las instrucciones declaradas en el Dockerfile. El orden de esas instrucciones afecta la reutilización de capas. .dockerignore determina qué archivos quedan fuera del contexto y el volumen permite separar los datos persistentes de la instancia concreta que ejecuta la aplicación.
También debería poder relacionar las principales operaciones utilizadas durante el ejercicio.
| Acción | Comando |
|---|---|
| Construir la imagen | docker build -t grocerylistapp:1.0.0 . |
| Ejecutar el contenedor | docker run -d --name grocerylist -p 8080:8080 -v grocerylist-data:/data grocerylistapp:1.0.0 |
| Detener el contenedor | docker stop grocerylist |
| Iniciar el contenedor | docker start grocerylist |
| Reiniciar el contenedor | docker restart grocerylist |
| Seguir los registros | docker logs -f grocerylist |
| Acceder al shell | docker exec -it grocerylist /bin/bash |
| Eliminar el contenedor | docker rm -f grocerylist |
| Eliminar la imagen | docker rmi grocerylistapp:1.0.0 |
| Eliminar el volumen | docker volume rm grocerylist-data |
Advertencia
Eliminar
grocerylist-dataelimina también los datos persistidos en ese volumen. Hágalo únicamente cuando ya no necesite conservar la información.
Fuente original
Laboratorio 05 - Contenerización de GroceryListApp con Docker