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 .env en 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:

  1. Trigger: Se activa cuando haces push a main (o a la rama que configures)
  2. Test: Ejecuta tests unitarios y de integración
  3. Build: Construye el Docker image
  4. Push: Sube el image a un registry (si la plataforma lo requiere)
  5. Deploy: Dispara el deployment en la plataforma
  6. 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

PuntoQué fallaSíntomaSolución
Docker buildDependencias incompatiblesBuild falla en CI pero no localUsar pip freeze > requirements.txt exacto
SecretsVariable no configurada en plataformaApp crashea al iniciarVerificar TODAS las env vars antes de deploy
Health checkEndpoint no responde a tiempoPlatform mata el containerAjustar timeout, verificar cold start
PortApp escucha en puerto diferenteConnection refusedVerificar PORT env var o hardcoded
RegistryImage no accesibleDeploy falla: image not foundVerificar permisos del registry
CORSFrontend no puede llamar a la APIError en browser, API funciona en curlConfigurar 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

  1. GitHub Actions Documentation — Referencia completa de GitHub Actions
  2. Docker Multi-stage Builds — Optimización de Docker images
  3. Pydantic Settings Management — Configuración con Pydantic
  4. Render Deploy Hooks — Automatizar deploys en Render
  5. Railway CLI Reference — CLI de Railway para CI/CD
  6. Fly.io Continuous Deployment — CI/CD con Fly.io