Diferencia entre revisiones de «Tutorial-docker»
Línea 329: | Línea 329: | ||
Abre el navegador en [http://localhost:5000 http://localhost:5000]. ¿Mejor? | Abre el navegador en [http://localhost:5000 http://localhost:5000]. ¿Mejor? | ||
+ | |||
+ | = Ejercicio 3: Persistencia = | ||
+ | |||
+ | Vamos a simular que hemos desplegado justo esto en producción y el servidor se reinicia por un corte de luz. Reiniciemos a mano cada contenedor: | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker restart web_app_container | ||
+ | docker restart mariadb_container | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Abre de nuevo [http://localhost:5000 http://localhost:5000]. What? ¿Cómo? ¿Qué ha pasado? ¿Otra vez problema de conexión? Ve dándole a F5... F5... ¡bingo! Ahora sí sale, pero menuda aleatoriedad, ¿no? Claro, es que ahora caes en que una cosa es que el contenedor haya arrancado y otra que esté listo. | ||
+ | |||
+ | Aparte, tenemos otro problema. ¿Qué pasa con la persistencia? Porque si borro el contenedor de MariaDB, todos los datos que estén en su interior también se pierden. Y si estamos en producción, lío asegurado. | ||
+ | |||
+ | == Volúmenes == | ||
+ | |||
+ | Cada vez que eliminas el contenedor de MariaDB, todas las bases de datos se pierden. Esto ocurre porque los datos se guardan dentro del sistema de archivos del contenedor, y al borrarlo desaparecen con él. | ||
+ | |||
+ | En este ejercicio creas un volumen de Docker para que los datos persistan entre ejecuciones. | ||
+ | |||
+ | === Crear un volumen === | ||
+ | Un volumen es una carpeta gestionada por Docker que vive fuera del ciclo de vida de los contenedores. | ||
+ | |||
+ | Ejecuta: | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker volume create mariadb_data | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Comprueba que existe: | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker volume ls | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | La salida muestra algo como: | ||
+ | |||
+ | DRIVER VOLUME NAME | ||
+ | local mariadb_data | ||
+ | |||
+ | === Crear un contenedor de MariaDB con el volumen === | ||
+ | Lanza el contenedor y conecta el volumen a la ruta donde MariaDB guarda los datos (/var/lib/mysql): | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker stop mariadb_container | ||
+ | docker rm mariadb_container | ||
+ | docker run -d \ | ||
+ | --name mariadb_container \ | ||
+ | --hostname db \ | ||
+ | --network uvlhub_network \ | ||
+ | -v mariadb_data:/var/lib/mysql \ | ||
+ | -e FLASK_APP_NAME="UVLHUB.IO(dev)" \ | ||
+ | -e FLASK_ENV=development \ | ||
+ | -e DOMAIN=localhost \ | ||
+ | -e MARIADB_HOSTNAME=db \ | ||
+ | -e MARIADB_PORT=3306 \ | ||
+ | -e MARIADB_DATABASE=uvlhubdb \ | ||
+ | -e MARIADB_TEST_DATABASE=uvlhubdb_test \ | ||
+ | -e MARIADB_USER=uvlhubdb_user \ | ||
+ | -e MARIADB_PASSWORD=uvlhubdb_password \ | ||
+ | -e MARIADB_ROOT_PASSWORD=uvlhubdb_root_password \ | ||
+ | -e WORKING_DIR=/app/ \ | ||
+ | -p 3306:3306 \ | ||
+ | mariadb:12.0.2 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | === Qué significa ":" en los volúmenes === | ||
+ | |||
+ | Cuando usas la opción -v o --volume en Docker, el signo dos puntos (:) separa dos rutas: | ||
+ | |||
+ | '''-v origen:destino''' | ||
+ | |||
+ | origen → es la ruta o el volumen en tu máquina (el host). | ||
+ | |||
+ | destino → es la ruta dentro del contenedor. |
Revisión del 21:26 11 oct 2025
Contenido
Ejercicio 1: Empezamos desde cero
El objetivo es eliminar toda la configuración previa y crear desde cero la estructura de carpetas que contendrá nuestros futuros archivos Docker.
Eliminar la configuración existente
Antes de comenzar, asegúrate de borrar cualquier carpeta Docker existente en el proyecto. En este caso, le haremos una copia de seguridad para poder volver a ella.
mv docker docker.bk
También debes asegurarte que trabajas con las variables de entorno adecuadas:
cp .env.docker.example .env
⚠️ Este paso es importante: partimos completamente desde cero para entender cada parte del sistema.
Crear un Dockerfile mínimo
Ahora crea nuevamente la carpeta base donde almacenaremos los archivos relacionados con Docker:
mkdir -p docker/images
Vamos a explicar los distintos apartados:
FROM
FROM python:3.12-slim
Indica la imagen base sobre la que se construirá la tuya. En este caso, parte de una imagen ligera que ya tiene Python 3.12 instalado.
WORKDIR
WORKDIR /app
Esta línea define el directorio de trabajo dentro del contenedor. A partir de aquí, todos los comandos que aparezcan después (COPY, RUN, CMD, etc.) se ejecutarán dentro de /app.
Ojo: /app no es la carpeta app/ de tu proyecto local. Son cosas distintas.
En tu máquina puede existir algo como:
uvlhub
app
core
requirements.txt
Pero dentro del contenedor la estructura será distinta:
/ app/ app/ ← aquí dentro se habrá copiado tu carpeta local "app"
Cuando más adelante uses:
COPY . .
Docker copiará todos los archivos de tu proyecto dentro de la carpeta /app del contenedor. Por eso el código quedará en /app/app/.
COPY
COPY . .
Copia todos los archivos del proyecto (del host) dentro del contenedor, en /app. El primer punto es el origen; el segundo, el destino.
RUN
RUN pip install --no-cache-dir -r requirements.txt
Ejecuta comandos durante la construcción de la imagen. Aquí instalamos las dependencias necesarias desde requirements.txt. El resultado de este paso quedará guardado en la imagen.
EXPOSE
EXPOSE 5000
Documenta el puerto que la aplicación usa dentro del contenedor. Esto no lo abre al exterior; simplemente indica que la app escucha en el 5000.
CMD
CMD ["flask", "run", "--host=0.0.0.0", "--port=5000", "--reload", "--debug"]
Define el comando que se ejecutará cuando el contenedor se inicie. En este caso, ejecutará la aplicación Flask.
RUN vs CMD
Estas dos instrucciones parecen similares, pero hacen cosas muy distintas.
RUN
Se ejecuta durante la construcción de la imagen (en el momento del docker build). Cada RUN crea una nueva capa dentro de la imagen.
Ejemplo:
RUN pip install -r requirements.txt
Este comando instala las dependencias una sola vez, cuando se construye la imagen. El resultado (las librerías instaladas) queda grabado dentro de la imagen final.
Si después lanzas un contenedor nuevo a partir de esa imagen, las dependencias ya estarán instaladas y no se volverán a ejecutar.
CMD
Se ejecuta cuando se inicia el contenedor (en el docker run). Define el proceso principal que se ejecutará dentro del contenedor mientras esté activo.
Ejemplo:
CMD ["flask", "run", "--host=0.0.0.0", "--port=5000"]
Este comando no se ejecuta durante la construcción, sino cada vez que arrancas el contenedor. Si detienes el contenedor, este proceso también se detiene.
Diferencia resumida
RUN → se ejecuta al construir la imagen (fase de docker build).
CMD → se ejecuta al iniciar el contenedor (fase de docker run).
Solo puede haber un CMD por Dockerfile. Si hay más de uno, solo se usará el último.
Dockerfile completo
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 5000
CMD ["flask", "run", "--host=0.0.0.0", "--port=5000", "--reload", "--debug"]
Construir la imagen
Desde la raíz del proyecto (no dentro de docker/images):
docker build -t uvlhub:dev -f docker/images/Dockerfile.dev .
Ejecutar el contenedor
docker run -p 5000:5000 uvlhub:dev
Abre el navegador en http://localhost:5000. ¿Qué es lo que observas?
Ejercicio 2: Conectando servicios
¡Vaya! ¡No funciona! Can't connect to MySQL server on 'db'... ¡Ah, claro, intenta conectarse a la base de datos, que no lo tenemos! Pero claro, en un contenedor no es recomendable tener la app y la base de datos conviviendo...
Crear contenedor MariaDB
Antes que nada, haz Ctrl + C para parar el contenedor web. En este ejercicio aprenderás a lanzar un contenedor de MariaDB 12.0.2 de forma manual, configurando todas las variables necesarias para tu aplicación Flask.
El objetivo es entender la complejidad de hacerlo “a mano”, antes de automatizarlo con docker-compose.
Descargar la imagen oficial
Primero descarga la versión exacta que usaremos:
docker pull mariadb:12.0.2
Crear el contenedor de MariaDB
Lanza un contenedor con todas las variables necesarias para que Flask pueda conectarse correctamente:
docker run -d --name mariadb_container \
-e FLASK_APP_NAME="UVLHUB.IO(dev)" \
-e FLASK_ENV=development \
-e DOMAIN=localhost \
-e MARIADB_HOSTNAME=db \
-e MARIADB_PORT=3306 \
-e MARIADB_DATABASE=uvlhubdb \
-e MARIADB_TEST_DATABASE=uvlhubdb_test \
-e MARIADB_USER=uvlhubdb_user \
-e MARIADB_PASSWORD=uvlhubdb_password \
-e MARIADB_ROOT_PASSWORD=uvlhubdb_root_password \
-e WORKING_DIR=/app/ \
-p 3306:3306 mariadb:12.0.2
Explicación:
-d: ejecuta el contenedor en segundo plano (modo detached).
--name: asigna un nombre identificable al contenedor.
-e: define variables de entorno dentro del contenedor.
-p 3306:3306: publica el puerto de MariaDB en tu máquina local.
mariadb:12.0.2: imagen exacta que vamos a usar.
Aquí ya, de entrada, vemos que esto es más feo que una nevera por detrás. Para empezar, estamos pasando a lo loco un montón de variables que difílmente será replicable en producción, por contener información sensible. Aparte, si metemos nuevas variables, ese comando queda inservible. Necesitaremos una solución más ágil para este problema
Comprobar que está corriendo
docker ps
Deberías ver algo así:
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
abcd1234efgh mariadb:12.0.2 "docker-entrypoint.s…" Up 5 seconds 0.0.0.0:3306->3306/tcp mariadb_container
Conectarse a la base de datos
Abre una consola dentro del contenedor:
docker exec -it mariadb_container bash
Y dentro, accede al cliente de MariaDB:
mariadb -u root -p
Contraseña: uvlhubdb_root_password
Verificar la base de datos
Una vez dentro del cliente MySQL, ejecuta:
SHOW DATABASES;
Deberías ver que existen la bases de datos uvlhubdb porque pedimos crearla a la hora de levantar el contenedor. ¿Qué tiene la base de datos dentro? Vamos a verlo...
USE uvlhubdb;
SHOW TABLES;
Un total de... 0 tablas... ¡Claro! Es que solo hemos creado la base en sí, pero no las tablas. ¿Con un script SQL tal vez? ¡Espera, espera, si tenemos las migraciones! Pero claro, para eso necesito el contenedor de la app levantado. Sal de la consola SQL y del contenedor de MariaDB. Bien, ahora en el host...
docker run -p 5000:5000 -d uvlhub:dev
Esta vez con el flag "-d" para que corra en segundo plano. Ahora, accedamos al contenedor y ejecutemos las migraciones. Pero... ¿cómo se llama mi contenedor?
docker ps
Fíjate que Docker asigna un nombre aleatorio a tu contenedor web porque no definiste ninguno. Una vez identificado el nombre:
docker exec -it <nombre> bash
flask db upgrade
What? ¿Qué pasa? ¿Otra vez error de conexión con la base? Si tenemos el contenedor funcionando, esto es un jaleo...
Redes internas
Ahora mismo tienes dos contenedores aislados, el de la app y el la base de datos. Por defecto, cada contenedor tiene su propia red interna y no puede ver a los demás por nombre. El hostname db que usa Flask no existe en su DNS interno.
Conectar servicios
Vamos a crear una red interna que compartan ambos contenedores
docker network create uvlhub_network
docker network ls
Vemos que ya aparece nuestra red uvlhub_network. Ahora, conectemos el contenedor de MariaDB a la red:
docker stop mariadb_container
docker rm mariadb_container
docker run -d \
--name mariadb_container \
--hostname db \
--network uvlhub_network \
-e FLASK_APP_NAME="UVLHUB.IO(dev)" \
-e FLASK_ENV=development \
-e DOMAIN=localhost \
-e MARIADB_HOSTNAME=db \
-e MARIADB_PORT=3306 \
-e MARIADB_DATABASE=uvlhubdb \
-e MARIADB_TEST_DATABASE=uvlhubdb_test \
-e MARIADB_USER=uvlhubdb_user \
-e MARIADB_PASSWORD=uvlhubdb_password \
-e MARIADB_ROOT_PASSWORD=uvlhubdb_root_password \
-e WORKING_DIR=/app/ \
-p 3306:3306 \
mariadb:12.0.2
Claro, si te fijas, ahora hay que añadir, además de toda esa configuración engorrosa, que se añada en la misma red y que además tenga de hostname "db" para que el contenedor web entienda qué es "db". Tenemos que hacer lo mismo para el contenedor web:
docker stop <nombre>
docker rm <nombre>
Y lo levantamos de nuevo, pero ya en la nueva red. De paso, le ponemos un nombre para identificarlo mejor.
docker run -p 5000:5000 --name web_app_container --network uvlhub_network -d uvlhub:dev
Y ahora ya podemos acceder al contenedor por su nombre y ejecutar las migraciones:
docker exec -it web_app_container bash
flask db upgrade
Abre el navegador en http://localhost:5000. ¿Mejor?
Ejercicio 3: Persistencia
Vamos a simular que hemos desplegado justo esto en producción y el servidor se reinicia por un corte de luz. Reiniciemos a mano cada contenedor:
docker restart web_app_container
docker restart mariadb_container
Abre de nuevo http://localhost:5000. What? ¿Cómo? ¿Qué ha pasado? ¿Otra vez problema de conexión? Ve dándole a F5... F5... ¡bingo! Ahora sí sale, pero menuda aleatoriedad, ¿no? Claro, es que ahora caes en que una cosa es que el contenedor haya arrancado y otra que esté listo.
Aparte, tenemos otro problema. ¿Qué pasa con la persistencia? Porque si borro el contenedor de MariaDB, todos los datos que estén en su interior también se pierden. Y si estamos en producción, lío asegurado.
Volúmenes
Cada vez que eliminas el contenedor de MariaDB, todas las bases de datos se pierden. Esto ocurre porque los datos se guardan dentro del sistema de archivos del contenedor, y al borrarlo desaparecen con él.
En este ejercicio creas un volumen de Docker para que los datos persistan entre ejecuciones.
Crear un volumen
Un volumen es una carpeta gestionada por Docker que vive fuera del ciclo de vida de los contenedores.
Ejecuta:
docker volume create mariadb_data
Comprueba que existe:
docker volume ls
La salida muestra algo como:
DRIVER VOLUME NAME local mariadb_data
Crear un contenedor de MariaDB con el volumen
Lanza el contenedor y conecta el volumen a la ruta donde MariaDB guarda los datos (/var/lib/mysql):
docker stop mariadb_container
docker rm mariadb_container
docker run -d \
--name mariadb_container \
--hostname db \
--network uvlhub_network \
-v mariadb_data:/var/lib/mysql \
-e FLASK_APP_NAME="UVLHUB.IO(dev)" \
-e FLASK_ENV=development \
-e DOMAIN=localhost \
-e MARIADB_HOSTNAME=db \
-e MARIADB_PORT=3306 \
-e MARIADB_DATABASE=uvlhubdb \
-e MARIADB_TEST_DATABASE=uvlhubdb_test \
-e MARIADB_USER=uvlhubdb_user \
-e MARIADB_PASSWORD=uvlhubdb_password \
-e MARIADB_ROOT_PASSWORD=uvlhubdb_root_password \
-e WORKING_DIR=/app/ \
-p 3306:3306 \
mariadb:12.0.2
Qué significa ":" en los volúmenes
Cuando usas la opción -v o --volume en Docker, el signo dos puntos (:) separa dos rutas:
-v origen:destino
origen → es la ruta o el volumen en tu máquina (el host).
destino → es la ruta dentro del contenedor.