Módulo 8: Proyecto Integrador — Deployed AI System
2. Architecture Integration — Conectando las Piezas
Descripción
En esta cápsula vas a entender cómo Docker images, CI/CD pipelines, y plataformas de deployment se conectan en un flujo de producción coherente. No vas a aprender herramientas nuevas — vas a ver cómo las herramientas que ya conoces encajan entre sí. La diferencia entre "sé usar Docker" y "sé poner un sistema AI en producción" es exactamente esta: integración.
Contexto: En módulos anteriores, trabajaste cada pieza por separado: Docker images optimizados (M2), CI/CD con GitHub Actions (#16), plataformas de deployment (M7). Ahora las conectas. El diagrama que sale de esta cápsula es el mapa de todo lo que ejecutarás en las cápsulas siguientes.
La Arquitectura de Integración
El flujo completo
Cuando haces git push a tu rama principal, esto es lo que debe ocurrir — sin intervención manual:
Developer GitHub Platform
│ │ │
│ git push main │ │
├────────────────────────►│ │
│ │ Trigger workflow │
│ ├──────┐ │
│ │ │ Run tests │
│ │ │ Build image │
│ │ │ Push to registry │
│ │◄─────┘ │
│ │ │
│ │ Deploy (push/webhook) │
│ ├─────────────────────────►│
│ │ │ Pull image
│ │ │ Start container
│ │ │ Health check
│ │ │
│ │ Post-deploy validation │
│ ├──────┐ │
│ │ │ Smoke tests │
│ │ │ Verify inference │
│ │◄─────┘ │
│ │ │
│ ✅ Deploy complete │ │
│◄────────────────────────┤ │
Cada flecha es un punto de integración. Cada punto de integración es un lugar donde algo puede fallar. Esta cápsula te prepara para cada uno.
Los tres componentes
┌─────────────────────────────────────────────────────────┐
│ INTEGRATION ARCHITECTURE │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ Docker │───►│ CI/CD │───►│ Platform │ │
│ │ Image │ │ Pipeline │ │ (Deploy) │ │
│ └──────────┘ └──────────────┘ └───────────────┘ │
│ │ │ │ │
│ Dockerfile GitHub Actions Render/Railway │
│ .dockerignore build + test Fly.io/AWS │
│ Multi-stage Push to registry Pull + run │
│ Deploy trigger Health check │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ CONFIGURATION LAYER │ │
│ │ .env.local │ .env.staging │ .env.production │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Componente 1: Docker Image Optimizado
Lo que ya tienes del Módulo 2
Tu Dockerfile debe producir un image que:
- Sea reproducible (misma versión, mismo resultado)
- Sea pequeño (multi-stage build)
- No contenga secrets (no
.enven el image) - Tenga health check integrado
Docker Image para producción
# === Stage 1: Builder ===
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
COPY src/ ./src/
# === Stage 2: Runtime ===
FROM python:3.11-slim AS runtime
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app/src ./src/
ENV PATH=/root/.local/bin:$PATH
ENV PYTHONUNBUFFERED=1
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
El .dockerignore que protege tu image
.git
.github
.env
.env.*
__pycache__
*.pyc
.pytest_cache
docs/
tests/
*.md
.vscode
.cursor
node_modules
Verificación local antes de integrar
# Build del image
docker build -t mi-ai-app:latest .
# Ejecutar localmente
docker run -d --name test-app \
-p 8000:8000 \
-e OPENAI_API_KEY=$OPENAI_API_KEY \
mi-ai-app:latest
# Verificar health
curl http://localhost:8000/health
# Verificar inferencia
curl -X POST http://localhost:8000/api/inference \
-H "Content-Type: application/json" \
-d '{"prompt": "Explica qué es Docker en una frase"}'
# Cleanup
docker stop test-app && docker rm test-app
Si tu image no funciona localmente, no funcionará en producción. Verifica ANTES de integrar.
Componente 2: CI/CD Pipeline
Cómo CI/CD conecta con Docker
El pipeline de GitHub Actions es el puente entre tu código y la plataforma. Su trabajo:
- Trigger: Se activa cuando haces push a main (o a la rama que configures)
- Test: Ejecuta tests unitarios y de integración
- Build: Construye el Docker image
- Push: Sube el image a un registry (si la plataforma lo requiere)
- Deploy: Dispara el deployment en la plataforma
- Validate: Ejecuta smoke tests contra producción
Pipeline base para cualquier plataforma
# .github/workflows/deploy.yml
name: Deploy AI System
on:
push:
branches: [main]
env:
PYTHON_VERSION: "3.11"
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest httpx
- name: Run tests
run: pytest tests/ -v --tb=short
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ENVIRONMENT: test
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# === AQUÍ VA LA SECCIÓN ESPECÍFICA DE TU PLATAFORMA ===
# Ver las variantes por plataforma más abajo
validate:
needs: deploy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Wait for deployment
run: sleep 30
- name: Health check
run: |
response=$(curl -s -o /dev/null -w "%{http_code}" ${{ vars.PRODUCTION_URL }}/health)
if [ "$response" != "200" ]; then
echo "Health check failed with status $response"
exit 1
fi
- name: Smoke test - inference
run: |
response=$(curl -s -X POST ${{ vars.PRODUCTION_URL }}/api/inference \
-H "Content-Type: application/json" \
-d '{"prompt": "Test prompt: respond with OK"}')
echo "Response: $response"
if echo "$response" | grep -q "error"; then
echo "Smoke test failed"
exit 1
fi
Variantes por plataforma
Render — Deploy por Git push automático:
# Render despliega automáticamente al detectar push en el repo conectado.
# No necesitas step de deploy en GitHub Actions.
# Solo configura el repo en el dashboard de Render.
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- name: Trigger Render deploy
run: |
curl -X POST "${{ secrets.RENDER_DEPLOY_HOOK_URL }}"
# El deploy hook lo obtienes en Settings → Deploy Hook en Render
Railway — Deploy con CLI:
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Railway CLI
run: npm install -g @railway/cli
- name: Deploy to Railway
run: railway up --service ${{ vars.RAILWAY_SERVICE_ID }}
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
Fly.io — Deploy con flyctl:
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Fly.io CLI
uses: superfly/flyctl-actions/setup-flyctl@master
- name: Deploy to Fly.io
run: flyctl deploy --remote-only
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
AWS Lambda — Deploy con SAM o Serverless Framework:
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy with SAM
run: |
sam build
sam deploy --no-confirm-changeset --no-fail-on-empty-changeset
Componente 3: Plataforma de Deployment
Lo que la plataforma hace por ti
Independientemente de cuál elijas, la plataforma se encarga de:
Lo que TÚ provees: Lo que la PLATAFORMA hace:
───────────────────── ──────────────────────────
Docker image (o código) → Build del container
Variables de entorno → Inyección de secrets
Health check endpoint → Verificación de salud
Puerto de la app → Routing + SSL/TLS
→ Dominio público (*.railway.app, etc.)
→ Logs accesibles
→ Restart automático si crashea
Configuración mínima por plataforma
Para Render:
# render.yaml (Infrastructure as Code)
services:
- type: web
name: mi-ai-app
env: docker
plan: free
healthCheckPath: /health
envVars:
- key: OPENAI_API_KEY
sync: false
- key: ENVIRONMENT
value: production
Para Railway:
# railway.toml
[build]
builder = "dockerfile"
dockerfilePath = "Dockerfile"
[deploy]
healthcheckPath = "/health"
healthcheckTimeout = 30
restartPolicyType = "on_failure"
restartPolicyMaxRetries = 5
Para Fly.io:
# fly.toml
app = "mi-ai-app"
primary_region = "iad"
[build]
dockerfile = "Dockerfile"
[http_service]
internal_port = 8000
force_https = true
[[http_service.checks]]
interval = "30s"
timeout = "10s"
grace_period = "5s"
method = "GET"
path = "/health"
Configuration Layer: Multi-entorno
El mismo código, diferente configuración
Tu app debe funcionar en tres entornos con la misma imagen Docker, diferenciada solo por variables de entorno:
# src/config.py
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
environment: str = "development"
app_name: str = "AI System"
debug: bool = False
openai_api_key: str = ""
openai_model: str = "gpt-4o-mini"
log_level: str = "INFO"
cors_origins: list[str] = ["http://localhost:3000"]
class Config:
env_file = ".env"
@property
def is_production(self) -> bool:
return self.environment == "production"
@lru_cache()
def get_settings() -> Settings:
return Settings()
Variables por entorno
# .env.local (desarrollo)
ENVIRONMENT=development
DEBUG=true
LOG_LEVEL=DEBUG
OPENAI_API_KEY=sk-...
CORS_ORIGINS=["http://localhost:3000"]
# .env.staging (si aplica)
ENVIRONMENT=staging
DEBUG=false
LOG_LEVEL=INFO
OPENAI_API_KEY=sk-...
CORS_ORIGINS=["https://staging.tu-dominio.com"]
# Variables en plataforma (producción)
# NO en archivo — configuradas en el dashboard o CI/CD secrets
ENVIRONMENT=production
DEBUG=false
LOG_LEVEL=WARNING
OPENAI_API_KEY=sk-... # En secrets de la plataforma
CORS_ORIGINS=["https://tu-dominio.com"]
Health check endpoint
# src/main.py
from fastapi import FastAPI
from src.config import get_settings
app = FastAPI()
settings = get_settings()
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"environment": settings.environment,
"version": "1.0.0",
}
@app.post("/api/inference")
async def inference(request: InferenceRequest):
# Tu lógica de inferencia AI
...
Diagrama de Integración Completo
Todo junto
┌──────────────────────────────────────────────────────────────┐
│ LOCAL DEVELOPMENT │
│ │
│ docker compose up → localhost:8000 → test manually │
│ .env.local loaded │
└──────────────────────┬───────────────────────────────────────┘
│
git push main
│
┌──────────────────────▼───────────────────────────────────────┐
│ GITHUB ACTIONS │
│ │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Test │──►│ Build │──►│ Deploy │──►│ Validate │ │
│ │ pytest │ │ docker │ │ platform│ │ smoke │ │
│ └─────────┘ └─────────┘ └──────────┘ └──────────┘ │
│ │
│ Secrets: OPENAI_API_KEY, PLATFORM_TOKEN, PRODUCTION_URL │
└──────────────────────────────────────────────────────────────┘
│
deploy trigger
│
┌──────────────────────▼───────────────────────────────────────┐
│ PRODUCTION PLATFORM │
│ │
│ Pull image → Start container → Health check → Live ✅ │
│ .env from platform secrets │
│ URL: https://tu-app.platform.app │
│ Auto-restart on failure │
│ SSL/TLS included │
└──────────────────────────────────────────────────────────────┘
Puntos de Fallo Comunes
Dónde se rompe la integración
| Punto | Qué falla | Síntoma | Solución |
|---|---|---|---|
| Docker build | Dependencias incompatibles | Build falla en CI pero no local | Usar pip freeze > requirements.txt exacto |
| Secrets | Variable no configurada en plataforma | App crashea al iniciar | Verificar TODAS las env vars antes de deploy |
| Health check | Endpoint no responde a tiempo | Platform mata el container | Ajustar timeout, verificar cold start |
| Port | App escucha en puerto diferente | Connection refused | Verificar PORT env var o hardcoded |
| Registry | Image no accesible | Deploy falla: image not found | Verificar permisos del registry |
| CORS | Frontend no puede llamar a la API | Error en browser, API funciona en curl | Configurar CORS_ORIGINS para producción |
Troubleshooting
Problema 1: "Docker build funciona local pero falla en GitHub Actions"
Causa: Diferencia de plataforma (M1/M2 Mac vs Linux en CI), dependencias del sistema, o cache corrupta.
Solución:
# Forzar build sin cache en CI
- name: Build Docker image
run: docker build --no-cache -t mi-app:latest .
# Si usas dependencias con extensiones C (numpy, etc.)
# asegúrate de instalar build tools
RUN apt-get update && apt-get install -y build-essential
Problema 2: "El deploy se completa pero la app no responde"
Causa: La app crashea al iniciar porque falta una variable de entorno o un servicio dependiente.
Solución:
import sys
from src.config import get_settings
settings = get_settings()
if not settings.openai_api_key:
print("ERROR: OPENAI_API_KEY not set", file=sys.stderr)
sys.exit(1)
Problema 3: "Health check pasa pero la inferencia no funciona"
Causa: El health check solo verifica que la API responde, no que el servicio de inferencia está configurado correctamente.
Solución: Implementar un health check que verifica dependencias (ver cápsula 04).
Problema 4: "Todo funciona la primera vez pero el redeploy falla"
Causa: Estado stale en la plataforma, container no se reinicia limpiamente.
Solución:
# Forzar redeploy limpio (Render)
curl -X POST "$RENDER_DEPLOY_HOOK_URL"
# Railway: forzar redeploy
railway up --service SERVICE_ID
# Fly.io: redeploy
flyctl deploy --remote-only --strategy immediate
Problema 5: "Variables de entorno no se cargan en producción"
Causa: Las env vars están en .env local pero no configuradas en la plataforma.
Solución:
# Listar variables configuradas
# Railway
railway variables
# Fly.io
flyctl secrets list
# Render: verifica en Dashboard → Environment
# Script de verificación
python -c "
from src.config import get_settings
s = get_settings()
print(f'Environment: {s.environment}')
print(f'API Key set: {bool(s.openai_api_key)}')
print(f'Model: {s.openai_model}')
"
Ejercicios Prácticos
Ejercicio 1: Verifica tu Docker image
Construye tu Docker image y verifica que funciona antes de integrarlo con CI/CD.
# Build
docker build -t mi-ai-app:test .
# Run con env vars
docker run -d --name test-integration \
-p 8000:8000 \
-e OPENAI_API_KEY=$OPENAI_API_KEY \
-e ENVIRONMENT=test \
mi-ai-app:test
# Verificar
curl http://localhost:8000/health
curl -X POST http://localhost:8000/api/inference \
-H "Content-Type: application/json" \
-d '{"prompt": "Say hello"}'
Ver solución
# Build exitoso
$ docker build -t mi-ai-app:test .
# [+] Building 45.3s (12/12) FINISHED
# Run exitoso
$ docker run -d --name test-integration \
-p 8000:8000 \
-e OPENAI_API_KEY=$OPENAI_API_KEY \
-e ENVIRONMENT=test \
mi-ai-app:test
# abc123def456...
# Health check
$ curl http://localhost:8000/health
# {"status":"healthy","environment":"test","version":"1.0.0"}
# Inference
$ curl -s -X POST http://localhost:8000/api/inference \
-H "Content-Type: application/json" \
-d '{"prompt": "Say hello"}'
# {"response":"Hello! How can I help you today?","model":"gpt-4o-mini"}
# Cleanup
$ docker stop test-integration && docker rm test-integration
Si el health check falla, revisa los logs con docker logs test-integration. Si la inferencia falla pero el health check pasa, tu API key no se inyectó correctamente.
Ejercicio 2: Crea el pipeline base de GitHub Actions
Crea el archivo .github/workflows/deploy.yml con los jobs de test, deploy, y validate.
Ver solución
# .github/workflows/deploy.yml
name: Deploy AI System
on:
push:
branches: [main]
workflow_dispatch:
env:
PYTHON_VERSION: "3.11"
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- run: pip install -r requirements.txt && pip install pytest httpx
- run: pytest tests/ -v --tb=short
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ENVIRONMENT: test
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Agrega los steps de TU plataforma (Render/Railway/Fly.io/AWS)
- name: Deploy
run: echo "Add your platform-specific deploy step here"
validate:
needs: deploy
runs-on: ubuntu-latest
steps:
- name: Wait for deployment
run: sleep 45
- name: Health check
run: |
for i in 1 2 3 4 5; do
status=$(curl -s -o /dev/null -w "%{http_code}" ${{ vars.PRODUCTION_URL }}/health)
if [ "$status" = "200" ]; then
echo "Health check passed"
exit 0
fi
echo "Attempt $i: status $status, retrying in 15s..."
sleep 15
done
echo "Health check failed after 5 attempts"
exit 1
El job validate tiene retry logic porque el deploy puede tardar más de lo esperado. 5 intentos × 15 segundos = 75 segundos de tolerancia.
Ejercicio 3: Configura multi-entorno
Crea el archivo src/config.py con Pydantic Settings y verifica que funciona en local y producción.
Ver solución
# src/config.py
from pydantic_settings import BaseSettings
from functools import lru_cache
from typing import Optional
class Settings(BaseSettings):
environment: str = "development"
app_name: str = "AI System"
debug: bool = False
version: str = "1.0.0"
openai_api_key: str = ""
openai_model: str = "gpt-4o-mini"
openai_temperature: float = 0.7
log_level: str = "INFO"
cors_origins: list[str] = ["http://localhost:3000"]
port: int = 8000
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
@property
def is_production(self) -> bool:
return self.environment == "production"
@property
def is_development(self) -> bool:
return self.environment == "development"
def validate_for_production(self) -> list[str]:
"""Retorna lista de errores de configuración."""
errors = []
if not self.openai_api_key:
errors.append("OPENAI_API_KEY is required")
if self.debug and self.is_production:
errors.append("DEBUG must be False in production")
return errors
@lru_cache()
def get_settings() -> Settings:
return Settings()
Verificación:
# Local
ENVIRONMENT=development python -c "
from src.config import get_settings
s = get_settings()
print(f'Env: {s.environment}, Debug: {s.debug}, Key set: {bool(s.openai_api_key)}')
errors = s.validate_for_production()
print(f'Production-ready: {len(errors) == 0}, Errors: {errors}')
"
Ejercicio 4: Dibuja tu diagrama de integración
Usando el template de esta cápsula, dibuja el diagrama de integración específico para tu stack. Incluye: tu plataforma elegida, los services que tu app necesita, y las variables de entorno por entorno.
Ver solución
Ejemplo para una app RAG con Railway:
┌───────────────────────────────────────────────┐
│ LOCAL (docker compose up) │
│ │
│ FastAPI (:8000) ──► ChromaDB (:8001) │
│ │ │
│ └──► OpenAI API (external) │
│ │
│ ENV: .env.local │
│ OPENAI_API_KEY=sk-dev... │
│ ENVIRONMENT=development │
└──────────────────┬────────────────────────────┘
│ git push main
┌──────────────────▼────────────────────────────┐
│ GITHUB ACTIONS │
│ │
│ pytest → build check → railway up │
│ │
│ Secrets: OPENAI_API_KEY, RAILWAY_TOKEN │
└──────────────────┬────────────────────────────┘
│ deploy
┌──────────────────▼────────────────────────────┐
│ RAILWAY (production) │
│ │
│ FastAPI service ──► ChromaDB volume │
│ │ │
│ └──► OpenAI API (external) │
│ │
│ URL: https://mi-rag-app.railway.app │
│ ENV: configured in Railway dashboard │
│ OPENAI_API_KEY=sk-prod... │
│ ENVIRONMENT=production │
└───────────────────────────────────────────────┘
Lo importante es que identifiques cada servicio, cada variable de entorno, y cada punto de conexión entre componentes.
Resumen
- La arquitectura de integración conecta tres componentes: Docker image, CI/CD pipeline, y plataforma de deployment
- El flujo es:
git push→ test → build → deploy → validate — sin intervención manual - Multi-entorno significa el mismo código con diferente configuración: local, staging, producción
- Las variables de entorno son el mecanismo para diferenciar entornos — nunca hardcodees secrets
- Los puntos de fallo están en las interfaces: secrets no configurados, puertos incorrectos, health checks con timeout
- La configuración por plataforma varía (render.yaml, railway.toml, fly.toml) pero el concepto es igual
- Verifica local antes de integrar — si Docker no funciona en tu máquina, no funcionará en producción
Recursos Adicionales
- GitHub Actions Documentation — Referencia completa de GitHub Actions
- Docker Multi-stage Builds — Optimización de Docker images
- Pydantic Settings Management — Configuración con Pydantic
- Render Deploy Hooks — Automatizar deploys en Render
- Railway CLI Reference — CLI de Railway para CI/CD
- Fly.io Continuous Deployment — CI/CD con Fly.io