Despliegue de pila completa de IA con Docker Compose: orquestación con un clic desde LLM hasta base de datos vectorial
¿Configurar un entorno de desarrollo de IA sigue tardando tres días?
En 2026, la configuración del entorno de desarrollo de IA sigue siendo una pesadilla para los desarrolladores. Necesitas instalar Ollama para el servicio LLM, configurar Qdrant para almacenamiento vectorial, configurar servicios de embedding, construir una puerta de enlace API para autenticación y gestionar controladores GPU, versiones de CUDA y descargas de modelos... Tres días desaparecidos, y no has escrito una sola línea de código.
El despliegue de pila completa de IA con Docker Compose orquesta todo en un solo archivo. docker compose up -d lanza toda la pila de IA en minutos. Este artículo es una guía práctica completa que cubre 7 patrones principales, 5 errores comunes, 10 pasos de solución de problemas y estrategias de endurecimiento para producción.
Puntos clave
- Despliegue de pila completa de IA con Docker Compose = LLM + Base de datos vectorial + Embedding + Puerta de enlace API + Monitoreo, un archivo para gobernarlos a todos
- Ollama + OpenWebUI es la solución de servicio LLM local más madura
- Qdrant/Milvus son las bases de datos vectoriales de referencia con despliegue Docker ultrasencillo
- El paso de GPU es crítico para el despliegue de IA — configúralo mediante
deploy.resources.reservations.devices - La producción requiere autenticación, limitación de tasa, monitoreo y endurecimiento de respaldos
Tabla de contenido
- Visión general de la arquitectura de pila completa de IA
- Patrón 1: Servicio LLM con Ollama + OpenWebUI
- Patrón 2: Bases de datos vectoriales Qdrant/Milvus
- Patrón 3: Servicios de embedding y gestión de modelos
- Patrón 4: Puerta de enlace API y autenticación
- Patrón 5: Paso de GPU y límites de recursos
- Patrón 6: Monitoreo y observabilidad
- Patrón 7: Endurecimiento para producción y seguridad
- 5 errores comunes y soluciones
- 10 soluciones a errores comunes
- Consejos avanzados de optimización
- Comparación: Docker Compose vs K8s vs Docker Swarm
- Herramientas en línea recomendadas
- Resumen
Visión general de la arquitectura de pila completa de IA
El despliegue de pila completa de IA con Docker Compose se basa en una arquitectura de 7 capas, desde la GPU en la parte inferior hasta la puerta de enlace API en la parte superior:
┌─────────────────────────────────────────────────────┐
│ API Gateway │
│ (Traefik / Nginx) │
│ Auth · Rate Limit · Routing · TLS │
├──────────┬──────────┬──────────┬────────────────────┤
│ OpenWebUI│ RAG App │ Agent │ Admin Panel │
│ (Chat) │ (Search) │ (Proxy) │ (Management) │
├──────────┴──────────┴──────────┴────────────────────┤
│ Embedding Service │
│ (TEI / Infinity / FastEmbed) │
├──────────────────┬──────────────────────────────────┤
│ Ollama LLM │ vLLM / TGI │
│ (Model Serving) │ (High-Perf Inference) │
├──────────────────┴──────────────────────────────────┤
│ Vector Database │
│ (Qdrant / Milvus / Weaviate) │
├─────────────────────────────────────────────────────┤
│ Infrastructure │
│ Redis · PostgreSQL · MinIO · Prometheus │
├─────────────────────────────────────────────────────┤
│ GPU / CPU Runtime │
│ NVIDIA CUDA · ROCm · CPU Fallback │
└─────────────────────────────────────────────────────┘
Estructura de directorios del proyecto para el despliegue de pila completa de IA con Docker Compose:
ai-stack/
├── docker-compose.yml
├── docker-compose.gpu.yml
├── docker-compose.prod.yml
├── .env
├── ollama/
│ └── Modelfile
├── qdrant/
│ └── config.yaml
├── traefik/
│ ├── traefik.yml
│ └── acme.json
├── monitoring/
│ ├── prometheus.yml
│ └── grafana/
│ └── dashboards/
└── scripts/
├── init-models.sh
└── backup-vectors.sh
Patrón 1: Servicio LLM con Ollama + OpenWebUI
Ollama es la solución de servicio LLM local más madura en 2026, compatible con Llama 4, Qwen 3, DeepSeek V3 y otros modelos principales. OpenWebUI proporciona una interfaz web estilo ChatGPT.
Configuración básica
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
environment:
OLLAMA_KEEP_ALIVE: "24h"
OLLAMA_NUM_PARALLEL: "4"
OLLAMA_MAX_LOADED_MODELS: "3"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
restart: unless-stopped
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
ports:
- "3000:8080"
volumes:
- open_webui_data:/app/backend/data
environment:
OLLAMA_BASE_URL: "http://ollama:11434"
WEBUI_SECRET_KEY: "${WEBUI_SECRET_KEY}"
ENABLE_SIGNUP: "false"
DEFAULT_USER_ROLE: "user"
depends_on:
ollama:
condition: service_healthy
restart: unless-stopped
volumes:
ollama_data:
open_webui_data:
Descarga automática de modelos
Después de iniciar Ollama, necesitas descargar modelos manualmente. Automatiza esto con un script de inicialización:
#!/bin/bash
# scripts/init-models.sh
MODELS=(
"qwen3:8b"
"llama4:8b"
"deepseek-v3:8b"
"nomic-embed-text"
)
for model in "${MODELS[@]}"; do
echo "Pulling model: $model"
until curl -s http://localhost:11434/api/pull -d "{\"name\":\"$model\"}" | grep -q "success"; do
echo " Retrying $model..."
sleep 5
done
echo " ✓ $model ready"
done
echo "All models pulled successfully!"
Agrega el servicio de inicialización a Docker Compose:
model-init:
image: curlimages/curl:latest
container_name: model-init
depends_on:
ollama:
condition: service_healthy
volumes:
- ./scripts/init-models.sh:/init-models.sh:ro
entrypoint: ["/bin/sh", "/init-models.sh"]
restart: "no"
Modelfile personalizado
# ollama/Modelfile
FROM qwen3:8b
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER num_ctx 8192
PARAMETER stop "<|im_end|>"
SYSTEM """
You are a professional AI assistant. When answering questions:
1. Give a concise conclusion first
2. Then provide detailed explanation
3. If uncertain, say so explicitly
"""
Construye un modelo personalizado:
docker exec ollama ollama create my-assistant -f /root/.ollama/Modelfile
Patrón 2: Bases de datos vectoriales Qdrant/Milvus
Las bases de datos vectoriales son el núcleo de la arquitectura RAG. El despliegue de pila completa de IA con Docker Compose generalmente usa Qdrant (ligero) o Milvus (gran escala).
Configuración de Qdrant (Recomendado para proyectos pequeño-medianos)
qdrant:
image: qdrant/qdrant:latest
container_name: qdrant
ports:
- "6333:6333"
- "6334:6334"
volumes:
- qdrant_data:/qdrant/storage
- ./qdrant/config.yaml:/qdrant/config/production.yaml:ro
environment:
QDRANT__SERVICE__GRPC_PORT: "6334"
QDRANT__LOG_LEVEL: "INFO"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:6333/healthz"]
interval: 10s
timeout: 5s
retries: 3
restart: unless-stopped
Archivo de configuración de Qdrant:
# qdrant/config.yaml
storage:
performance:
max_search_threads: 4
wal:
wal_capacity_mb: 32
wal_segments_ahead: 0
optimizers:
indexing_threshold: 20000
memmap_threshold: 50000
service:
max_request_size_mb: 64
enable_cors: true
telemetry_disabled: true
Configuración de Milvus (Recomendado para proyectos a gran escala)
etcd:
image: quay.io/coreos/etcd:v3.5.16
container_name: milvus-etcd
environment:
ETCD_AUTO_COMPACTION_MODE: "revision"
ETCD_AUTO_COMPACTION_RETENTION: "1000"
ETCD_QUOTA_BACKEND_BYTES: "4294967296"
volumes:
- etcd_data:/etcd
command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379
restart: unless-stopped
minio:
image: minio/minio:latest
container_name: milvus-minio
environment:
MINIO_ACCESS_KEY: "${MINIO_ACCESS_KEY}"
MINIO_SECRET_KEY: "${MINIO_SECRET_KEY}"
ports:
- "9001:9001"
- "9000:9000"
volumes:
- minio_data:/minio_data
command: minio server /minio_data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
restart: unless-stopped
milvus:
image: milvusdb/milvus:v2.5-latest
container_name: milvus
ports:
- "19530:19530"
- "9091:9091"
volumes:
- milvus_data:/var/lib/milvus
environment:
ETCD_ENDPOINTS: "etcd:2379"
MINIO_ADDRESS: "minio:9000"
depends_on:
- etcd
- minio
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
interval: 30s
timeout: 20s
retries: 3
start_period: 90s
restart: unless-stopped
Comparación de bases de datos vectoriales
| Característica | Qdrant | Milvus | Weaviate | ChromaDB |
|---|---|---|---|---|
| Complejidad de despliegue | Muy baja (1 contenedor) | Alta (3+ contenedores) | Baja (1 contenedor) | Muy baja (1 contenedor) |
| Rendimiento (Millones) | Excelente | Excelente | Bueno | Regular |
| Rendimiento (Miles de millones) | Bueno | Excelente | Regular | N/A |
| Búsqueda filtrada | ✅ Potente | ✅ Potente | ✅ Buena | ⚠️ Básica |
| Persistencia | ✅ | ✅ | ✅ | ⚠️ En memoria por defecto |
| Multi-réplica | ✅ | ✅ | ✅ | ❌ |
| Soporte gRPC | ✅ | ✅ | ❌ | ❌ |
| Compatibilidad con Docker Compose | ✅ Mejor | ⚠️ Pesado | ✅ Bueno | ✅ Solo desarrollo |
| Listo para producción | ✅ | ✅ | ✅ | ❌ Solo desarrollo |
Recomendación: Para el despliegue de pila completa de IA con Docker Compose, Qdrant es la primera opción — despliegue simple, rendimiento excelente. Considera Milvus cuando el conteo de vectores supere los 100 millones. ChromaDB solo es adecuado para prototipado.
Patrón 3: Servicios de embedding y gestión de modelos
Los servicios de embedding convierten texto en vectores, un paso crítico en el pipeline RAG. La orquestación de contenedores Docker Compose AI ofrece tres soluciones principales.
Hugging Face TEI (Recomendado)
tei:
image: ghcr.io/huggingface/text-embeddings-inference:latest
container_name: tei
ports:
- "8080:80"
volumes:
- tei_cache:/data
environment:
MODEL_ID: "BAAI/bge-m3"
REVISION: "main"
MAX_BATCH_TOKENS: "16384"
MAX_CLIENT_BATCH_SIZE: "32"
HF_TOKEN: "${HF_TOKEN}"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 120s
restart: unless-stopped
Servicio de embedding Infinity
infinity:
image: michaelf34/infinity:latest
container_name: infinity
ports:
- "7997:7997"
volumes:
- infinity_cache:/app/.cache
environment:
MODEL_ID: "BAAI/bge-m3"
ENGINE: "optimum"
BATCH_SIZE: "32"
command: >
--model-id BAAI/bge-m3
--engine optimum
--port 7997
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:7997/health"]
interval: 10s
timeout: 5s
retries: 3
restart: unless-stopped
Ejemplo de uso del servicio de embedding
import httpx
import numpy as np
async def get_embeddings(texts: list[str]) -> list[list[float]]:
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
"http://tei:80/embed",
json={"inputs": texts}
)
response.raise_for_status()
return response.json()
async def search_similar(query: str, top_k: int = 5) -> list[dict]:
query_embedding = await get_embeddings([query])
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
"http://qdrant:6333/collections/documents/points/search",
json={
"vector": query_embedding[0],
"limit": top_k,
"with_payload": True
}
)
response.raise_for_status()
return response.json()["result"]
Comparación de servicios de embedding
| Característica | TEI | Infinity | FastEmbed |
|---|---|---|---|
| Aceleración GPU | ✅ Nativa | ✅ Nativa | ❌ Solo CPU |
| Inferencia por lotes | ✅ Eficiente | ✅ Eficiente | ⚠️ Regular |
| Multi-modelo | ✅ | ✅ | ✅ |
| Tamaño de imagen Docker | ~2GB | ~4GB | ~500MB |
| Listo para producción | ✅ | ✅ | ⚠️ Solo desarrollo |
| API compatible con OpenAI | ✅ | ✅ | ❌ |
Patrón 4: Puerta de enlace API y autenticación
El despliegue de pila completa de IA con Docker Compose en producción requiere una puerta de enlace API para autenticación unificada, limitación de tasa y enrutamiento.
Configuración de Traefik
traefik:
image: traefik:v3.2
container_name: traefik
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- traefik_certs:/etc/traefik/certs
- ./traefik/dynamic:/etc/traefik/dynamic:ro
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.file.directory=/etc/traefik/dynamic"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
labels:
traefik.enable: "true"
traefik.http.routers.traefik.rule: "Host(`traefik.ai-stack.local`)"
traefik.http.routers.traefik.entrypoints: "websecure"
traefik.http.routers.traefik.tls: "true"
traefik.http.services.traefik.loadbalancer.server.port: "8080"
restart: unless-stopped
OpenWebUI con Traefik
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
volumes:
- open_webui_data:/app/backend/data
environment:
OLLAMA_BASE_URL: "http://ollama:11434"
labels:
traefik.enable: "true"
traefik.http.routers.webui.rule: "Host(`chat.ai-stack.local`)"
traefik.http.routers.webui.entrypoints: "websecure"
traefik.http.routers.webui.tls: "true"
traefik.http.services.webui.loadbalancer.server.port: "8080"
depends_on:
ollama:
condition: service_healthy
restart: unless-stopped
Middleware de autenticación
# traefik/dynamic/auth.yml
http:
middlewares:
auth-middleware:
forwardAuth:
address: "http://auth-service:8000/verify"
trustForwardHeader: true
authResponseHeaders:
- "X-User-Id"
- "X-User-Role"
rate-limit:
rateLimit:
average: 30
burst: 60
period: 1m
routers:
api-router:
rule: "Host(`api.ai-stack.local`)"
entrypoints:
- "websecure"
tls: true
middlewares:
- "auth-middleware"
- "rate-limit"
service: "ollama-api"
Patrón 5: Paso de GPU y límites de recursos
La GPU es el núcleo del despliegue de IA. El despliegue de pila completa de IA con Docker Compose habilita el paso de GPU mediante deploy.resources.reservations.devices.
Paso de GPU NVIDIA
ollama:
image: ollama/ollama:latest
container_name: ollama
volumes:
- ollama_data:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
limits:
memory: 16G
cpus: "8.0"
environment:
NVIDIA_VISIBLE_DEVICES: "all"
NVIDIA_DRIVER_CAPABILITIES: "compute,utility"
OLLAMA_KEEP_ALIVE: "24h"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
Asignación multi-GPU
ollama:
image: ollama/ollama:latest
deploy:
resources:
reservations:
devices:
- driver: nvidia
device_ids: ["0"]
capabilities: [gpu]
environment:
CUDA_VISIBLE_DEVICES: "0"
tei:
image: ghcr.io/huggingface/text-embeddings-inference:latest
deploy:
resources:
reservations:
devices:
- driver: nvidia
device_ids: ["1"]
capabilities: [gpu]
environment:
CUDA_VISIBLE_DEVICES: "1"
Configuración de respaldo a CPU
ollama-cpu:
image: ollama/ollama:latest
profiles: ["cpu-only"]
container_name: ollama
volumes:
- ollama_data:/root/.ollama
environment:
OLLAMA_NUM_PARALLEL: "2"
OLLAMA_MAX_LOADED_MODELS: "1"
deploy:
resources:
limits:
memory: 8G
cpus: "4.0"
ollama-gpu:
image: ollama/ollama:latest
profiles: ["gpu"]
container_name: ollama
volumes:
- ollama_data:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
limits:
memory: 16G
Comandos de inicio:
# Modo GPU
docker compose --profile gpu up -d
# Modo CPU
docker compose --profile cpu-only up -d
Script de monitoreo de recursos GPU
import subprocess
import json
import time
def monitor_gpu_usage(interval: int = 60):
while True:
result = subprocess.run(
["nvidia-smi", "--query-gpu=index,name,memory.used,memory.total,utilization.gpu",
"--format=csv,noheader,nounits"],
capture_output=True, text=True
)
for line in result.stdout.strip().split("\n"):
idx, name, mem_used, mem_total, util = line.split(", ")
print(f"GPU {idx} ({name}): {mem_used}/{mem_total}MB, Util: {util}%")
time.sleep(interval)
if __name__ == "__main__":
monitor_gpu_usage()
Patrón 6: Monitoreo y observabilidad
El monitoreo del despliegue de pila completa de IA con Docker Compose debe cubrir la utilización de GPU, la latencia de inferencia, el rendimiento de la base de datos vectorial y otras métricas específicas de IA.
Prometheus + Grafana
prometheus:
image: prom/prometheus:v3.2.0
container_name: prometheus
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.retention.time=30d"
- "--storage.tsdb.retention.size=10GB"
ports:
- "9090:9090"
restart: unless-stopped
grafana:
image: grafana/grafana:11.5.0
container_name: grafana
volumes:
- grafana_data:/var/lib/grafana
- ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro
- ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources:ro
environment:
GF_SECURITY_ADMIN_USER: "${GRAFANA_USER}"
GF_SECURITY_ADMIN_PASSWORD: "${GRAFANA_PASSWORD}"
GF_USERS_ALLOW_SIGN_UP: "false"
ports:
- "3001:3000"
depends_on:
- prometheus
restart: unless-stopped
dcgm-exporter:
image: nvidia/dcgm-exporter:latest
container_name: dcgm-exporter
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
ports:
- "9400:9400"
restart: unless-stopped
Configuración de Prometheus
# monitoring/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: "ollama"
static_configs:
- targets: ["ollama:11434"]
metrics_path: "/metrics"
scrape_interval: 30s
- job_name: "qdrant"
static_configs:
- targets: ["qdrant:6333"]
metrics_path: "/metrics"
scrape_interval: 30s
- job_name: "dcgm"
static_configs:
- targets: ["dcgm-exporter:9400"]
scrape_interval: 10s
- job_name: "node-exporter"
static_configs:
- targets: ["node-exporter:9100"]
- job_name: "traefik"
static_configs:
- targets: ["traefik:8080"]
Reglas de alerta clave
# monitoring/alerts.yml
groups:
- name: ai-stack
rules:
- alert: OllamaHighLatency
expr: histogram_quantile(0.95, rate(ollama_request_duration_seconds_bucket[5m])) > 30
for: 5m
labels:
severity: warning
annotations:
summary: "Latencia de inferencia de Ollama demasiado alta"
- alert: GPUMemoryHigh
expr: DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_TOTAL > 0.9
for: 5m
labels:
severity: critical
annotations:
summary: "El uso de memoria GPU supera el 90%"
- alert: QdrantHighLatency
expr: histogram_quantile(0.95, rate(qdrant_request_duration_seconds_bucket[5m])) > 5
for: 5m
labels:
severity: warning
annotations:
summary: "Latencia de consulta de Qdrant demasiado alta"
- alert: OllamaContainerDown
expr: up{job="ollama"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "Servicio Ollama no disponible"
Patrón 7: Endurecimiento para producción y seguridad
La seguridad es la línea base para el despliegue de pila completa de IA con Docker Compose en producción.
Gestión de secretos
services:
ollama:
image: ollama/ollama:latest
secrets:
- hf_token
environment:
HF_TOKEN_FILE: /run/secrets/hf_token
qdrant:
image: qdrant/qdrant:latest
secrets:
- qdrant_api_key
environment:
QDRANT__SERVICE__API_KEY_FILE: /run/secrets/qdrant_api_key
secrets:
hf_token:
file: ./secrets/hf_token.txt
qdrant_api_key:
file: ./secrets/qdrant_api_key.txt
db_password:
file: ./secrets/db_password.txt
Aislamiento de red
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
monitoring:
driver: bridge
internal: true
services:
traefik:
networks:
- frontend
- backend
open-webui:
networks:
- frontend
- backend
ollama:
networks:
- backend
qdrant:
networks:
- backend
tei:
networks:
- backend
prometheus:
networks:
- monitoring
- backend
grafana:
networks:
- frontend
- monitoring
Estrategia de respaldo
#!/bin/bash
# scripts/backup-vectors.sh
BACKUP_DIR="/backups/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"
echo "Backing up Qdrant..."
curl -s -X POST "http://localhost:6333/snapshots" | jq .
echo "Backing up Ollama models list..."
curl -s "http://localhost:11434/api/tags" | jq . > "$BACKUP_DIR/ollama_models.json"
echo "Backing up environment config..."
cp .env "$BACKUP_DIR/.env.backup"
cp docker-compose.yml "$BACKUP_DIR/docker-compose.yml.backup"
echo "Backup completed: $BACKUP_DIR"
Configuración completa de producción
# docker-compose.prod.yml
services:
ollama:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
limits:
memory: 16G
cpus: "8.0"
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 5
window: 120s
logging:
driver: json-file
options:
max-size: "100m"
max-file: "5"
read_only: true
tmpfs:
- /tmp
qdrant:
deploy:
resources:
limits:
memory: 4G
cpus: "2.0"
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: json-file
options:
max-size: "50m"
max-file: "3"
5 errores comunes y soluciones
Error 1: Tiempo de espera agotado al descargar modelos de Ollama
Síntoma: Después de docker compose up, Ollama se queda atascado descargando modelos. Los modelos grandes (ej., Llama 4 70B) pueden tardar más de una hora en descargarse.
Solución: Usa el servicio model-init para descarga asíncrona. El servicio Ollama en sí no necesita esperar por los modelos.
model-init:
image: curlimages/curl:latest
depends_on:
ollama:
condition: service_healthy
volumes:
- ./scripts/init-models.sh:/init-models.sh:ro
entrypoint: ["/bin/sh", "/init-models.sh"]
restart: "no"
deploy:
resources:
limits:
memory: 256M
Error 2: OOM del contenedor Qdrant
Síntoma: A medida que crecen los datos vectoriales, el contenedor Qdrant sufre OOM Killed.
Solución: Establece límites de memoria y habilita el mapeo de memoria.
qdrant:
image: qdrant/qdrant:latest
deploy:
resources:
limits:
memory: 8G
environment:
QDRANT__STORAGE__PERFORMANCE__MAX_SEARCH_THREADS: "4"
QDRANT__STORAGE__WAL__WAL_CAPACITY_MB: "64"
Error 3: Incompatibilidad de versión del controlador GPU
Síntoma: docker compose up muestra el error CUDA driver version is insufficient.
Solución: Asegúrate de que el controlador NVIDIA del host sea ≥ 535, instala nvidia-container-toolkit.
# Verificar versión del controlador
nvidia-smi | head -3
# Instalar nvidia-container-toolkit (Ubuntu)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
Error 4: Servicio de embedding y LLM compitiendo por la GPU
Síntoma: Memoria GPU insuficiente cuando TEI y Ollama se ejecutan simultáneamente; la carga del modelo falla.
Solución: Usa device_ids para asignación precisa de GPU, o ejecuta el servicio de embedding en CPU.
ollama:
deploy:
resources:
reservations:
devices:
- driver: nvidia
device_ids: ["0"]
capabilities: [gpu]
tei:
# Ejecutar servicio de embedding en modo CPU
environment:
MODEL_ID: "BAAI/bge-m3"
# Sin asignación de GPU, usa CPU
Error 5: Fallo de resolución DNS entre contenedores
Síntoma: OpenWebUI informa ollama: Name or service not known.
Solución: Asegúrate de que todos los servicios estén en la misma red, usa container_name o el nombre del servicio como nombre de host.
networks:
ai-network:
driver: bridge
services:
ollama:
container_name: ollama
networks:
- ai-network
open-webui:
container_name: open-webui
networks:
- ai-network
environment:
OLLAMA_BASE_URL: "http://ollama:11434"
10 soluciones a errores comunes
1. could not select device driver — Runtime NVIDIA no instalado
# Instalar nvidia-container-toolkit y reiniciar Docker
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
docker info | grep -i runtime
# Deberías ver el runtime nvidia
2. OOM Killed — Memoria GPU insuficiente
# Verificar memoria GPU
nvidia-smi
# Usar modelos más pequeños o cuantizados
docker exec ollama ollama run qwen3:4b
3. Connection refused a Ollama — Servicio no listo
# Verificar estado de salud de Ollama
docker compose ps
docker compose logs ollama
# Esperar a que pase el healthcheck antes de conectar
4. permission denied en el socket Docker
sudo usermod -aG docker $USER
newgrp docker
5. Qdrant collection not found — Colección no creada
curl -X PUT "http://localhost:6333/collections/documents" \
-H "Content-Type: application/json" \
-d '{"vectors": {"size": 1024, "distance": "Cosine"}}'
6. model not found — Modelo de Ollama no descargado
docker exec ollama ollama pull qwen3:8b
7. CUDA out of memory — Desbordamiento de memoria GPU durante inferencia
# Reducir solicitudes paralelas
# Configurar en docker-compose.yml
environment:
OLLAMA_NUM_PARALLEL: "1"
OLLAMA_MAX_LOADED_MODELS: "1"
8. TLS handshake error — Problema de certificado Traefik
# Verificar permisos del archivo de certificado
chmod 600 traefik/acme.json
# Verificar logs de Traefik
docker compose logs traefik
9. too many open files — Límite de descriptores de archivo
# Aumentar límite temporalmente
ulimit -n 65536
# Configuración permanente (/etc/security/limits.conf)
# * soft nofile 65536
# * hard nofile 65536
10. vector dimension mismatch — Inconsistencia de dimensiones de embedding
# Asegurar que la dimensión de la colección Qdrant coincida con la salida del modelo de embedding
# bge-m3: 1024 dimensiones
# nomic-embed-text: 768 dimensiones
curl -X PUT "http://localhost:6333/collections/documents" \
-d '{"vectors": {"size": 1024, "distance": "Cosine"}}'
Consejos avanzados de optimización
Calentamiento de modelos en múltiples etapas
model-warmer:
image: curlimages/curl:latest
container_name: model-warmer
depends_on:
ollama:
condition: service_healthy
entrypoint: >
/bin/sh -c "
echo 'Warming up models...' &&
curl -s http://ollama:11434/api/generate -d '{\"model\":\"qwen3:8b\",\"prompt\":\"hi\",\"stream\":false}' > /dev/null &&
curl -s http://ollama:11434/api/generate -d '{\"model\":\"nomic-embed-text\",\"prompt\":\"test\",\"stream\":false}' > /dev/null &&
echo 'Models warmed up!'
"
restart: "no"
Descarga inteligente de modelos
ollama:
environment:
OLLAMA_KEEP_ALIVE: "5m"
OLLAMA_NUM_PARALLEL: "4"
OLLAMA_MAX_LOADED_MODELS: "2"
OLLAMA_KEEP_ALIVE: "5m" descarga automáticamente modelos inactivos durante 5 minutos, liberando memoria GPU.
Dependencias encadenadas de verificaciones de salud
tei:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 120s
qdrant:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:6333/healthz"]
interval: 10s
timeout: 5s
retries: 3
rag-app:
depends_on:
ollama:
condition: service_healthy
tei:
condition: service_healthy
qdrant:
condition: service_healthy
Docker Compose Watch para desarrollo
# docker-compose.yml
services:
rag-app:
build: .
develop:
watch:
- action: rebuild
path: ./app
target: /app
- action: sync
path: ./app/static
target: /app/static
Comparación: Docker Compose vs K8s vs Docker Swarm
| Dimensión | Docker Compose | Kubernetes | Docker Swarm |
|---|---|---|---|
| Complejidad de despliegue de pila IA | ⭐ Muy baja | ⭐⭐⭐⭐⭐ Muy alta | ⭐⭐ Baja |
| Programación de GPU | ✅ Nativa | ✅ Device Plugin | ⚠️ Necesita configuración |
| Auto-escalado | ❌ | ✅ HPA | ⚠️ Manual |
| Descubrimiento de servicios | ✅ DNS | ✅ CoreDNS | ✅ DNS |
| Actualizaciones continuas | ⚠️ Necesita scripts | ✅ Nativo | ✅ Nativo |
| Gestión de configuración | ✅ .env | ✅ ConfigMap | ⚠️ Config |
| Gestión de secretos | ✅ Docker Secret | ✅ K8s Secret | ⚠️ Básica |
| Ecosistema de monitoreo | ✅ Prometheus | ✅ Completo | ⚠️ Limitado |
| Orquestación multi-nodo | ❌ Nodo único | ✅ Capacidad central | ✅ Nativo |
| Curva de aprendizaje | Baja | Alta | Baja |
| Actividad de la comunidad | ✅ Activa | ✅ Muy activa | ❌ En declive |
| Escala de proyecto IA adecuada | 1-5 GPUs | 10+ GPUs | 2-5 GPUs |
Recomendación: El despliegue de pila completa de IA con Docker Compose es ideal para escenarios de máquina única con 1-5 GPUs — la mejor opción para desarrollo de IA y producción a pequeña escala. Para 5+ GPUs o necesidades multi-nodo, considera Kubernetes + KServe/vLLM. Docker Swarm no se recomienda para despliegue de IA.
Herramientas en línea recomendadas
- Formateador JSON - Formatea datos JSON de Docker Compose y respuestas de API
- Codificación Base64 - Codifica configuraciones de Secrets y API Key
- cURL a Código - Convierte comandos cURL de Qdrant/Ollama a código Python/JS
Resumen
El despliegue de pila completa de IA con Docker Compose transforma los entornos de desarrollo de IA de "tres días para configurar" a "un comando para lanzar". Ollama + OpenWebUI gestiona el servicio LLM, Qdrant gestiona el almacenamiento vectorial, TEI gestiona los embeddings, Traefik gestiona la puerta de enlace, Prometheus + Grafana gestiona el monitoreo, y el paso de GPU hace volar la inferencia. 7 patrones cubren toda la cadena desde el desarrollo hasta la producción, 5 errores comunes y 10 pasos de solución de problemas te ayudan a evitar desvíos. Para proyectos de IA con 1-5 GPUs, Docker Compose es la solución de orquestación de contenedores de IA más práctica en 2026.
Artículos relacionados
- Despliegue en producción con Docker Compose - 7 estrategias de producción desde verificaciones de salud hasta actualizaciones sin tiempo de inactividad
- Guía de despliegue en producción de IA con Python - Mejores prácticas para el despliegue en producción de modelos de IA en Python
- Guía de endurecimiento de seguridad Docker - Endurecimiento de seguridad de contenedores y protección contra vulnerabilidades
Referencias externas
- Documentación oficial de Ollama - Documentación completa del servicio de modelos Ollama
- Documentación oficial de Qdrant - Guía de despliegue y optimización de base de datos vectorial
Prueba estas herramientas que se ejecutan en tu navegador — no requieren registro →