Diferencia entre revisiones de «Tutorial-docker»
(Página creada con «= Ejercicio 1: Empezamos desde cero — creando la estructura base de Docker = El objetivo es eliminar toda la configuración previa y crear desde cero la estructura de ca...») |
|||
Línea 1: | Línea 1: | ||
− | = Ejercicio 1: Empezamos desde cero | + | = 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. | 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. | 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. | ||
− | <syntaxhighlight lang="bash"> mv docker docker.bk </syntaxhighlight> | + | <syntaxhighlight lang="bash"> |
+ | mv docker docker.bk | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | También debes asegurarte que trabajas con las variables de entorno adecuadas: | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | cp .env.docker.example .env | ||
+ | </syntaxhighlight> | ||
⚠️ Este paso es importante: partimos completamente desde cero para entender cada parte del sistema. | ⚠️ 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: | Ahora crea nuevamente la carpeta base donde almacenaremos los archivos relacionados con Docker: | ||
Línea 89: | Línea 97: | ||
En este caso, ejecutará la aplicación Flask. | 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: | ||
+ | |||
+ | <syntaxhighlight lang="dockerfile"> RUN pip install -r requirements.txt </syntaxhighlight> | ||
+ | |||
+ | 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: | ||
+ | |||
+ | <syntaxhighlight lang="dockerfile"> CMD ["flask", "run", "--host=0.0.0.0", "--port=5000"] </syntaxhighlight> | ||
+ | |||
+ | 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 == | ||
<syntaxhighlight lang="dockerfile"> | <syntaxhighlight lang="dockerfile"> | ||
FROM python:3.12-slim | FROM python:3.12-slim | ||
Línea 99: | Línea 144: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | == | + | == Construir la imagen == |
Desde la raíz del proyecto (no dentro de docker/images): | Desde la raíz del proyecto (no dentro de docker/images): | ||
<syntaxhighlight lang="bash"> docker build -t uvlhub:dev -f docker/images/Dockerfile.dev . </syntaxhighlight> | <syntaxhighlight lang="bash"> docker build -t uvlhub:dev -f docker/images/Dockerfile.dev . </syntaxhighlight> | ||
− | == | + | == Ejecutar el contenedor == |
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
docker run -p 5000:5000 uvlhub:dev | docker run -p 5000:5000 uvlhub:dev | ||
Línea 110: | Línea 155: | ||
Abre el navegador en [http://localhost:5000 http://localhost:5000]. ¿Qué es lo que observas? | Abre el navegador en [http://localhost:5000 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: | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker pull mariadb:12.0.2 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | == Crear el contenedor de MariaDB == | ||
+ | Lanza un contenedor con todas las variables necesarias para que Flask pueda conectarse correctamente: | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | 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 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 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 == | ||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker ps | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Deberías ver algo así: | ||
+ | |||
+ | <syntaxhighlight lang="text"> | ||
+ | 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 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | == Conectarse a la base de datos == | ||
+ | Abre una consola dentro del contenedor: | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker exec -it mariadb_container bash | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Y dentro, accede al cliente de MariaDB: | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | mariadb -u root -p | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Contraseña: uvlhubdb_root_password | ||
+ | |||
+ | == Verificar la base de datos == | ||
+ | Una vez dentro del cliente MySQL, ejecuta: | ||
+ | |||
+ | <syntaxhighlight lang="sql"> | ||
+ | SHOW DATABASES; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 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... | ||
+ | |||
+ | <syntaxhighlight lang="sql"> | ||
+ | USE uvlhubdb; | ||
+ | SHOW TABLES; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 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... | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker run -p 5000:5000 -d uvlhub:dev | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 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? | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker ps | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Fíjate que Docker asigna un nombre aleatorio a tu contenedor web porque no definiste ninguno. Una vez identificado el nombre: | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker exec -it <nombre> bash | ||
+ | flask db upgrade | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 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 | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker network create uvlhub_network | ||
+ | docker network ls | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Vemos que ya aparece nuestra red '''uvlhub_network'''. Ahora, conectemos el contenedor de MariaDB a la red: | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker stop mariadb_container | ||
+ | docker rm mariadb_container | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | 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 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 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: | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker stop <nombre> | ||
+ | docker rm <nombre> | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Y lo levantamos de nuevo, pero ya en la nueva red. De paso, le ponemos un nombre para identificarlo mejor. | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker run -p 5000:5000 --name web_app_container --network uvlhub_network -d uvlhub:dev | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Y ahora ya podemos acceder al contenedor por su nombre y ejecutar las migraciones: | ||
+ | |||
+ | <syntaxhighlight lang="bash"> | ||
+ | docker exec -it web_app_container bash | ||
+ | flask db upgrade | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Abre el navegador en [http://localhost:5000 http://localhost:5000]. ¿Mejor? |
Revisión del 21:11 11 oct 2025
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?