11 · Desplegar una app web
Envía la app igual que la pista de MLOps envía modelos: contenerízala. Una app de Flask corre detrás de `gunicorn` (el servidor de desarrollo no es de grado producción); una app de Streamlit corre con `streamlit run` en el puerto 8501. Un `Dockerfile` + `requirements.txt` hace cualquiera de las dos reproducible, y luego las opciones de nube de MLOps la ponen en vivo.
Despliega CONTENERIZANDO, exactamente como la pista de MLOps: un `requirements.txt` fija las dependencias, un `Dockerfile` empaqueta la app. El `CMD` de una app de Flask corre `gunicorn` (un servidor WSGI real — el servidor de desarrollo integrado NO es de grado producción); el `CMD` de una app de Streamlit corre `streamlit run` ligado a `0.0.0.0:8501`. La imagen luego se despliega por las MISMAS opciones de nube que la pista de MLOps cubrió (registro de Docker → cloud run/host).
Sin esto:
Correr el servidor de desarrollo de Flask en producción no aguanta carga real y expone el depurador como superficie de ataque; y sin un contenedor el entorno exacto de Python/dependencias de la app no puede reproducirse en el host de nube.
Tu app funciona localmente — ahora la necesitas en vivo, en un servidor que cualquiera pueda alcanzar. La buena noticia: ya aprendiste el cómo en la pista de MLOps. Desplegar una app web es el mismo manual que desplegar un modelo — contenerízala, luego empújala a la nube — con un giro específico de la app: el comando de arranque.
Paso 1 — fija las dependencias con requirements.txt. Lista los paquetes exactos que la app necesita (flask, gunicorn, scikit-learn, joblib — o streamlit, pandas, ...). Esto es lo que hace el entorno reproducible: el host de nube instala precisamente estos, así que corre el mismo código que corriste localmente.
Paso 2 — escribe un Dockerfile. Igual que la pista de MLOps (Ch 2, Docker): parte de una imagen base de Python, copia el código y requirements.txt, pip install, y fija el CMD que lanza el servidor. El CMD es la única línea que difiere entre una app de Flask y una de Streamlit:
Flask en producción → gunicorn, NO el servidor de desarrollo. Recuerda la advertencia de la lección 2: app.run(debug=True) es solo para desarrollo. El servidor integrado es de un solo hilo, lento bajo carga, y (con debug) un agujero de seguridad. En producción corres Flask detrás de un servidor WSGI real — gunicorn — que maneja muchas peticiones concurrentes a través de procesos worker:
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"] — app:app significa "el objeto app dentro de app.py", y -b 0.0.0.0:8000 liga todas las interfaces (para que el contenedor sea alcanzable) en el puerto 8000.
Streamlit en producción → streamlit run, puerto 8501. Streamlit trae su propio servidor capaz de producción, así que no hay equivalente de gunicorn — corres el mismo comando streamlit run, pero ligado para un contenedor:
CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"] — el puerto 8501 es el puerto convencional de Streamlit, y --server.address=0.0.0.0 lo hace alcanzable desde fuera del contenedor (no solo localhost).
Paso 3 — despliega vía las opciones de nube de MLOps. Una vez construida la imagen, es solo un contenedor — así que se envía exactamente como una imagen de modelo: empújala a un registro y córrela en una plataforma de nube (la pista de MLOps, Ch 7, despliegue en la nube cubre las opciones — servicios de cloud run, hosts de contenedores, etc.). Expón el puerto correcto (8000 para el ejemplo de Flask, 8501 para Streamlit), y la app está en vivo.
El principio clave: una app web es solo otro contenedor. Todo lo que la pista de MLOps enseñó sobre Docker y despliegue en la nube aplica sin cambios; las únicas decisiones específicas de web son el CMD (gunicorn vs streamlit run) y el puerto expuesto.
Las celdas de solo-lectura muestran ambos Dockerfiles lado a lado y los comandos de build/run.
Un Dockerfile de producción de Flask: fija dependencias, copia el código, y corre detrás de `gunicorn` (`gunicorn -b 0.0.0.0:8000 app:app`) — un servidor WSGI real, nunca el `app.run()` de desarrollo.
Un Dockerfile de producción de Streamlit: misma base, pero el `CMD` corre `streamlit run app.py --server.port=8501 --server.address=0.0.0.0` — el propio servidor de Streamlit en el puerto 8501, ligado para acceso externo.
Construye y corre cada imagen (`docker build`/`docker run`, puertos 8000 para Flask y 8501 para Streamlit), luego despliega empujando el contenedor a un registro y corriéndolo en la nube — el mismo flujo que la pista de MLOps (Ch 2 Docker, Ch 7 nube).
¿Qué afirmación empareja correctamente cada framework con su configuración de servidor en producción?
- Despliega una app web al estilo MLOps: un `requirements.txt` fija las dependencias y un `Dockerfile` la empaqueta — luego envíala a la nube como cualquier contenedor (MLOps Ch 2 Docker, Ch 7 nube).
- El Flask de producción corre detrás de `gunicorn` (`gunicorn -b 0.0.0.0:8000 app:app`) — el servidor de desarrollo integrado no es de grado producción y `debug=True` es un riesgo de seguridad.
- Streamlit trae su propio servidor: corre `streamlit run app.py --server.port=8501 --server.address=0.0.0.0` en el puerto 8501 — sin necesidad de gunicorn.
Un front end de modelo desplegado es un contenedor como cualquier otro — el mismo pipeline de Docker + nube que la pista de MLOps construyó para imágenes de modelo lleva una API de Flask o un panel de Streamlit a producción sin cambios.
Si lo quitas: Sin contenerizar y usar un servidor real, la app o no se reproduce en el host o se cae bajo carga — y un servidor de Flask con debug habilitado es un agujero de seguridad activo.