Despliegue de Software con Docker
En esta sección vamos desplegar nuestra API usando Docker.
¿Qué es un Contenedor de Docker?
Es una herramienta que nos permite aislar el entorno de cualquier software que queramos implementar.
Si desarrollo una pieza de software en mi entorno, con ciertas dependencias y versiones de las bibliotecas que uso en un sistema operativo específico, entonces, lo que hice en mi PC, podría no funcionar en otro PC.
El contenedor de Docker es como un mini computador, está asociado al concepto de máquinas virtuales. Sin embargo, las máquinas virtuales asignan una gran cantidad de recursos, incluso si la aplicación no se está utilizando.
Cuando el contenedor de Docker no usa recursos, no bloquea el host para usarlos, por lo que es más eficiente que las máquinas virtuales.
El contenedor de Docker tiene un sistema operativo específico, normalmente Linux. La aplicación se ejecuta allí, de tal manera que no importa quién la ejecute o en qué computadora, el sistema operativo y las bibliotecas operativo son los mismos.
¿Qué es una Imagen de Docker?
Es como un molde de donde se crean los contenedores.
Un contenedor es una instanciación específica de la imagen de Docker, que puede ejecutarse en cualquier host que tenga la aplicación de escritorio de Docker (Docker desktop).
Para seleccionar una imagen de Docker, debemos partir de una imagen que ya tenga algunas de nuestras dependencias. Por ejemplo, en este caso nos interesará una imagen que ya tenga python.
¿Dónde encontramos las imágenes de Docker?
Dockerhub es el repositorio oficial de imágenes de Docker
Docker Hub Container Image Library | App Containerization

Ahora busquemos una imagen que ya contenga FastAPI usando las palabras clave “tiangolo fastapi”:

La primera imagen que podemos ver, tiangolo/uvicorn-gunicorn-fastapi
es lo más cercano a una “imagen oficial de Docker de fastapi” que encontraremos, ya que fue desarrollada por tiangolo, el creador de fastapi.
¿Qué es un Dockerfile?
Una imagen de Docker es un molde para crear un contenedor de Docker, y ¿cómo creamos ese molde? Con un Dockerfile.
Un Dockerfile es la receta para crear el molde.
Un Dockerfile es un archivo con un conjunto de pasos que le dicen a Docker cómo crear esa imagen.
Vamos a crear un archivo llamado Dockerfile, en la misma carpeta donde tenemos nuestra aplicación y luego vamos a escribir algunos comandos en él.
El directorio debería verse así ahora mismo:
despliegue
├── preprocesser.pkl
├── lr_model.pkl
├── transformers.py
├── main.py
├── Dockerfile
└── requirements.txt
Comandos del Dockerfile
FROM → aquí especificamos en qué imagen nos basamos para crear nuestra imagen propia.
Ya seleccionamos la imagen base tiangolo/uvicorn-gunicorn-fastapi
Pero también tenemos que seleccionar una versión o una etiqueta en la sección Tags
:

