En esta guía documento la instalación de mi stack de IA llamado Cloria AI Stack, basado en Docker y Docker Compose. El objetivo es tener, en una sola máquina, servicios como Ollama, Open WebUI, Flowise, n8n, una base vectorial (Qdrant), bases de datos PostgreSQL, un túnel de Cloudflare y la API de WhatsApp (WAHA).
El artículo está pensado para técnicos que ya se sienten cómodos en consola y con conceptos básicos de contenedores, pero que quieren tener un ejemplo concreto, real y documentado. Al final incluyo una sección específica para quienes quieran hacer lo mismo en una Mac.
1. Requisitos previos (Ubuntu)
- Ubuntu Server o Desktop (22.04 o similar).
- Usuario con permisos
sudo. - Conexión a internet estable.
- Algo de espacio en disco (idealmente SSD) para modelos y datos.
2. Instalación de Docker y Docker Compose en Ubuntu
Primero actualizamos los paquetes del sistema:
sudo apt update
sudo apt upgrade -y
Instalamos Docker desde los repositorios de Ubuntu:
sudo apt install -y docker.io
Habilitamos y arrancamos el servicio de Docker:
sudo systemctl enable docker
sudo systemctl start docker
Opcionalmente, añadimos nuestro usuario al grupo docker para no usar sudo en cada comando:
sudo usermod -aG docker $USER
# Cerrar sesión y volver a entrar para que tome efecto
Desde Docker 20.10, Docker Compose viene como plugin. Podemos instalarlo (en caso de no venir ya) con:
sudo apt install -y docker-compose-plugin
Comprobamos versiones:
docker --version
docker compose version
3. Estructura de carpetas para el proyecto
Elegimos una carpeta donde vivirá todo el stack. Por ejemplo:
mkdir -p ~/cloria-stack
cd ~/cloria-stack
# Carpeta para modelos externos (opcional)
mkdir -p models
# Carpeta para WAHA (WhatsApp API)
mkdir -p waha-data
En esta carpeta ~/cloria-stack colocaremos el archivo docker-compose.yml y un archivo .env para las contraseñas.
4. Archivo .env (para ofuscar contraseñas y tokens)
Antes de ver el docker-compose.yml, creamos un archivo .env en la misma carpeta. Ahí pondremos los datos sensibles. No subas este archivo a Git ni lo publiques.
cd ~/cloria-stack
nano .env
Ejemplo de contenido (usa tus propios valores):
# Cloudflare Tunnel
CLOUDFLARE_TUNNEL_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# PostgreSQL (Supabase y Cloria)
POSTGRES_PASSWORD=TuPassword-Segura-123
CLORIA_POSTGRES_PASSWORD=TuPassword-Segura-123
# Open WebUI secret
WEBUI_SECRET_KEY=clave_larga_hexadecimal_generada_con_openssl
La clave de WEBUI_SECRET_KEY la puedes generar con algo como:
openssl rand -hex 32
5. docker-compose.yml (Cloria AI Stack)
Este es el docker-compose.yml que utilizo actualmente, adaptado para usar variables de entorno y no exponer contraseñas reales.
# docker-compose.yml
# Proyecto: Cloria AI Stack
# Versión: 1.4
# Actualizado: 2025-12-05
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
restart: always
ports:
- "11434:11434"
volumes:
- ollama:/root/.ollama
- ./models:/models
environment:
- NVIDIA_VISIBLE_DEVICES=all
- OLLAMA_NUM_GPU=60 # Ajusta según tu GPU (en Mac puede omitirse)
- OLLAMA_KEEP_ALIVE=5m # Descarga modelos inactivos de VRAM
deploy:
resources:
reservations:
devices:
- capabilities: [gpu]
runtime: nvidia
networks:
- stack_gepeto
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
restart: unless-stopped
command: tunnel --no-autoupdate run
environment:
- TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
networks:
- stack_gepeto
waha:
image: devlikeapro/whatsapp-http-api:latest
container_name: waha
restart: unless-stopped
ports:
- "3010:3000"
- "3011:3001"
volumes:
- ./waha-data:/usr/src/app/data
networks:
- stack_gepeto
qdrant:
image: qdrant/qdrant
container_name: qdrant
restart: unless-stopped
ports:
- "6333:6333"
- "6334:6334"
volumes:
- qdrant-data:/qdrant/storage
networks:
- stack_gepeto
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
ports:
- "3000:8080"
volumes:
- openwebui-data:/app/backend/data
environment:
- OLLAMA_BASE_URL=http://ollama:11434
# Usar Qdrant como base vectorial (memoria de documentos/RAG)
- VECTOR_DB=qdrant
- QDRANT_URI=http://qdrant:6333
# Que las incrustaciones (embeddings) las haga Ollama
- RAG_EMBEDDING_ENGINE=ollama
- RAG_OLLAMA_BASE_URL=http://ollama:11434
- RAG_EMBEDDING_MODEL=nomic-embed-text:latest
# Secret fijo para sesiones (cargar desde .env)
- WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY}
depends_on:
- ollama
- qdrant
networks:
- stack_gepeto
flowise:
image: flowiseai/flowise
container_name: flowise
restart: unless-stopped
ports:
- "3001:3000"
environment:
- DATABASE_PATH=/root/.flowise
volumes:
- flowise-data:/root/.flowise
entrypoint: ["npx", "flowise", "start"]
networks:
- stack_gepeto
supabase-api:
image: postgrest/postgrest:latest
container_name: supabase-api
restart: unless-stopped
ports:
- "54325:3000"
networks:
- stack_gepeto
# Nota: aquí deberás añadir las variables de configuración de PostgREST
# para apuntar a supabase-db, esquema, JWT, etc.
supabase-db:
image: postgres:14
container_name: supabase-db
restart: unless-stopped
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=postgres
volumes:
- supabase-db-data:/var/lib/postgresql/data
networks:
- stack_gepeto
n8n:
image: n8nio/n8n:1.120.0
container_name: n8n
restart: unless-stopped
ports:
- "5678:5678"
volumes:
- n8n-data:/home/node/.n8n
networks:
- stack_gepeto
cloria-postgres:
image: postgres:16-alpine
container_name: cloria-postgres
restart: unless-stopped
environment:
- POSTGRES_DB=cloria
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${CLORIA_POSTGRES_PASSWORD}
ports:
- "5432:5432"
volumes:
- cloria-data:/var/lib/postgresql/data
networks:
- stack_gepeto
volumes:
ollama:
openwebui-data:
waha-data:
qdrant-data:
flowise-data:
supabase-db-data:
n8n-data:
cloria-data:
networks:
stack_gepeto:
6. Explicación servicio por servicio (para técnicos)
6.1. Servicio ollama
- Imagen:
ollama/ollama:latest– servidor de modelos de lenguaje locales. - Puerto 11434: expone la API HTTP de Ollama hacia el host.
- Volúmenes:
ollama:/root/.ollama– almacena modelos y configuración interna../models:/models– carpeta del host para gestionar modelos o respaldos externos.
- Variables de entorno:
NVIDIA_VISIBLE_DEVICES=allyruntime: nvidia– habilitan uso de GPU NVIDIA en Linux.OLLAMA_NUM_GPU– controla el uso de GPU (ajústalo o elimínalo según tu hardware).OLLAMA_KEEP_ALIVE– tiempo que se mantiene un modelo en VRAM sin uso antes de descargarse.
- Red: se conecta a
stack_gepetopara que otros servicios lo vean comohttp://ollama:11434.
6.2. Servicio cloudflared
- Imagen:
cloudflare/cloudflared:latest. - Comando:
tunnel --no-autoupdate run– levanta el túnel configurado. - TUNNEL_TOKEN: viene desde el
.envcomo${CLOUDFLARE_TUNNEL_TOKEN}.
Este token no debe publicarse ni subirse a repositorios. - Permite exponer servicios internos (Open WebUI, n8n, etc.) a través de un subdominio de Cloudflare sin abrir puertos en el router.
6.3. Servicio waha (WhatsApp HTTP API)
- Imagen:
devlikeapro/whatsapp-http-api:latest. - Puertos:
3010:3000– API principal.3011:3001– dashboard/web UI (según configuración).
- Volumen:
./waha-data:/usr/src/app/datapara persistir sesiones, QR, etc.
6.4. Servicio qdrant (base vectorial)
- Imagen:
qdrant/qdrant. - Puertos:
6333– API HTTP.6334– gRPC.
- Volumen:
qdrant-data:/qdrant/storagepara colecciones y vectores. - Open WebUI se conecta aquí para RAG y memoria de documentos.
6.5. Servicio open-webui
- Imagen:
ghcr.io/open-webui/open-webui:main. - Puertos:
3000:8080→ accedes conhttp://TU_IP:3000. - Volumen:
openwebui-data:/app/backend/data– usuarios, configuraciones, etc. - Variables de entorno clave:
OLLAMA_BASE_URL=http://ollama:11434– apunta al servicioollama.VECTOR_DB=qdrantyQDRANT_URI=http://qdrant:6333– integra Qdrant para RAG.RAG_EMBEDDING_ENGINE=ollamayRAG_OLLAMA_BASE_URL– embeddings generados por Ollama.RAG_EMBEDDING_MODEL=nomic-embed-text:latest– modelo de embeddings (puedes cambiarlo).WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY}– clave secreta para sesiones y seguridad.
6.6. Servicio flowise
- Imagen:
flowiseai/flowise. - Puerto:
3001:3000→ UI de Flowise enhttp://TU_IP:3001. - Volumen:
flowise-data:/root/.flowise– flujos, configuraciones, etc. - Entrypoint:
["npx", "flowise", "start"].
6.7. Servicios supabase-api y supabase-db
supabase-db:- PostgreSQL 14 con datos persistidos en
supabase-db-data. POSTGRES_PASSWORD=${POSTGRES_PASSWORD}– desde.env.
- PostgreSQL 14 con datos persistidos en
supabase-api:- Usa la imagen
postgrest/postgrest, que expone una API REST sobre PostgreSQL. - Pendiente configurar variables como
PGRST_DB_URI,PGRST_DB_SCHEMA, JWT, etc., según tu diseño.
- Usa la imagen
6.8. Servicio n8n
- Imagen:
n8nio/n8n:1.120.0. - Puerto:
5678:5678. - Volumen:
n8n-data:/home/node/.n8npara credenciales, flujos y configuraciones. - Lo uso como orquestador: escucha mensajes de WhatsApp (WAHA), llama a Ollama, guarda cosas en PostgreSQL, etc.
6.9. Servicio cloria-postgres
- Imagen:
postgres:16-alpine– base de datos ligera. - Variables:
POSTGRES_DB=cloriaPOSTGRES_USER=postgresPOSTGRES_PASSWORD=${CLORIA_POSTGRES_PASSWORD}
- Puerto:
5432:5432– te permite conectarte desde tu host (por ejemplo, con DBeaver). - Volumen:
cloria-data– almacenamiento persistente de la BD.
6.10. Volúmenes y red
- Volúmenes declarados:
ollama,openwebui-data,waha-data,qdrant-data,flowise-data,supabase-db-data,n8n-data,cloria-data.
Cada uno guarda datos persistentes de su servicio. - Red:
stack_gepeto.
Todos los servicios que la comparten se ven por nombre de contenedor (por ejemplo,http://ollama:11434,http://qdrant:6333, etc.).
7. Cómo levantar el stack
Desde la carpeta del proyecto:
cd ~/cloria-stack
# Comprobar que el .env está presente
ls .env
# Levantar todo en segundo plano
docker compose up -d
Verificamos que los contenedores estén corriendo:
docker ps
Algunos accesos típicos desde el navegador:
- Open WebUI:
http://TU_IP:3000 - Flowise:
http://TU_IP:3001 - n8n:
http://TU_IP:5678
8. Cómo agregar modelos a Ollama
Una vez que el contenedor ollama está corriendo, hay dos formas principales de agregar modelos:
8.1. Desde la consola (docker exec)
Ejemplo para entrar al contenedor y descargar un modelo:
# Entrar al contenedor ollama
docker exec -it ollama bash
# Dentro del contenedor:
ollama pull llama3
ollama pull zephyr
ollama pull mistral
# Salir con:
exit
O, si prefieres un solo comando desde el host:
docker exec -it ollama ollama pull llama3
docker exec -it ollama ollama pull zephyr
docker exec -it ollama ollama pull mistral
Ollama descargará los modelos y los guardará en el volumen ollama. Desde Open WebUI podrás seleccionarlos como modelos disponibles.
8.2. Desde Open WebUI
En la interfaz de Open WebUI normalmente puedes:
- Ir a la sección de modelos.
- Indicar el nombre del modelo (por ejemplo,
llama3ozephyr). - Open WebUI llama internamente a Ollama y realiza el pull si no existe.
Así puedes administrar qué modelos quieres exponer a tus usuarios finales, incluso ocultando algunos modelos “técnicos” y dejando visibles nombres más amigables.
9. Buenas prácticas de seguridad y mantenimiento
- Nunca publiques tu archivo .env con tokens o contraseñas reales.
- Usa contraseñas distintas para cada base de datos (aunque en el ejemplo se reutilice por simplicidad).
- Haz respaldos periódicos de:
- Volúmenes de PostgreSQL (
supabase-db-data,cloria-data). - Datos de n8n y Flowise.
- Configs de Open WebUI.
- Volúmenes de PostgreSQL (
- Revisa periódicamente actualizaciones de imágenes:
docker pull <imagen>y luegodocker compose up -d.
10. ¿Y si quiero hacer lo mismo en una Mac?
Buena noticia: sí, el mismo docker-compose.yml sirve en macOS, con algunos matices:
- En Mac no hay GPU NVIDIA, así que:
- Las líneas
NVIDIA_VISIBLE_DEVICES=all,deploy...yruntime: nvidiasimplemente no aportan nada. - Puedes dejarlas (Docker Desktop las ignora), o eliminarlas si quieres un YAML más limpio para Mac.
- Ollama dentro de Docker en Mac trabajará típicamente en CPU.
- Las líneas
- En Mac con Apple Silicon (M1/M2/M3), casi todas estas imágenes ya ofrecen variante
arm64. Si alguna falla con un mensaje del tipo no matching manifest for linux/arm64/v8, tendrás que:- Forzar
platform: linux/amd64en ese servicio, o - Buscar una imagen alternativa compatible con arm64.
- Forzar
10.1. Instalar Docker Desktop en macOS
- Descarga Docker Desktop para Mac desde la página oficial de Docker.
- Instálalo arrastrando el icono a Aplicaciones.
- Abre Docker Desktop y espera a que muestre “Docker Desktop is running”.
Docker Desktop ya incluye soporte para docker compose, así que no necesitas instalar nada extra.
10.2. Crear la carpeta del proyecto en Mac
En la app Terminal (o iTerm):
mkdir -p ~/cloria-stack
cd ~/cloria-stack
Copia dentro de esta carpeta el archivo docker-compose.yml y crea también el archivo .env igual que en la sección de Ubuntu:
nano .env
(Pega las mismas variables: CLOUDFLARE_TUNNEL_TOKEN, POSTGRES_PASSWORD, CLORIA_POSTGRES_PASSWORD, WEBUI_SECRET_KEY, etc.)
10.3. Levantar el stack en macOS
Desde la misma carpeta:
cd ~/cloria-stack
docker compose up -d
Los puertos son exactamente los mismos que en Ubuntu, así que desde la propia Mac podrás entrar a:
- Open WebUI:
http://localhost:3000 - Flowise:
http://localhost:3001 - n8n:
http://localhost:5678
10.4. Nota importante sobre rendimiento en Mac
En macOS, especialmente con Apple Silicon, Ollama funciona mejor si lo instalas nativamente (fuera de Docker), porque entonces puede aprovechar la GPU de Apple vía Metal. En ese caso puedes:
- Instalar Ollama directamente en macOS.
- Configurar Open WebUI (dentro de Docker) para que use:
OLLAMA_BASE_URL=http://host.docker.internal:11434
en lugar dehttp://ollama:11434.
Pero si prefieres tener todo “empaquetado” en Docker de la misma forma que en Ubuntu (aunque sea sólo con CPU), el mismo docker-compose.yml te sirve, simplemente sabiendo que las líneas de GPU serán ignoradas.
Con este stack tienes un “laboratorio” completo: modelos locales con Ollama, interfaz amigable con Open WebUI, flujos con n8n y Flowise, bases de datos para memoria de largo plazo y exposición segura a internet mediante Cloudflare. A partir de aquí puedes construir asistentes personalizados, automatizar respuestas de WhatsApp, conectar sistemas externos y seguir ampliando la arquitectura según tus necesidades.
