Aprende Docker desde cero creando y dockerizando una aplicación web que muestra fotos aleatorias de gatitos.
Una API (Application Programming Interface) es un contrato entre dos programas: define qué puedes pedir y qué recibirás a cambio, sin que necesites saber cómo funciona por dentro.
Piénsalo como un camarero en un restaurante:
- Tú (cliente) haces un pedido → request
- El camarero (API) lo lleva a cocina y te trae la respuesta → response
- No necesitas saber cómo se cocina, solo qué pedir
Una API puede devolver:
- JSON — datos estructurados (
{"status": "healthy"}) - HTML — una página web completa (lo que hace
app.pyen/) - Archivos — imágenes, PDFs, etc.
| Endpoint | Método | Devuelve | Para qué |
|---|---|---|---|
/ |
GET | HTML | Página con gatito aleatorio |
/health |
GET | JSON | Verificar que la app está viva |
FastAPI es el framework que usamos para construir esta API en Python. Genera automáticamente documentación interactiva en
/docs.
Cuando tu navegador pide una página o tu código llama a una API, usan HTTP (HyperText Transfer Protocol): el idioma estándar de la web. Un protocolo es simplemente un conjunto de reglas que dos partes acuerdan seguir para entenderse.
Cada intercambio tiene dos partes:
1. Request (petición) — el cliente habla:
GET /health HTTP/1.1
Host: localhost:5000
Accept: application/json
- Método (
GET,POST,PUT,DELETE) — qué quieres hacer - Path (
/health) — sobre qué recurso - Headers — metadatos (qué formato acepto, quién soy, etc.)
- Body — datos opcionales (solo en
POST/PUT)
2. Response (respuesta) — el servidor responde:
HTTP/1.1 200 OK
Content-Type: application/json
{"status": "healthy", "message": "Gatitos app is running!"}
- Status code — resultado en un número (ver tabla abajo)
- Headers — metadatos de la respuesta
- Body — el contenido devuelto (JSON, HTML, etc.)
| Método | Para qué | Ejemplo |
|---|---|---|
GET |
Leer/obtener datos | Cargar la página de gatitos |
POST |
Crear un recurso nuevo | Enviar un formulario |
PUT |
Actualizar un recurso | Editar un perfil |
DELETE |
Eliminar un recurso | Borrar un registro |
| Código | Significado | Cuándo ocurre |
|---|---|---|
200 OK |
Todo bien | Respuesta exitosa |
201 Created |
Creado correctamente | POST exitoso |
400 Bad Request |
El cliente mandó algo mal | Parámetro inválido |
401 Unauthorized |
No autenticado | Falta token/login |
403 Forbidden |
Autenticado pero sin permiso | Acceso denegado |
404 Not Found |
Recurso no existe | Path equivocado |
500 Internal Server Error |
El servidor explotó | Bug en el backend |
El backend es el programa que escucha requests HTTP y devuelve responses. Cuando escribes app.py con FastAPI estás definiendo exactamente:
- Qué paths existen (
/,/health) - Qué métodos aceptan (
@app.get(...)) - Qué devuelven (HTML, JSON)
- Qué status code corresponde (FastAPI lo gestiona automáticamente)
Sin este protocolo, cliente y servidor no tendrían forma de entenderse — como intentar hablar español con alguien que solo habla japonés.
- Ejecutar una app web local
- Entender qué es Docker y para qué sirve
- Crear un Dockerfile
- Construir una imagen Docker
- Ejecutar un contenedor
- Comparar local vs Docker
practica-gatitos/
├── app.py # App LOCAL (versión simple)
├── app_docker.py # App DOCKER (optimizada para contenedores)
├── requirements.txt # Dependencias: FastAPI + Uvicorn
├── Dockerfile # Instrucciones para crear la imagen
├── .dockerignore # Archivos a ignorar
├── templates/
│ ├── index.html # Template para versión local
│ └── index_docker.html # Template para versión Docker
└── README.md # Esta guía
Para entender qué problemas resuelve Docker, primero necesitamos experimentar los problemas de una app local.
python --version
# Debe ser Python 3.8 o superiorcd modulo-08-despliegue/deploy/intro-dockers# Con uv (recomendado) - Usa comillas para evitar error en zsh
uv add fastapi "uvicorn[standard]" jinja2
# O con pip
pip install -r requirements.txtNota para usuarios de zsh: Si usas zsh (shell por defecto en Mac), debes usar comillas alrededor de
uvicorn[standard]para evitar el error "no matches found".
uv run python app.pySalida esperada:
Iniciando Gatitos App...
Abre tu navegador en: http://127.0.0.1:5000
Para detener: Ctrl+C
* Running on http://127.0.0.1:5000
Abre: http://127.0.0.1:5000
Deberías ver:
- Foto aleatoria de un gatito
- Badge verde "Corriendo Localmente"
- Botón "Otro gatito"
- Texto "¡Aplicación web con FastAPI!"
- Click en "Otro gatito" → Cambia la imagen
- Recarga la página → Otro gatito diferente
- Ir a http://127.0.0.1:5000/health → Ver endpoint de salud
Presiona Ctrl+C en la terminal
| Problema | Descripción |
|---|---|
| Dependencias | Necesitas instalar FastAPI y Uvicorn manualmente |
| Versiones | ¿Qué pasa si otro proyecto necesita otras versiones? |
| Portabilidad | En otra computadora, ¿funcionará igual? |
| Configuración | Necesitas Python instalado |
| "En mi máquina funciona" | Clásico problema de desarrollo |
Docker resuelve todos estos problemas.
Docker es una plataforma que permite empaquetar una aplicación con todas sus dependencias en un contenedor que puede ejecutarse en cualquier lugar.
- Imagen: Plantilla con todo lo necesario (código + dependencias)
- Contenedor: Instancia en ejecución de una imagen
- Dockerfile: Receta para crear una imagen
docker --versionDeberías ver algo como: Docker version xx.xx.xx
Abre el archivo Dockerfile y revisa cada línea:
# Imagen base: Python ya instalado
FROM python:3.11-slim
# Directorio de trabajo dentro del contenedor
WORKDIR /app
# Copiar dependencias
COPY requirements.txt .
# Instalar FastAPI y dependencias
RUN pip install --no-cache-dir -r requirements.txt
# Copiar código de la app
COPY app_docker.py app.py
COPY templates/ templates/
# Exponer el puerto 5000 (donde corre FastAPI)
EXPOSE 5000
# Variable de entorno para Python
ENV PYTHONUNBUFFERED=1
# Comando para iniciar la app con uvicorn
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "5000"]Explicación:
FROM: Usamos Python 3.11 como baseWORKDIR: Creamos carpeta/appen el contenedorCOPY requirements.txt: Copiamos solo dependencias primeroRUN pip install: Instalamos FastAPI, Uvicorn y Jinja2COPY app_docker.py: Copiamos el códigoEXPOSE 5000: Documentamos el puertoCMD: Comando para ejecutar la app con uvicorn
docker build -t gatitos-app .Explicación:
docker build: Construir imagen-t gatitos-app: Nombre (tag) de la imagen.: Usar Dockerfile en directorio actual
Salida esperada (~30-60 segundos):
[+] Building 15.2s (11/11) FINISHED
=> [1/7] FROM docker.io/library/python:3.11-slim
=> [2/7] WORKDIR /app
=> [3/7] COPY requirements.txt .
=> [4/7] RUN pip install --no-cache-dir -r requirements.txt
=> [5/7] COPY app_docker.py app.py
=> [6/7] COPY templates/ templates/
=> exporting to image
=> => naming to docker.io/library/gatitos-app
docker build -t gatitos-app .Qué hace: Reutiliza capas que no han cambiado.
Ventajas:
- ⚡ Muy rápido (0.2-2 segundos)
- 💾 No descarga Python de nuevo
- 🎯 No reinstala Flask si no cambió
Cuándo usar:
- Desarrollo diario
- Solo cambiaste código Python
- Quieres iterar rápido
Salida típica:
=> CACHED [4/6] RUN pip install ... ← No reinstala
Total: 0.2 segundos ⚡
docker build --no-cache -t gatitos-app .Qué hace: Reconstruye TODO desde cero.
Ventajas:
- ✅ Build limpia garantizada
- ✅ Elimina problemas de cache corrupto
- ✅ Recomendado antes de producción
Cuándo usar:
- Hay un bug extraño
- Antes de desplegar a producción
- Cambiaste
requirements.txt
Salida típica:
=> [4/6] RUN pip install ... 15.0s ← Reinstala todo
Total: 45 segundos 🐌
Regla de oro: Usa CON cache (99% del tiempo). Solo usa SIN cache cuando tengas dudas o vayas a producción.
docker imagesSalida esperada:
REPOSITORY TAG IMAGE ID CREATED SIZE
gatitos-app latest abc123def456 10 seconds ago 150MB
docker run -d -p 8080:5000 --name mi-gatitos gatitos-appExplicación:
docker run: Ejecutar contenedor-d: Modo detached (segundo plano)-p 8080:5000: Mapear puerto 8080 (tu PC) → 5000 (contenedor)--name mi-gatitos: Nombre del contenedorgatitos-app: Imagen a usar
Salida esperada:
a1b2c3d4e5f6789... (ID del contenedor)
docker psSalida esperada:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 gatitos-app "python app.py" 5 seconds ago Up 4 seconds 0.0.0.0:8080->5000/tcp mi-gatitos
Abre: http://localhost:8080
Deberías ver:
- 🐱 Foto aleatoria de un gatito
- 🐳 Badge azul "Corriendo en Docker"
- Fondo azul (diferente al local)
- Información sobre Docker
docker logs mi-gatitosSalida esperada:
🐳 Iniciando Gatitos App en Docker...
📍 La app estará disponible en el puerto mapeado
* Running on http://0.0.0.0:5000
docker stats mi-gatitosPresiona Ctrl+C para salir
docker stop mi-gatitosdocker rm mi-gatitos| Aspecto | Local | Docker |
|---|---|---|
| Comando para ejecutar | python app.py |
docker run ... |
| Puerto | 5000 | 8080 → 5000 |
| Dependencias | Instalar manualmente | Incluidas en la imagen |
| Python | Necesitas instalarlo | Ya incluido |
| Portabilidad | ❌ Depende del sistema | ✅ Funciona en cualquier lado |
| Aislamiento | ❌ Comparte con el sistema | ✅ Totalmente aislado |
| Reproducibilidad | ❌ Puede variar | ✅ Siempre igual |
| Tamaño | ~50 MB (solo código) | ~150 MB (todo incluido) |
| Inicio | Rápido (~1s) | Medio (~3s) |
- 💻 Badge verde "Corriendo Localmente"
- Fondo morado
- Puerto: 5000
- 🐳 Badge azul "Corriendo en Docker"
- Fondo azul
- Puerto: 8080
- ✅ Portabilidad: Funciona igual en Mac, Windows, Linux
- ✅ Aislamiento: No afecta otras apps ni el sistema
- ✅ Reproducibilidad: Mismo resultado siempre
- ✅ Fácil de compartir: Solo necesitas la imagen
- ✅ Escalabilidad: Fácil crear múltiples instancias
- ✅ Versionado: Puedes tener múltiples versiones
- ✅ CI/CD: Integración continua simplificada
✅ Usa Docker cuando:
- Trabajas en equipo
- Despliegas en producción
- Necesitas consistencia entre ambientes
- Quieres aislar dependencias
- Trabajas con microservicios
❌ No necesitas Docker para:
- Scripts simples de una sola vez
- Desarrollo muy rápido/prototipos
- Aprendizaje básico de Python
# Terminal 1: App local
uv run python app.py
# Abre: http://127.0.0.1:5000
# Terminal 2: App Docker
docker run -d -p 8080:5000 --name gatitos-docker gatitos-app
# Abre: http://localhost:8080
# Compara las dos versiones lado a lado# Crear 3 contenedores
docker run -d -p 8080:5000 --name gatitos-1 gatitos-app
docker run -d -p 8081:5000 --name gatitos-2 gatitos-app
docker run -d -p 8082:5000 --name gatitos-3 gatitos-app
# Abrir en el navegador:
# http://localhost:8080
# http://localhost:8081
# http://localhost:8082
# Cada uno muestra gatitos diferentes!# Ver detalles del contenedor
docker inspect mi-gatitos
# Ejecutar comando dentro del contenedor
docker exec -it mi-gatitos bash
# Dentro del contenedor:
ls -la
cat app.py
exit- Modifica
app_docker.py(agrega más URLs de gatitos) - Reconstruye la imagen:
docker build -t gatitos-app:v2 . - Ejecuta la nueva versión:
docker run -d -p 8080:5000 --name gatitos-v2 gatitos-app:v2
# Ver contenedores corriendo
docker ps
# Ver todos los contenedores (incluyendo detenidos)
docker ps -a
# Detener contenedor
docker stop <nombre>
# Iniciar contenedor detenido
docker start <nombre>
# Reiniciar contenedor
docker restart <nombre>
# Eliminar contenedor
docker rm <nombre>
# Eliminar contenedor corriendo (forzar)
docker rm -f <nombre>
# Eliminar todos los contenedores detenidos
docker container prune# Ver imágenes
docker images
# Eliminar imagen
docker rmi <nombre>
# Eliminar imágenes sin usar
docker image prune
# Construir imagen
docker build -t <nombre> .
# Construir sin cache
docker build --no-cache -t <nombre> .# Ver logs
docker logs <nombre>
# Seguir logs en tiempo real
docker logs -f <nombre>
# Ver últimas 50 líneas
docker logs --tail 50 <nombre>
# Ejecutar comando en contenedor
docker exec <nombre> <comando>
# Abrir shell interactiva
docker exec -it <nombre> bash
# Ver estadísticas
docker stats <nombre>
# Ver procesos
docker top <nombre># Eliminar todo lo que no se usa
docker system prune
# Eliminar todo (incluyendo volúmenes)
docker system prune -a --volumes
# Ver espacio usado
docker system dfError: Bind for 0.0.0.0:8080 failed: port is already allocated
Solución:
# Opción 1: Usar otro puerto
docker run -d -p 8081:5000 --name mi-gatitos gatitos-app
# Opción 2: Detener el contenedor que usa el puerto
docker ps
docker stop <contenedor-que-usa-8080>Solución:
# Ver logs para identificar el error
docker logs mi-gatitos
# Ejecutar en modo interactivo para ver errores
docker run -it -p 8080:5000 gatitos-appVerificar:
- ¿El contenedor está corriendo? →
docker ps - ¿El puerto está bien mapeado? → Revisar columna PORTS
- ¿La URL es correcta? → http://localhost:8080 (no 5000)
- ¿Hay firewall bloqueando? → Revisar configuración
Solución:
# Reconstruir la imagen
docker build -t gatitos-app .
# Detener y eliminar contenedor viejo
docker stop mi-gatitos
docker rm mi-gatitos
# Crear nuevo contenedor con la imagen actualizada
docker run -d -p 8080:5000 --name mi-gatitos gatitos-app- Documentación oficial de Docker
- Docker Hub - Repositorio de imágenes
- Cat as a Service API - API de gatitos usada
- FastAPI Documentation
- Uvicorn Documentation
- Ejecuté la app localmente
- Entiendo los problemas de las apps locales
- Entiendo qué es Docker
- Leí y entendí el Dockerfile línea por línea
- Construí una imagen Docker
- Ejecuté un contenedor
- Mapeé puertos correctamente
- Vi los logs de un contenedor
- Detuve y eliminé contenedores
- Comparé local vs Docker
- Ejecuté múltiples contenedores simultáneamente
- Entiendo cuándo usar Docker
Has completado la práctica de Docker. Ahora sabes:
✅ Qué es Docker y para qué sirve
✅ Cómo crear un Dockerfile
✅ Cómo construir imágenes
✅ Cómo ejecutar contenedores
✅ Las diferencias entre local y Docker
✅ Comandos básicos de Docker
- Docker Compose: Aprende a orquestar múltiples contenedores
- Volúmenes: Persistir datos entre ejecuciones
- Redes: Comunicación entre contenedores
- Docker Hub: Publicar tus imágenes
- Producción: Desplegar apps dockerizadas
💡 Recuerda: Docker es una herramienta, no una solución mágica. Úsala cuando tenga sentido para tu proyecto.