Servidor: Debian 12 amd64 | Docker Compose v2 | Ruta base:
/home/user/docker/ollama
Hardware: i7-9700, 64GB RAM, Nvidia Quadro P620 (2GB VRAM)
Este documento describe la instalación completa de un ecosistema de IA local basado en Ollama, desplegado en un servidor Debian 12 con Docker. El sistema incluye múltiples interfaces y puntos de acceso para maximizar la productividad.
| Componente | Descripción | Acceso |
|---|---|---|
| Ollama Core | Motor de IA con aceleración GPU (Nvidia Quadro P620) | Puerto 11434 |
| Web UI | Interfaz web tipo ChatGPT | Puerto 11435 |
| Bot de Telegram | Asistente remoto con control de acceso | Sin puerto expuesto |
| Home Assistant | Integración domótica vía rest_command | Integración API |
| CLI Avanzado | Herramienta de terminal con historial | Comando: ollama-cli |
| VS Code + Continue | Autocompletado de código tipo Copilot | Cliente remoto |
docker compose)Nota sobre GPU: La GPU P620 con 2GB VRAM es suficiente para modelos pequeños (1.5B-3B). Los modelos más grandes (14B) se ejecutarán principalmente en RAM del sistema.
Todo el ecosistema se organiza bajo una única carpeta raíz:
/home/user/docker/ollama/
├── docker-compose.yml # Orquestación de todos los servicios
├── data/ # Datos persistentes de Ollama (modelos, config)
├── web/ # Código fuente de la Web UI
│ ├── Dockerfile
│ ├── app.py
│ ├── requirements.txt
│ └── templates/
│ └── index.html
├── telegram-bot/ # Bot de Telegram
│ ├── Dockerfile
│ ├── bot.py
│ └── requirements.txt
├── cli/ # CLI y datos
│ └── history/ # Historial de conversaciones
└── scripts/ # Scripts de gestión
└── manage.sh # Script de administración
mkdir -p /home/user/docker/ollama/{data,web/templates,telegram-bot,cli/history,scripts}
cd /home/user/docker/ollama
/home/user/docker/ollama/docker-compose.ymlservices:
# Servicio principal Ollama con GPU
ollama:
image: ollama/ollama:latest
container_name: ollama-core
restart: unless-stopped
ports:
- "11434:11434"
volumes:
- ./data:/root/.ollama
environment:
- OLLAMA_ORIGINS=*
- OLLAMA_HOST=0.0.0.0
- OLLAMA_NUM_PARALLEL=4
- OLLAMA_CPU_THREADS=8
networks:
- ollama-net
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
# Web UI
web:
build: ./web
container_name: ollama-web
restart: unless-stopped
ports:
- "11435:5000"
depends_on:
- ollama
networks:
- ollama-net
environment:
- OLLAMA_API=http://ollama:11434
# Bot de Telegram
telegram-bot:
build:
context: ./telegram-bot
dockerfile: Dockerfile
container_name: ollama-telegram
restart: unless-stopped
depends_on:
- ollama
networks:
- ollama-net
environment:
- TELEGRAM_TOKEN=TU_TOKEN_AQUI
- ALLOWED_USER_IDS=TU_USER_ID_AQUI
- OLLAMA_API=http://ollama:11434
- DEFAULT_MODEL=qwen2.5-coder:3b-instruct-q4_K_M
networks:
ollama-net:
driver: bridge
name: ollama-network
| Variable | Descripción |
|---|---|
OLLAMA_ORIGINS=* |
Permite conexiones desde cualquier origen (necesario para VS Code remoto) |
OLLAMA_HOST=0.0.0.0 |
Escucha en todas las interfaces de red |
OLLAMA_NUM_PARALLEL=4 |
Número de peticiones paralelas |
OLLAMA_CPU_THREADS=8 |
Hilos de CPU para inferencia |
TELEGRAM_TOKEN |
Token del bot de BotFather |
ALLOWED_USER_IDS |
IDs de Telegram permitidos (separados por coma) |
cd /home/user/docker/ollama
docker compose up -d
docker compose ps
Los modelos deben descargarse DENTRO del contenedor:
# Modelo rápido para autocompletado (1.5B - ~1GB)
docker exec ollama-core ollama pull qwen2.5-coder:1.5b
# Modelo balanceado (3B - ~2GB)
docker exec ollama-core ollama pull qwen2.5-coder:3b-instruct-q4_K_M
# Modelo potente (14B - ~8GB)
docker exec ollama-core ollama pull qwen2.5-coder:14b
# Verificar modelos instalados
docker exec ollama-core ollama list
# Ver modelos disponibles
curl -s http://localhost:11434/api/tags | jq
# Prueba de generación
curl -s http://localhost:11434/api/generate \
-H 'Content-Type: application/json' \
-d '{
"model":"qwen2.5-coder:3b-instruct-q4_K_M",
"prompt":"Escribe hola en una palabra",
"stream":false
}' | jq .response
⚠️ IMPORTANTE: Si recreaste el contenedor
ollama-core, los modelos SE PIERDEN. Debes volver a ejecutar los comandosollama pullo usar un volumen persistente en./data
Interfaz web tipo ChatGPT que se conecta a Ollama Core. Accesible desde cualquier navegador en la red local.
/home/user/docker/ollama/web/DockerfileFROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
/home/user/docker/ollama/web/requirements.txtflask==3.0.0
requests==2.31.0
/home/user/docker/ollama/web/app.pyfrom flask import Flask, render_template, request, jsonify, Response
import requests
import os
import json
app = Flask(__name__)
OLLAMA_API = os.getenv('OLLAMA_API', 'http://ollama:11434')
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/models', methods=['GET'])
def get_models():
try:
response = requests.get(f'{OLLAMA_API}/api/tags', timeout=5)
if response.status_code == 200:
return jsonify(response.json())
return jsonify({'error': 'No se pudieron obtener los modelos'}), 500
except Exception as e:
return jsonify({'error': f'Error de conexión: {str(e)}'}), 500
@app.route('/api/chat', methods=['POST'])
def chat():
data = request.json
model = data.get('model', 'qwen2.5-coder:3b-instruct-q4_K_M')
messages = data.get('messages', [])
def generate():
try:
response = requests.post(
f'{OLLAMA_API}/api/chat',
json={'model': model, 'messages': messages, 'stream': True},
stream=True,
timeout=120
)
if response.status_code != 200:
yield f"data: {json.dumps({'error': f'Error {response.status_code}'})}
"
return
for line in response.iter_lines():
if line:
yield f"data: {line.decode('utf-8')}
"
except Exception as e:
yield f"data: {json.dumps({'error': str(e)})}
"
return Response(generate(), mimetype='text/event-stream')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
/home/user/docker/ollama/web/templates/index.html<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ollama Chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.container {
width: 90%;
max-width: 900px;
height: 90vh;
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
display: flex;
flex-direction: column;
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 { font-size: 24px; }
.model-selector {
background: rgba(255,255,255,0.2);
border: none;
color: white;
padding: 8px 12px;
border-radius: 8px;
cursor: pointer;
}
.chat-container {
flex: 1;
overflow-y: auto;
padding: 20px;
background: #f5f5f5;
}
.message {
margin-bottom: 15px;
display: flex;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.message.user { justify-content: flex-end; }
.message-content {
max-width: 70%;
padding: 12px 16px;
border-radius: 18px;
word-wrap: break-word;
}
.message.user .message-content {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.message.assistant .message-content {
background: white;
color: #333;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.input-container {
padding: 20px;
background: white;
border-top: 1px solid #e0e0e0;
display: flex;
gap: 10px;
}
#userInput {
flex: 1;
padding: 12px 16px;
border: 2px solid #e0e0e0;
border-radius: 25px;
font-size: 14px;
outline: none;
transition: border-color 0.3s;
}
#userInput:focus { border-color: #667eea; }
#sendBtn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 30px;
border-radius: 25px;
cursor: pointer;
font-weight: bold;
transition: transform 0.2s;
}
#sendBtn:hover { transform: scale(1.05); }
#sendBtn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.typing-indicator {
display: none;
padding: 10px;
background: white;
border-radius: 18px;
width: 60px;
}
.typing-indicator span {
height: 8px;
width: 8px;
background: #667eea;
border-radius: 50%;
display: inline-block;
margin: 0 2px;
animation: typing 1.4s infinite;
}
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing {
0%, 60%, 100% { transform: translateY(0); }
30% { transform: translateY(-10px); }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🤖 Ollama Chat</h1>
<select id="modelSelect" class="model-selector">
<option value="">Cargando modelos...</option>
</select>
</div>
<div class="chat-container" id="chatContainer">
<div class="message assistant">
<div class="message-content">
¡Hola! Soy tu asistente de IA local. ¿En qué puedo ayudarte hoy?
</div>
</div>
</div>
<div class="input-container">
<input type="text" id="userInput" placeholder="Escribe tu mensaje..." />
<button id="sendBtn">Enviar</button>
</div>
</div>
<script>
const chatContainer = document.getElementById('chatContainer');
const userInput = document.getElementById('userInput');
const sendBtn = document.getElementById('sendBtn');
const modelSelect = document.getElementById('modelSelect');
let conversationHistory = [];
// Cargar modelos disponibles
fetch('/api/models')
.then(res => res.json())
.then(data => {
modelSelect.innerHTML = '';
if (data.models && data.models.length > 0) {
data.models.forEach(model => {
const option = document.createElement('option');
option.value = model.name;
option.textContent = model.name;
modelSelect.appendChild(option);
});
} else {
modelSelect.innerHTML = '<option>No hay modelos</option>';
}
})
.catch(err => {
modelSelect.innerHTML = '<option>Error al cargar</option>';
});
function addMessage(role, content) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${role}`;
messageDiv.innerHTML = `<div class="message-content">${content}</div>`;
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function showTypingIndicator() {
const indicator = document.createElement('div');
indicator.className = 'message assistant';
indicator.innerHTML = '<div class="typing-indicator" style="display:flex;"><span></span><span></span><span></span></div>';
indicator.id = 'typingIndicator';
chatContainer.appendChild(indicator);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function removeTypingIndicator() {
const indicator = document.getElementById('typingIndicator');
if (indicator) indicator.remove();
}
async function sendMessage() {
const message = userInput.value.trim();
if (!message) return;
const selectedModel = modelSelect.value;
if (!selectedModel) {
alert('Por favor selecciona un modelo');
return;
}
addMessage('user', message);
conversationHistory.push({ role: 'user', content: message });
userInput.value = '';
sendBtn.disabled = true;
showTypingIndicator();
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: selectedModel,
messages: conversationHistory
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let assistantMessage = '';
let messageDiv = null;
removeTypingIndicator();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.slice(6));
if (data.message && data.message.content) {
assistantMessage += data.message.content;
if (!messageDiv) {
messageDiv = document.createElement('div');
messageDiv.className = 'message assistant';
messageDiv.innerHTML = '<div class="message-content"></div>';
chatContainer.appendChild(messageDiv);
}
messageDiv.querySelector('.message-content').textContent = assistantMessage;
chatContainer.scrollTop = chatContainer.scrollHeight;
}
} catch (e) {}
}
}
}
conversationHistory.push({ role: 'assistant', content: assistantMessage });
} catch (error) {
removeTypingIndicator();
addMessage('assistant', '❌ Error: ' + error.message);
}
sendBtn.disabled = false;
userInput.focus();
}
sendBtn.addEventListener('click', sendMessage);
userInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendMessage();
});
</script>
</body>
</html>
cd /home/user/docker/ollama
docker compose up -d --build web
URL: http://192.168.100.100:11435 (sustituye la IP por la de tu servidor)
| Problema | Solución |
|---|---|
| Error 400 / «no models» | Los modelos no están descargados. Ejecuta: docker exec ollama-core ollama list |
| Contenedor no arranca | Verifica que ollama-core esté corriendo: docker compose ps |
| Cambios no se aplican | Reconstruye: docker compose build --no-cache web && docker compose restart web |
docker-compose.yml con tus datos reales/home/user/docker/ollama/telegram-bot/DockerfileFROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY bot.py .
CMD ["python", "bot.py"]
/home/user/docker/ollama/telegram-bot/requirements.txtpython-telegram-bot==20.7
requests==2.31.0
/home/user/docker/ollama/telegram-bot/bot.pyimport os
import logging
import requests
from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
TELEGRAM_TOKEN = os.getenv('TELEGRAM_TOKEN')
ALLOWED_USER_IDS = [int(uid) for uid in os.getenv('ALLOWED_USER_IDS', '').split(',') if uid]
OLLAMA_API = os.getenv('OLLAMA_API', 'http://ollama:11434')
DEFAULT_MODEL = os.getenv('DEFAULT_MODEL', 'qwen2.5-coder:3b-instruct-q4_K_M')
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
if update.effective_user.id not in ALLOWED_USER_IDS:
await update.message.reply_text("❌ No autorizado")
return
await update.message.reply_text(
f"🤖 Bot Ollama activo\n"
f"Modelo: {DEFAULT_MODEL}\n\n"
f"Envía cualquier mensaje para obtener respuesta."
)
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
if update.effective_user.id not in ALLOWED_USER_IDS:
return
user_message = update.message.text
await update.message.reply_text("⏳ Procesando...")
try:
response = requests.post(
f'{OLLAMA_API}/api/generate',
json={
'model': DEFAULT_MODEL,
'prompt': user_message,
'stream': False
},
timeout=120
)
if response.status_code == 200:
result = response.json()
answer = result.get('response', 'Sin respuesta')
# Telegram tiene límite de 4096 caracteres
if len(answer) > 4000:
for i in range(0, len(answer), 4000):
await update.message.reply_text(answer[i:i+4000])
else:
await update.message.reply_text(answer)
else:
await update.message.reply_text(f"❌ Error {response.status_code}")
except Exception as e:
logger.error(f"Error: {e}")
await update.message.reply_text(f"❌ Error: {str(e)}")
def main():
if not TELEGRAM_TOKEN:
logger.error("TELEGRAM_TOKEN no configurado")
return
if not ALLOWED_USER_IDS:
logger.error("ALLOWED_USER_IDS no configurado")
return
app = Application.builder().token(TELEGRAM_TOKEN).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
logger.info(f"Bot iniciado. Usuarios permitidos: {ALLOWED_USER_IDS}")
app.run_polling()
if __name__ == '__main__':
main()
docker compose up -d --build telegram-bot
docker logs -f ollama-telegram
Simplemente envía mensajes al bot. Solo responderá a los User IDs configurados en ALLOWED_USER_IDS.
rest_commanden Home Assistant ejecuta la llamada HTTP pero NO captura automáticamente la respuesta. Para ver el resultado necesitas un Script con response_variable.
⚠️ Si ya tienes rest_command:, NO crees otro bloque. Añade dentro del existente:
rest_command:
# ... tus otros rest_command existentes ...
ollama_generate:
url: "http://192.168.100.100:11434/api/generate"
method: POST
content_type: "application/json"
timeout: 60
payload: >
{
"model": "{{ model | default('qwen2.5-coder:3b-instruct-q4_K_M') }}",
"prompt": "{{ prompt }}",
"stream": false
}
input_text:
ollama_prompt:
name: "Escribe tu consulta"
initial: ""
ollama_response:
name: "Última respuesta"
initial: ""
input_select:
ollama_model:
name: "Modelo"
options:
- "qwen2.5-coder:3b-instruct-q4_K_M"
- "qwen2.5-coder:14b"
initial: "qwen2.5-coder:3b-instruct-q4_K_M"
Crear en UI → Scripts → Modo YAML:
alias: "Ollama: Ejecutar y Capturar"
sequence:
- action: rest_command.ollama_generate
data:
prompt: "{{ states('input_text.ollama_prompt') }}"
model: "{{ states('input_select.ollama_model') }}"
response_variable: respuesta_ollama
- action: input_text.set_value
target:
entity_id: input_text.ollama_response
data:
value: "{{ respuesta_ollama['content']['response'][:250] }}"
mode: single
type: entities
title: Ollama Coder Local
entities:
- entity: input_text.ollama_prompt
- entity: input_select.ollama_model
- type: button
name: Enviar a Ollama
icon: mdi:send
action_name: GENERAR
tap_action:
action: call-service
service: script.ollama_ejecutar_y_capturar
- entity: input_text.ollama_response
Para respuestas largas, mejor enviar al móvil:
- action: notify.mobile_app_tu_dispositivo
data:
title: "Respuesta de Ollama"
message: "{{ respuesta_ollama['content']['response'] }}"
Encuentra tu servicio en: Herramientas para desarrolladores → Acciones → busca «notify.mobile_app_»
/usr/local/bin/ollama-cli#!/bin/bash
# Configuración
OLLAMA_API="${OLLAMA_API:-http://localhost:11434}"
OLLAMA_MODEL="${OLLAMA_MODEL:-qwen2.5-coder:3b-instruct-q4_K_M}"
HISTORY_DIR="${OLLAMA_HISTORY_DIR:-$HOME/.ollama_history}"
HISTORY_FILE="$HISTORY_DIR/conversation_$(date +%Y%m%d).json"
# Colores
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Crear directorio de historial
mkdir -p "$HISTORY_DIR"
# Función para enviar prompt
send_prompt() {
local prompt="$1"
local model="${2:-$OLLAMA_MODEL}"
local system_prompt="$3"
local payload
if [ -n "$system_prompt" ]; then
payload=$(jq -n \
--arg model "$model" \
--arg prompt "$prompt" \
--arg system "$system_prompt" \
'{model: $model, prompt: $prompt, system: $system, stream: false}')
else
payload=$(jq -n \
--arg model "$model" \
--arg prompt "$prompt" \
'{model: $model, prompt: $prompt, stream: false}')
fi
response=$(curl -s "$OLLAMA_API/api/generate" \
-H "Content-Type: application/json" \
-d "$payload")
echo "$response" | jq -r '.response // "Error: sin respuesta"'
}
# Modo one-shot (para scripts)
if [ "$1" = "-q" ] || [ "$1" = "--query" ]; then
shift
model="$OLLAMA_MODEL"
system_prompt=""
# Parsear argumentos
while [[ $# -gt 0 ]]; do
case $1 in
-m|--model)
model="$2"
shift 2
;;
--system)
system_prompt="$2"
shift 2
;;
*)
query="$1"
shift
;;
esac
done
if [ -z "$query" ]; then
echo "Uso: ollama-cli -q "tu pregunta" [-m modelo] [--system "prompt del sistema"]"
exit 1
fi
send_prompt "$query" "$model" "$system_prompt"
exit 0
fi
# Modo interactivo
echo -e "${BLUE}╔════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ Ollama CLI - Modo Interactivo ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════╝${NC}"
echo -e "${YELLOW}Modelo: $OLLAMA_MODEL${NC}"
echo -e "${YELLOW}Historial: $HISTORY_FILE${NC}"
echo -e "${GREEN}Comandos: /exit, /clear, /history${NC}"
echo ""
while true; do
echo -ne "${GREEN}Tú >${NC} "
read -r user_input
case "$user_input" in
/exit|/quit)
echo "¡Hasta luego!"
exit 0
;;
/clear)
> "$HISTORY_FILE"
echo "Historial limpiado"
continue
;;
/history)
if [ -f "$HISTORY_FILE" ]; then
cat "$HISTORY_FILE" | jq -r '.[] | "\(.role): \(.content)"'
else
echo "Sin historial"
fi
continue
;;
"")
continue
;;
esac
# Guardar en historial
echo "{"role":"user","content":"$user_input","timestamp":"$(date -Iseconds)"}" >> "$HISTORY_FILE"
# Obtener respuesta
echo -ne "${BLUE}Ollama >${NC} "
response=$(send_prompt "$user_input")
echo "$response"
echo ""
# Guardar respuesta en historial
echo "{"role":"assistant","content":"$response","timestamp":"$(date -Iseconds)"}" >> "$HISTORY_FILE"
done
sudo nano /usr/local/bin/ollama-cli
# Pegar el script anterior
sudo chmod +x /usr/local/bin/ollama-cli
# Configurar historial
mkdir -p /home/user/docker/ollama/cli/history
echo 'export OLLAMA_HISTORY_DIR="/home/user/docker/ollama/cli/history"' >> ~/.bashrc
source ~/.bashrc
# Modo interactivo
ollama-cli
# Modo one-shot (para scripts)
ollama-cli -q "Explica qué es Docker"
# Con modelo específico
ollama-cli -q "Optimiza este código" -m qwen2.5-coder:14b
# Con system prompt
ollama-cli -q "Script de backup" --system "Responde solo con código Bash"
# Redirigir a archivo
ollama-cli -q "Script bash para backup" > backup.sh
# Ver Y guardar al mismo tiempo
ollama-cli -q "Script Python para..." | tee script.py
Crear comandos cortos que incluyan prompts especializados para diferentes lenguajes, forzando respuestas «solo código» sin explicaciones.
Añadir al final de ~/.bashrc:
# ============================================
# OLLAMA - Atajos especializados por lenguaje
# ============================================
# Bash/Shell scripting para Debian
alias ollama-bash='ollama-cli -q --system "Eres un experto en Bash y administración de sistemas Debian. Responde SOLO con código ejecutable, sin explicaciones."'
# Python
alias ollama-python='ollama-cli -q --system "Eres un experto en Python. Responde SOLO con código Python limpio y funcional, sin comentarios innecesarios."'
# Docker y Docker Compose
alias ollama-docker='ollama-cli -q --system "Eres un experto en Docker. Responde SOLO con Dockerfiles o docker-compose.yml válidos."'
# ESP32 / Firmware
alias ollama-esp32='ollama-cli -q --system "Eres un experto en ESP32 y Arduino. Responde SOLO con código C/C++ para ESP32."'
# JavaScript/Node.js
alias ollama-js='ollama-cli -q --system "Eres un experto en JavaScript y Node.js. Responde SOLO con código JavaScript funcional."'
# Aplicar cambios
# source ~/.bashrc
source ~/.bashrc
# Generar script de limpieza
ollama-bash "Script para limpiar logs antiguos de /var/log" | tee clean_logs.sh
# Generar función Python
ollama-python "Función para validar emails con regex" > validate.py
# Generar docker-compose
ollama-docker "Compose para Nginx + PHP-FPM + MariaDB" > docker-compose.yml
# Código ESP32
ollama-esp32 "Leer sensor DHT22 y publicar por MQTT" > sensor.ino
# Función JavaScript
ollama-js "Función async para fetch con retry" > fetch-retry.js
Archivo de configuración funcional y probado:
name: Local Config
version: 1.0.0
schema: v1
models:
- name: Ollama Turbo (3B)
provider: ollama
model: qwen2.5-coder:3b-instruct-q4_K_M
apiBase: http://192.168.100.100:11434
roles:
- chat
- edit
- name: Ollama Cerebro (14B)
provider: ollama
model: qwen2.5-coder:14b
apiBase: http://192.168.100.100:11434
roles:
- chat
- name: Ollama Fast (1.5B)
provider: ollama
model: qwen2.5-coder:1.5b
apiBase: http://192.168.100.100:11434
roles:
- autocomplete
tabAutocompleteModel:
name: Ollama Fast (1.5B)
provider: ollama
model: qwen2.5-coder:1.5b
apiBase: http://192.168.100.100:11434
tabAutocompleteOptions:
useCopyBuffer: true
maxPromptTokens: 400
debounceDelay: 100
Importante: Sustituye
192.168.100.100por la IP real de tu servidor Debian
| Parámetro | Descripción |
|---|---|
apiBase |
Debe apuntar a la IP del servidor Debian (192.168.100.100:11434) |
roles |
Define qué puede hacer cada modelo (chat, edit, autocomplete) |
modelo 1.5B |
Recomendado para autocomplete por velocidad |
OLLAMA_ORIGINS=* |
Debe estar en el docker-compose.yml para permitir conexiones remotas |
| Atajo | Función |
|---|---|
| Ctrl + L | Abrir chat lateral (consultas y explicaciones) |
| Ctrl + I | Edición inline (generar código en el archivo) |
| Tab | Aceptar sugerencia de autocompletado |
| Esc | Rechazar sugerencia |
| Problema | Solución |
|---|---|
| Autocompletado no funciona | Verificar que el modelo 1.5B esté descargado y seleccionado |
| No aparecen los modelos | Recargar config: Ctrl+Shift+P → «Continue: Reload Config» |
| Error de conexión | Verificar OLLAMA_ORIGINS=* en docker-compose.yml y reiniciar contenedor |
| Sugerencias muy lentas | Usar modelo 1.5B para autocomplete en lugar de 3B o 14B |
/home/user/docker/ollama/scripts/manage.sh#!/bin/bash
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
COMPOSE_FILE="/home/user/docker/ollama/docker-compose.yml"
show_status() {
echo -e "${BLUE}╔════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ ESTADO DEL ECOSISTEMA OLLAMA ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${YELLOW}--- Contenedores ---${NC}"
docker compose -f "$COMPOSE_FILE" ps
echo ""
echo -e "${YELLOW}--- Modelos descargados ---${NC}"
docker exec ollama-core ollama list 2>/dev/null || echo "Contenedor no disponible"
echo ""
echo -e "${YELLOW}--- Uso de recursos ---${NC}"
echo -n "RAM: "
free -h | grep "Mem:" | awk '{print $3 " / " $2}'
if command -v nvidia-smi &> /dev/null; then
echo -n "GPU: "
nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total --format=csv,noheader,nounits | awk '{print $1"% GPU, "$2"MB / "$3"MB VRAM"}'
fi
echo ""
}
clean_history() {
echo -e "${YELLOW}Limpiando historial del CLI...${NC}"
rm -rf /home/user/docker/ollama/cli/history/*
echo -e "${GREEN}✓ Historial limpio${NC}"
}
update_models() {
echo -e "${YELLOW}Actualizando modelos...${NC}"
models=$(docker exec ollama-core ollama list 2>/dev/null | awk 'NR>1 {print $1}')
if [ -z "$models" ]; then
echo -e "${RED}No se encontraron modelos${NC}"
return 1
fi
for model in $models; do
echo -e "${BLUE}Actualizando $model...${NC}"
docker exec ollama-core ollama pull "$model"
done
echo -e "${GREEN}✓ Modelos actualizados${NC}"
}
restart_services() {
echo -e "${YELLOW}Reiniciando servicios...${NC}"
docker compose -f "$COMPOSE_FILE" restart
echo -e "${GREEN}✓ Servicios reiniciados${NC}"
}
show_logs() {
local service="${1:-ollama-core}"
echo -e "${BLUE}Mostrando logs de $service (Ctrl+C para salir)${NC}"
docker logs -f "$service"
}
show_help() {
echo -e "${BLUE}╔════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ OLLAMA-MANAGE - Gestión del ecosistema ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════╝${NC}"
echo ""
echo "Uso: ollama-manage [comando]"
echo ""
echo "Comandos disponibles:"
echo " status Ver estado de contenedores, modelos y recursos"
echo " update Actualizar todos los modelos instalados"
echo " clean Limpiar historial del CLI"
echo " restart Reiniciar todos los servicios"
echo " logs [servicio] Ver logs (por defecto: ollama-core)"
echo " help Mostrar esta ayuda"
echo ""
echo "Ejemplos:"
echo " ollama-manage status"
echo " ollama-manage logs ollama-web"
echo " ollama-manage update"
}
case "$1" in
status)
show_status
;;
clean)
clean_history
;;
update)
update_models
;;
restart)
restart_services
;;
logs)
show_logs "$2"
;;
help|--help|-h)
show_help
;;
*)
show_help
exit 1
;;
esac
# Crear el script
nano /home/user/docker/ollama/scripts/manage.sh
# Pegar el contenido anterior
# Dar permisos de ejecución
chmod +x /home/user/docker/ollama/scripts/manage.sh
# Crear enlace simbólico para uso global
sudo ln -sf /home/user/docker/ollama/scripts/manage.sh /usr/local/bin/ollama-manage
| Comando | Descripción |
|---|---|
ollama-manage status |
Ver estado de contenedores, modelos y recursos (RAM/GPU) |
ollama-manage update |
Actualizar todos los modelos instalados |
ollama-manage clean |
Limpiar historial del CLI |
ollama-manage restart |
Reiniciar todos los servicios |
ollama-manage logs [servicio] |
Ver logs en tiempo real (por defecto: ollama-core) |
ollama-manage help |
Mostrar ayuda |
# Ver estado general
ollama-manage status
# Ver logs de la web UI
ollama-manage logs ollama-web
# Ver logs del bot de Telegram
ollama-manage logs ollama-telegram
# Actualizar modelos
ollama-manage update
# Limpiar historial
ollama-manage clean
| Problema | Causa probable | Solución |
|---|---|---|
| Contenedor ollama-core no arranca | Drivers Nvidia no instalados | Instalar nvidia-container-toolkit o quitar sección deploy: del compose |
| Web UI error 400 | Modelos no descargados | docker exec ollama-core ollama pull <modelo> |
| Bot Telegram no responde | Token o User ID incorrecto | Verificar variables en docker-compose.yml |
| Home Assistant timeout | Timeout muy bajo | Aumentar timeout: 60 en rest_command |
| VS Code no autocompleta | Modelo no seleccionado o no descargado | Descargar 1.5B y seleccionarlo en Continue |
| Modelos desaparecen | Contenedor recreado sin volumen | Usar volumen ./data:/root/.ollama |
| Error «no space left» | Disco lleno | Limpiar modelos antiguos o aumentar espacio |
| GPU no se usa | nvidia-container-toolkit no instalado | Instalar toolkit y reiniciar Docker |
# Ver logs de un servicio
docker logs -f ollama-core
docker logs -f ollama-web
docker logs -f ollama-telegram
# Ver uso de recursos en tiempo real
docker stats
# Verificar red
docker network inspect ollama-network
# Probar API directamente
curl http://localhost:11434/api/tags
# Ver modelos dentro del contenedor
docker exec ollama-core ollama list
# Entrar al contenedor para debug
docker exec -it ollama-core bash
# Ver uso de GPU
nvidia-smi
# Ver espacio en disco
df -h /home/user/docker/ollama/data
# Verificar que Ollama responde
curl http://localhost:11434/api/version
Si todo falla, reinstalar desde cero:
# Detener y eliminar contenedores
cd /home/user/docker/ollama
docker compose down -v
# Hacer backup de modelos (opcional)
sudo cp -r data data.backup
# Limpiar todo
sudo rm -rf data/*
# Volver a levantar
docker compose up -d
# Descargar modelos de nuevo
docker exec ollama-core ollama pull qwen2.5-coder:1.5b
docker exec ollama-core ollama pull qwen2.5-coder:3b-instruct-q4_K_M
docker exec ollama-core ollama pull qwen2.5-coder:14b
Sigue estos pasos en orden para desplegar todo el ecosistema:
1. Crear estructura de carpetas:
mkdir -p /home/user/docker/ollama/{data,web/templates,telegram-bot,cli/history,scripts}
cd /home/user/docker/ollama
2. Copiar docker-compose.yml a /home/user/docker/ollama/
3. Crear archivos de Web UI:
web/Dockerfileweb/app.pyweb/requirements.txtweb/templates/index.html4. Crear archivos de Bot Telegram:
telegram-bot/Dockerfiletelegram-bot/bot.pytelegram-bot/requirements.txt 5. Editar docker-compose.yml con TELEGRAM_TOKEN y ALLOWED_USER_IDS reales
6. Levantar servicios:
docker compose up -d
7. Descargar modelos:
docker exec ollama-core ollama pull qwen2.5-coder:1.5b
docker exec ollama-core ollama pull qwen2.5-coder:3b-instruct-q4_K_M
docker exec ollama-core ollama pull qwen2.5-coder:14b
8. Verificar modelos:
docker exec ollama-core ollama list
9. Probar Web UI: http://IP:11435
10. Probar Bot Telegram: enviar mensaje
11. Configurar Home Assistant:
rest_commandinput_text e input_select 12. Instalar ollama-cli:
sudo nano /usr/local/bin/ollama-cli
sudo chmod +x /usr/local/bin/ollama-cli
13. Configurar historial CLI:
echo 'export OLLAMA_HISTORY_DIR="/home/user/docker/ollama/cli/history"' >> ~/.bashrc
source ~/.bashrc
14. Añadir atajos a ~/.bashrc:
ollama-bashollama-pythonollama-dockerollama-esp3215. Instalar extensión Continue en VS Code
16. Configurar Continue con config.yaml
17. Verificar que el modelo 1.5B esté descargado para autocompletado
18. Instalar script de gestión:
chmod +x /home/user/docker/ollama/scripts/manage.sh
sudo ln -sf /home/user/docker/ollama/scripts/manage.sh /usr/local/bin/ollama-manage
19. Verificar todo con:
ollama-manage status
Este despliegue está pensado para uso en red local. Si deseas exponerlo públicamente, utiliza tu stack existente de Caddy + Authelia + Cloudflare Tunnel para autenticación segura.
ollama-manage updateollama-manage cleandocker compose pull && docker compose up -d./data/ (los modelos ocupan espacio)docker logs ollama-coreDocumentación creada para servidor Debian 12 con hardware:
Adaptable a otras configuraciones modificando los parámetros de recursos en docker-compose.yml.
Última actualización: 2026-02-01
Versión: 1.0