La Importancia de la Localidad#
El auge de los modelos fundacionales de código abierto ha democratizado el acceso a la Inteligencia Artificial Generativa. Herramientas como vLLM, Ollama o llama.cpp permiten levantar modelos potentes (como Llama 3 o Mistral) en infraestructura propia. Sin embargo, existe una brecha enorme entre ejecutar un modelo en localhost para pruebas y exponer esa misma API a internet o a una aplicación de producción.
Exponer directamente el puerto de tu contenedor de IA (por ejemplo, el 8000 de vLLM o el 11434 de Ollama) en un VPS público es una negligencia arquitectónica. Los servidores de inferencia están diseñados para el procesamiento de tensores y la gestión de la memoria de la GPU, no para lidiar con ataques de denegación de servicio, manejo de certificados TLS o control exhaustivo de cabeceras HTTP.
En este artículo, vamos a diseñar una arquitectura robusta utilizando Docker para la orquestación y Nginx como proxy inverso. Abordaremos problemas específicos de las APIs de IA, como los tiempos de espera prolongados (timeouts), el streaming de respuestas (Server-Sent Events) y la protección de los recursos de cómputo mediante rate limiting.
Arquitectura del Sistema#
El flujo de una petición segura a nuestra API de IA debe seguir este ciclo de vida:
- Cliente: Emite una petición HTTPS (con o sin streaming).
- Nginx (Capa perimetral): Recibe la petición, finaliza la conexión TLS, verifica las políticas de CORS y comprueba el rate limit asignado a la IP o al token.
- Red Interna Docker: Si la petición es válida, Nginx enruta el tráfico a través de una red aislada de Docker.
- Contenedor de Inferencia (IA): Recibe una petición HTTP limpia, procesa el prompt utilizando los recursos de la GPU y devuelve la respuesta.
Esta separación de responsabilidades asegura que tu modelo de IA se dedique exclusivamente a generar tokens, mientras Nginx actúa como un escudo protector.
Requisitos Previos#
Para implementar esta arquitectura, tu entorno de servidor (VPS o bare-metal) debe cumplir con lo siguiente:
- Un sistema operativo Linux (Ubuntu 24.04 o similar recomendado).
- Docker y Docker Compose instalados y actualizados.
- NVIDIA Container Toolkit: Esto es imperativo si vas a utilizar aceleración por GPU. Docker por sí solo no puede acceder a las GPUs del host sin este toolkit.
- Un dominio o subdominio apuntando a la IP pública de tu VPS (necesario para configurar SSL y CORS adecuadamente).
Paso 1: Orquestación con Docker Compose#
La forma más limpia de mantener la infraestructura es a través de infraestructura como código utilizando un archivo docker-compose.yml. Vamos a crear dos servicios: nuestro motor de IA y nuestro servidor Nginx.
Crea un directorio para tu proyecto y dentro, un archivo docker-compose.yml:
services: llm-api: image: vllm/vllm-openai:latest container_name: genai_engine restart: unless-stopped # No mapeamos puertos al host público, solo exponemos en la red interna expose: - "8000" volumes: - ./models:/root/.cache/huggingface # Configuración crítica para pasar la GPU al contenedor deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] command: ["--model", "meta-llama/Meta-Llama-3-8B-Instruct", "--dtype", "auto"] networks: - ai_network
nginx_proxy: image: nginx:alpine container_name: nginx_gateway restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./certs:/etc/nginx/certs:ro depends_on: - llm-api networks: - ai_network
networks: ai_network: driver: bridgeAnálisis del Compose#
- Aislamiento de Red: Nota que el servicio
llm-apiutiliza la directivaexposeen lugar deports.exposeabre el puerto 8000 solo para los contenedores que compartan la redai_network. Es imposible acceder a la IA directamente desde la IP pública del VPS. - Passthrough de GPU: El bloque
deploy.resources.reservations.deviceses la forma moderna de inyectar las capacidades de NVIDIA dentro del contenedor. - Persistencia: Montamos un volumen local (
./models) para almacenar en caché los pesos del modelo. Evitamos descargar decenas de gigabytes cada vez que reiniciamos el contenedor.
Paso 2: Configurando Nginx para Cargas de Trabajo de IA#
La configuración por defecto de Nginx está optimizada para servir páginas web estáticas y APIs rápidas. Las APIs de Inteligencia Artificial Generativa tienen un perfil de tráfico muy distinto: las peticiones pueden tardar minutos en resolverse (si el prompt es complejo) y a menudo devuelven la respuesta en fragmentos (streaming).
Crea un archivo en ./nginx/nginx.conf con el siguiente contenido. Lo desglosaremos paso a paso.
user nginx;worker_processes auto;error_log /var/log/nginx/error.log warn;pid /var/run/nginx.pid;
events { worker_connections 1024;}
http { include /etc/nginx/mime.types; default_type application/octet-stream;
# Ocultar la versión de Nginx por seguridad server_tokens off;
# 1. Configuración de Rate Limiting # Definimos una zona de memoria llamada 'ai_limit' de 10MB # Permite 1 petición por segundo en promedio por IP limit_req_zone $binary_remote_addr zone=ai_limit:10m rate=1r/s;
# Ajustes generales de red sendfile on; keepalive_timeout 65;
# Servidor Virtual HTTPS server { listen 443 ssl http2; server_name api.tu-dominio.com;
# Rutas a tus certificados (asumiendo que usas Let's Encrypt / Certbot) ssl_certificate /etc/nginx/certs/fullchain.pem; ssl_certificate_key /etc/nginx/certs/privkey.pem;
# Opciones SSL robustas ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers HIGH:!aNULL:!MD5;
# 2. Bloque Principal de la API location /v1/ { # Aplicar Rate Limiting: Permite ráfagas de hasta 5 peticiones, # descartando (nodelay) si se supera el límite de procesamiento. limit_req zone=ai_limit burst=5 nodelay;
# 3. Gestión de CORS # Verificación del método preflight (OPTIONS) if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '[https://tu-app-frontend.com](https://tu-app-frontend.com)' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; }
# Cabeceras CORS para las peticiones reales add_header 'Access-Control-Allow-Origin' '[https://tu-app-frontend.com](https://tu-app-frontend.com)' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
# 4. Proxy Pass hacia el contenedor interno proxy_pass http://genai_engine:8000/v1/;
# Ajustes de cabeceras para el backend proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
# 5. Optimizaciones Críticas para IA (Timeouts y Streaming) # Extender timeouts para inferencias largas proxy_read_timeout 300s; proxy_connect_timeout 60s; proxy_send_timeout 300s;
# Desactivar el buffering para soportar Server-Sent Events (Streaming) proxy_buffering off; proxy_cache off; chunked_transfer_encoding on; } }
# Redirección HTTP a HTTPS server { listen 80; server_name api.tu-dominio.com; return 301 https://$host$request_uri; }}Anatomía Crítica de la Configuración Nginx#
Analicemos por qué hemos tomado estas decisiones de diseño, ya que copiar y pegar sin comprender puede llevar a una API frágil.
A. Rate Limiting (Protección de Cómputo)#
A diferencia de una base de datos tradicional, cada petición POST a un modelo fundacional consume una cantidad significativa de TFLOPs y memoria VRAM. Un ataque de denegación de servicio, o incluso un simple bug en un bucle de tu aplicación frontend, puede bloquear tu GPU entera.
Utilizamos el módulo ngx_http_limit_req_module. En el bloque http definimos la memoria para rastrear IPs: limit_req_zone $binary_remote_addr zone=ai_limit:10m rate=1r/s;.
Luego, en el bloque location, lo aplicamos: limit_req zone=ai_limit burst=5 nodelay;.
El parámetro burst=5 significa que Nginx absorberá hasta 5 peticiones concurrentes de la misma IP antes de empezar a rechazar, asumiendo que el usuario está realizando múltiples llamadas en paralelo intencionadamente. El parámetro nodelay indica a Nginx que si la cola supera el límite de ráfaga, debe devolver inmediatamente un error 503 Service Unavailable en lugar de encolar las peticiones y dejar la conexión HTTP abierta, lo cual saturaría los sockets de Nginx.
B. Cross-Origin Resource Sharing (CORS)#
Si tu modelo de IA va a ser consumido directamente por un navegador web (por ejemplo, una aplicación React o Vue), el navegador realizará una petición OPTIONS (preflight) antes de enviar el POST con el payload.
Los servidores de inferencia puros como Ollama o llama.cpp a menudo tienen un soporte deficiente o nulo para manejar preflights. Al delegar esto a Nginx, garantizamos que el servidor intercepta el OPTIONS y devuelve un código 204 No Content inmediatamente, con las cabeceras Access-Control-Allow-* requeridas.
Nota de Seguridad: En la configuración se utiliza 'https://tu-app-frontend.com'. Es imperativo que evites usar * en producción. Permitir que cualquier origen lance peticiones a tu GPU consumirá tus recursos en beneficio de dominios de terceros.
C. Control de Timeouts#
Por defecto, Nginx asume que si el servidor backend no ha respondido en 60 segundos, ha ocurrido un error y cierra la conexión devolviendo un 504 Gateway Timeout.
Generar un bloque de código o un ensayo largo con un LLM puede tardar fácilmente entre 1 y 3 minutos dependiendo del hardware subyacente. Si no ajustas el timeout, la GPU terminará el trabajo, pero el cliente ya habrá recibido el error 504. La directiva proxy_read_timeout 300s; le da a tu contenedor de IA 5 minutos de margen para completar la inferencia antes de abortar.
D. Desactivar el Buffering (El Secreto del Streaming)#
La experiencia de usuario en la IA generativa depende en gran medida del “efecto máquina de escribir” (streaming de tokens). Técnicamente, esto se implementa utilizando Server-Sent Events (SSE).
Nginx, por eficiencia, tiende a almacenar la respuesta del backend en un búfer de memoria hasta que está completa y luego se la envía al cliente de golpe. Esto rompe completamente el streaming de la IA. El cliente se quedará esperando 30 segundos viendo un spinner de carga, y luego recibirá todo el texto de golpe.
Para evitarlo, son cruciales estas directivas:
proxy_buffering off;proxy_cache off;chunked_transfer_encoding on;Esto fuerza a Nginx a hacer de tubería directa: en cuanto el contenedor de IA emite un token (un chunk de bytes), Nginx lo transfiere inmediatamente al cliente a través del socket TCP abierto.
Paso 3: SSL y Despliegue#
La configuración Nginx asume la existencia de certificados TLS (fullchain.pem y privkey.pem). Nunca expongas tu API utilizando tráfico HTTP plano, especialmente porque probablemente envíes tokens de autorización (API Keys) en las cabeceras de la petición.
Puedes generar estos certificados gratuitamente en el propio VPS utilizando Certbot (Let’s Encrypt) antes de levantar los contenedores:
sudo apt-get install certbotsudo certbot certonly --standalone -d api.tu-dominio.comUna vez que tengas los certificados en /etc/letsencrypt/live/api.tu-dominio.com/, asegúrate de copiarlos o montar los volúmenes correspondientes hacia el directorio ./certs referenciado en tu docker-compose.yml.
Finalmente, despliega el sistema ejecutando:
docker compose up -dPuedes revisar los logs de tu proxy inverso para comprobar que el tráfico fluye correctamente:
docker compose logs -f nginx_proxyPruebas de Estrés y Verificación#
Con el entorno desplegado, es momento de validar la infraestructura.
Prueba 1: Verificación de Streaming
Lanza una petición CURL a tu dominio, solicitando una respuesta mediante streaming. Deberías ver cómo los datos caen en tu terminal poco a poco, demostrando que proxy_buffering off; está funcionando.
curl -X POST [https://api.tu-dominio.com/v1/completions](https://api.tu-dominio.com/v1/completions) \ -H "Content-Type: application/json" \ -d '{ "model": "meta-llama/Meta-Llama-3-8B-Instruct", "prompt": "Escribe un ensayo detallado sobre la ingeniería de software.", "stream": true, "max_tokens": 1000 }'Prueba 2: Verificación de Rate Limiting Si ejecutas un bucle rápido para saturar el servidor, Nginx debería detenerlo.
for i in {1..10}; do curl -s -o /dev/null -w "%{http_code}\n" [https://api.tu-dominio.com/v1/models](https://api.tu-dominio.com/v1/models); doneDeberías recibir códigos HTTP 200 para las primeras peticiones (dependiendo de la ráfaga permitida) y luego inmediatamente códigos 503 (Service Unavailable), protegiendo exitosamente tu GPU.
Consideraciones Finales#
Encapsular un servidor de Inteligencia Artificial detrás de Nginx y Docker es el estándar de la industria para despliegues on-premise o VPS propios.
Mientras el contenedor de Docker garantiza la reproducibilidad del entorno de inferencia con sus respectivas dependencias binarias (CUDA, tensores), Nginx proporciona la resiliencia a nivel de red, gestionando la concurrencia, la seguridad de orígenes cruzados y protegiendo el hardware de cómputo del abuso sin sacrificar el comportamiento en tiempo real de los Server-Sent Events. Tu modelo está ahora blindado y listo para integrarse en producción.