Y vamos a seleccionar la etiqueta python3.9-slim
, que es una versión liviana de python.
Entonces, el comando FROM se verá así:
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9-slim
WORKDIR → Para especificar nuestro directorio de trabajo. Ya está especificado en la imagen de Docker pero es bueno saber en que directorio de trabajo estamos.
WORKDIR /app
EXPOSE → El puerto que vamos a exponer. Ya está especificado en la imagen de Docker pero es bueno saber qué puerto está expuesto.
EXPOSE 80
Usamos el comando COPY para traer el archivo requirements.txt ubicado en la carpeta donde se encuentra el Dockerfile.
COPY requirements.txt .
Run → Queremos incluir algunas dependencias
-r → ejecutar pip -r permite recibir una ruta con una lista de dependencias que se requieran instalar. Es muy útil instalarlos todos a la vez.
No se recomienda tener varios comandos RUN en el dockerfile, es mejor tener solo un comando RUN.
RUN se utiliza para instalar dependencias normalmente. Para ejecutar cualquier cosa que necesitemos poner en nuestra imagen de Docker. Lo ejecutamos y termina.
RUN ["pip", "install", "-r","./requirements.txt"]
COPY → Necesitamos copiar cosas de nuestro host (de nuestra máquina) a la imagen de Docker. Se van a copiar en el contenedor de Docker cuando iniciemos la imagen para crear el contenedor. Esto se puede hacer con paths relativos.
Lo que sea que esté dentro de mi carpeta de despliegue
es lo que quiero que esté en mi directorio de trabajo en el contenedor de Docker o la imagen de Docker.
COPY .
→ El 1er punto representa la carpeta donde está el Dockerfile. Entonces, en este caso, se refiere a la carpeta despliegue
.
COPY . .
→ El 2ndo punto se refiere al directorio de trabajo dentro de la imagen de Docker.
Voy a obtener todo de la carpeta de despliegue
, copiarlo y pegarlo todo en el directorio de trabajo de la imagen de Docker (que es la carpeta app
).
Es una buena práctica dejar el dockerfile en la misma carpeta donde está el código de la aplicación.
COPY . .
Para los servicios que necesitamos que sigan funcionando usamos el comando CMD
CMD → Para iniciar el servicio. Por ejemplo, una aplicación que debería recibir solicitudes todo el tiempo. Este es el último comando en el dockerfile.
Ejecutaremos la aplicación con python3.
CMD ["python3","main.py"]
Entonces, al final, el archivo Docker se ve así:
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9-slim
WORKDIR /app
EXPOSE 80
COPY requirements.txt .
RUN ["pip", "install", "-r","./requirements.txt"]
COPY . .
CMD ["python3","main.py"]
Este es el molde de la imagen de docker que vamos a crear.
Cambios para hacer en nuestro archivo del backend, main.py
1. Incluir CORSMiddleware
CORS un mecanismo para controlar quién puede acceder a nuestra API, quién puede realizar solicitudes y obtener respuestas. Tenemos que incluir esto en nuestro archivo main.py.
Puedes consultar la documentación de fastAPI aquí.
Un middleware es una función que se aplica a cada solicitud que llega a nuestra aplicación y también a cada respuesta. Lo cual es muy útil, por ejemplo en este caso, esta función modifica el encabezado de las solicitudes a la API para incluir información sobre qué dominios están permitidos.
En este caso, habilitaremos las solicitudes desde todas partes, agregando este fragmento de código:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Sin embargo, en este caso incluir esto no es absolutamente necesario ya que esta implementación será local. Para desarrollo es útil, pero es inseguro permitir solicitudes de todas partes.
2. Cambiar el puerto Localhost
El puerto 3000 es localhost y deberíamos cambiarlo.
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", port=3000, reload=True)
Actualmente la aplicación intentará ejecutarse en enlocalhost en el puerto 3000. Eso no va a funcionar. Porque el host local del contenedor de la ventana acoplable no estará expuesto al host que ejecuta el contenedor. Incluso si lo fuera, no sería apropiado porque el puerto que exponen nuestras imágenes de la ventana acoplable es el puerto 80.
Tenemos que especificar un nuevo host. Por defecto es localhost. PERO para que esto sea accesible desde el exterior, incluida la máquina host que ejecuta el contenedor de Docker, el host debe ser el “0.0.0.0”
Cuando ejecutamos la aplicación debemos asegurarnos de que se esté ejecutando en la IP: “0.0.0.0" y el Puerto: “80”.
Así debe quedar el main del archivo:
if __name__ == "__main__":
import uvicorn
# For local development:
# uvicorn.run("main:app", port=3000, reload=True)
# for docker deployment:
uvicorn.run("main:app", host="0.0.0.0", port=80)
Ahora sí podemos crear la Imagen de Docker!
Crear la Imagen de Docker.
Abre la terminal, dirigete a la carpeta de despliegue
y corre el siguiente comando:
docker build -t taxi-app .
El .
representa la carpeta donde se encuentra el dockerfile.
Nota: el proceso de crear la imagen de docker puede tardar unos cuantos minutos.
2. Dirigete al Docker Desktop
Hemos creado una nueva imagen de Docker a partir de nuestro dockerfile y podemos verla en el Docker Desktop:

Ahora podemos usar esa imagen para crear el contenedor de Docker.
3. Crear el Contenedor de Docker
Estamos ejecutando una imagen para crear un contenedor.
La ejecución de Docker es diferente del inicio de Docker.
docker run: crea nuevos contenedores a partir de imágenes.
Docker start: reinicia todos los contenedores que ya se han creado.
Ahora corre en la terminal el siguiente comando:
docker run -p 3000:80 taxi-app
Debemos especificar un "port binding", o un enlace de puertos.
3000 → es el puerto en el host.
80 → es el puerto en el contenedor. Este debería ser un puerto que exponga la imagen. Si especificamos algo diferente de 80, la aplicación no podrá comunicarse con el host.
4. Ahora dirigete a http://localhost:3000/docs
¡Podemos observar nuestra app con éxito!

A simple vista nuestra aplicación se ve igual a cómo se veía antes, pero hemos hecho algo independiente del entorno y del sistema operativo. Funciona para mi mac y funcionará en cualquier lugar.
Cuando ejecutamos: se está ejecutando localmente pero desde su propio sistema operativo, desde un contenedor Docker que es algo similar a una VM que se ejecuta dentro de nuestra máquina. Este docker va a funcionar desde cualquier sistema operativo y cualquier entorno siempre que tengamos la aplicación Docker Desktop.
Checkpoint #5
Try out! Ensaya el segundo método POST con features reales:
{"vendor_id":2, "pickup_datetime":"2016-03-14 17:24:55", "passenger_count":1, "pickup_longitude":-73.9821548462, "pickup_latitude":40.7679367065, "dropoff_longitude":-73.964630127, "dropoff_latitude":40.7656021118, "pickup_borough":"Manhattan", "dropoff_borough":"Manhattan"}
Last updated
Was this helpful?