feat(stack): create unified agents stack with Tatlock and Webber

Consolidate Tatlock API into new agents stack and add Webber LLM agent
orchestration service. Webber provides autonomous agent execution with
tool use, authenticating via Tatlock API.

Changes:
- Add stacks/agents.yml with Tatlock (port 8000) and Webber (port 8086)
- Remove stacks/tatlock.yml (merged into agents stack)
- Document Webber service in CONTAINERS.md (Redis DB 9)
- Add full Tatlock API documentation to CONTAINERS.md

Config updates (pre-existing):
- core-api: Enable OIDC, add host stats access, simplify model config
- gitea: Migrate from dedicated DB to postgres-shared
- media: Remove GPU reservation from Jellyfin

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-09 19:27:44 +01:00
co-authored by Claude Opus 4.5
parent 3df367ff85
commit e8021307da
6 changed files with 232 additions and 151 deletions
+69 -5
View File
@@ -1,7 +1,7 @@
# Container Reference - tower-of-joy Infrastructure
> **Last Updated:** 2026-01-06
> **Total Services:** 30 containers across 20 stacks
> **Last Updated:** 2026-01-09
> **Total Services:** 31 containers across 20 stacks
> **System:** Intel i7-6700, RTX 2080 Ti (11GB VRAM), 64GB RAM, Zorin OS 16.3
---
@@ -33,6 +33,7 @@
| **Qdrant** | 6333, 6334 | http://192.168.86.149:6333 | LAN | No | - | ✅ Running |
| **Wiki.js** | 3000 | http://192.168.86.149:3000 | LAN | No | 2 | ✅ Running |
| **Tatlock** | 8000 | https://tatlock.schweitz.net | Internet (SSO) | No | 1, 6 | ✅ Running (external) |
| **Webber** | 8086 | http://192.168.86.149:8086 | LAN | No | 9 | ✅ Running (external) |
| **Library Desk** | 8089 | http://192.168.86.149:8089 | LAN | No | 4 | ✅ Running (external) |
| **AMP (Game Server)** | Varies | http://192.168.86.149:8081 | LAN | No | - | ✅ Running |
| **Home Assistant** | 8123 | https://housekeeping.schweitz.net | Internet | No | - | ✅ Running |
@@ -90,6 +91,7 @@ Services marked "(external)" have source code in separate Gitea repositories. Co
| **Core API** | [core-api](https://git.schweitz.net/jpmschweitzer/core-api) | `git.schweitz.net/jpmschweitzer/core-api:latest` |
| **Library Desk** | [library-desk](https://git.schweitz.net/jpmschweitzer/library-desk) | `git.schweitz.net/jpmschweitzer/library-desk:latest` |
| **Tatlock UI** | [tatlock-ui](https://git.schweitz.net/jpmschweitzer/tatlock-ui) | `git.schweitz.net/jpmschweitzer/tatlock-ui:latest` |
| **Webber** | [webber](https://git.schweitz.net/jpmschweitzer/webber) | `git.schweitz.net/jpmschweitzer/webber:latest` |
**Development workflow:** Clone repo → make changes → create Gitea release → Watchtower auto-updates container.
@@ -183,7 +185,7 @@ Redis Shared is a centralized Redis 7 key-value store providing cache, session s
| **Resource Limits** | None |
| **GPU Required** | No |
| **Dependencies** | docker-dataplane network |
| **Database Allocation** | DB 0: Available, DB 1: Tatlock (memory), DB 2: Wiki.js, DB 3: Scheduler, DB 4: Library Desk, DB 5: SearXNG, DB 6: Tatlock (benchmarks), DB 7: Nextcloud, DB 8: Paperless, DB 9-15: Available |
| **Database Allocation** | DB 0: Available, DB 1: Tatlock (memory), DB 2: Wiki.js, DB 3: Scheduler, DB 4: Library Desk, DB 5: SearXNG, DB 6: Tatlock (benchmarks), DB 7: Nextcloud, DB 8: Paperless, DB 9: Webber (sessions), DB 10-15: Available |
| **Persistence** | AOF (Append-Only File) enabled for durability |
| **Health Check** | `redis-cli ping` returns PONG (30s interval) |
| **Connection String** | `redis://redis-shared:6379/0` (DB 0), `redis://redis-shared:6379/1` (DB 1), etc. |
@@ -409,6 +411,65 @@ Core API provides OpenAI-compatible HTTP functions for Open WebUI, extending LLM
| **Key Features** | OpenAI-compatible API (`/v1/chat/completions`, `/v1/models`), Model aliasing (OpenAI → local models), Streaming & non-streaming responses, Web scraping (Trafilatura, BeautifulSoup), Hot-reload development, OpenAPI spec, Infrastructure management (Portainer) |
| **AI Orchestrator** | **Phase 1 Complete** - OpenAI API wrapper with model routing. Phase 2+ will add memory systems, multi-agent workflows, and tool integration. |
| **Health Check** | `GET /health` (30s interval) - checks API status and Ollama connectivity |
---
### Tatlock
Tatlock is the "Homelab Butler" - an OpenAI-compatible API server with LLM agent orchestration capabilities, providing intelligent automation and chat functionality for the tower-of-joy infrastructure. It serves as the primary AI backend for the homelab, offering model proxying through Ollama, vector search via Qdrant, web search integration through SearXNG, and memory persistence via Redis. The service provides user authentication and API key management that other agent services (like Webber) use for validation. Part of the unified agents stack alongside Webber.
| Property | Value |
|----------|-------|
| **Image** | `git.schweitz.net/jpmschweitzer/tatlock:latest` |
| **Container Name** | `tatlock` |
| **Stack** | `agents` (unified agents stack) |
| **Access URL (LAN)** | http://192.168.86.149:8000 |
| **Access URL (Public)** | https://tatlock.schweitz.net |
| **Internal Domain** | http://tatlock.schweitz.internal |
| **External Access** | Yes (via NPM reverse proxy with SSL + Authentik SSO) |
| **Port Mapping** | 8000:8000 (HTTP) |
| **Network Mode** | Bridge (docker-dataplane) |
| **Restart Policy** | `unless-stopped` |
| **Volume Mounts** | `~/docker-data/tatlock/logs:/app/logs` |
| **Environment** | `OLLAMA_HOST=http://ollama:11434`, `OLLAMA_DEFAULT_MODEL=mistral-nemo:latest`, `QDRANT_HOST=qdrant`, `SEARXNG_HOST=http://searxng:8080` |
| **Source Repository** | https://git.schweitz.net/jpmschweitzer/tatlock |
| **Resource Limits** | CPU: 1.0, Memory: 1GB limit, 256MB reservation |
| **GPU Required** | No (uses Ollama for GPU inference) |
| **Redis DB** | DB 1 (memory), DB 6 (benchmarks) |
| **Dependencies** | Ollama, Qdrant, Redis Shared, SearXNG, Library Desk |
| **Framework** | FastAPI, Python |
| **Key Features** | OpenAI-compatible API, LLM agent orchestration, vector search, web search, user auth, API key management |
| **Health Check** | `curl -f http://localhost:8000/health` (30s interval) |
| **Inter-service URL** | `http://tatlock:8000` (from other containers) |
---
### Webber
Webber is an LLM agent orchestration API that provides autonomous agent execution with tool use capabilities, enabling complex multi-step tasks through natural language instructions. It connects to Ollama for LLM inference and embedding models, authenticates users via the Tatlock API, and manages agent sessions with configurable TTL and context limits. The service supports sandboxed tool execution with configurable timeouts and allowed paths, making it suitable for agentic workflows that require code execution or file manipulation. Part of the unified agents stack alongside Tatlock.
| Property | Value |
|----------|-------|
| **Image** | `git.schweitz.net/jpmschweitzer/webber:latest` |
| **Container Name** | `webber` |
| **Stack** | `agents` (unified agents stack) |
| **Access URL (LAN)** | http://192.168.86.149:8086 |
| **External Access** | LAN only (internal API) |
| **Port Mapping** | 8086:8086 (HTTP) |
| **Network Mode** | Bridge (docker-dataplane) |
| **Restart Policy** | `unless-stopped` |
| **Volume Mounts** | `~/docker-data/webber/logs:/app/logs`, `~/docker-data/webber/sandbox:/app/sandbox` |
| **Environment** | `OLLAMA_URL=http://ollama:11434`, `OLLAMA_AGENT_MODEL=mistral-nemo:latest`, `OLLAMA_EMBED_MODEL=nomic-embed-text:latest`, `TATLOCK_API_URL=http://tatlock:8000`, `TOOL_TIMEOUT_SECONDS=30`, `SESSION_TTL_HOURS=24`, `MAX_CONTEXT_TOKENS=8192` |
| **Source Repository** | https://git.schweitz.net/jpmschweitzer/webber |
| **Resource Limits** | CPU: 1.0, Memory: 1GB limit, 256MB reservation |
| **GPU Required** | No (uses Ollama for GPU inference) |
| **Redis DB** | DB 9 (sessions) |
| **Dependencies** | Ollama, Tatlock (auth), Redis Shared |
| **Framework** | FastAPI, Python |
| **Key Features** | Autonomous agent execution, tool use, sandboxed code execution, session management, embedding support |
| **Health Check** | `curl -f http://localhost:8086/health` (30s interval) |
| **Inter-service URL** | `http://webber:8086` (from other containers) |
---
### Jellyfin
@@ -716,6 +777,7 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is
| **Headscale** | http://192.168.86.149:8085 | No | VPN control server |
| **Open WebUI** | https://webui.schweitz.net | Yes (SSO) | LLM chat interface |
| **Tatlock** | https://tatlock.schweitz.net | Yes (SSO) | AI orchestration API |
| **Webber** | http://192.168.86.149:8086 | No | LLM agent execution API |
| **Core API** | http://192.168.86.149:8083 | No | API functions & infrastructure mgmt |
| **Jellyfin** | https://media.schweitz.net | Yes | Media streaming |
| **Sonarr** | http://192.168.86.149:8989 | No | TV show automation |
@@ -764,6 +826,8 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is
| **Gitea** | `~/docker-data/gitea/` | N/A | Data: ~100MB, DB: ~50MB, Repos: variable |
| **Samba** | `~/docker-data/samba/` | Mounts: `/mnt/media/` (shares) | Config: ~5MB |
| **Home Assistant** | `~/docker-data/home-assistant/config/` | N/A | Config: ~100MB, DB: ~50MB |
| **Tatlock** | `~/docker-data/tatlock/logs/` | N/A | Logs: ~10MB |
| **Webber** | `~/docker-data/webber/logs/`, `~/docker-data/webber/sandbox/` | N/A | Logs: ~10MB, Sandbox: variable |
| **Tatlock UI** | None (stateless) | N/A | ~0MB (static files in container) |
**SSD Usage (docker-data):** ~6-11GB (configs, caches, databases)
@@ -777,7 +841,7 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is
| Network Name | Containers | Purpose |
|--------------|------------|---------|
| **docker-dataplane** | Ollama, Open WebUI, Core API, Qdrant, PostgreSQL Shared, Redis Shared, Headscale, Nextcloud, Gitea, Samba, Watchtower, Tatlock UI, Home Assistant, Jellyfin, Sonarr, Radarr, Prowlarr, SABnzbd | Unified service mesh for all containerized applications |
| **docker-dataplane** | Ollama, Open WebUI, Core API, Qdrant, PostgreSQL Shared, Redis Shared, Headscale, Nextcloud, Gitea, Samba, Watchtower, Tatlock, Webber, Tatlock UI, Home Assistant, Jellyfin, Sonarr, Radarr, Prowlarr, SABnzbd | Unified service mesh for all containerized applications |
| **host** | Portainer, NPM | Direct host port access for infrastructure management |
**Benefits of Consolidation**:
@@ -814,4 +878,4 @@ redis-cli -h redis-shared # Redis connection
---
*Last Updated: 2026-01-04*
*Last Updated: 2026-01-09*
+147
View File
@@ -0,0 +1,147 @@
version: '3.8'
# Agents Stack - LLM Agent Services
# Purpose: AI agent orchestration APIs (Tatlock + Webber)
# Ports: 8000 (Tatlock), 8086 (Webber)
# Network: docker-dataplane
# Images: git.schweitz.internal/jpmschweitzer/tatlock, git.schweitz.internal/jpmschweitzer/webber
services:
# ============================================
# Tatlock - The Homelab Butler
# OpenAI-compatible API with LLM agent orchestration
# Port: 8000
# ============================================
tatlock:
image: git.schweitz.internal/jpmschweitzer/tatlock:latest
container_name: tatlock
restart: unless-stopped
ports:
- "8000:8000"
environment:
- ENVIRONMENT=production
- API_HOST=0.0.0.0
- API_PORT=8000
# Ollama (shared service)
- OLLAMA_HOST=http://ollama:11434
- OLLAMA_DEFAULT_MODEL=mistral-nemo:latest
- OLLAMA_TIMEOUT=120
# SearXNG (optional, shared service)
- SEARXNG_HOST=http://searxng:8080
- SEARXNG_TIMEOUT=30
# Qdrant (vector database)
- QDRANT_HOST=qdrant
- QDRANT_PORT=6333
# Redis (shared service)
- REDIS_HOST=redis-shared
- REDIS_PORT=6379
- REDIS_MEMORY_DB=1
- REDIS_BENCHMARK_DB=6
- REDIS_TIMEOUT=5
# Features
- ENABLE_BENCHMARKS=true
- CORS_ORIGINS=["*"]
- PYTHONPATH=/app
# Library Desk API
- LIBRARY_DESK_HOST=http://library-desk:8089
- LIBRARY_DESK_API_KEY=${LIBRARY_API_KEY}
volumes:
- /home/jpmschweitzer/docker-data/tatlock/logs:/app/logs
networks:
- docker-dataplane
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
memory: 256M
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# ============================================
# Webber - LLM Agent Orchestration API
# Autonomous agent execution with tool use
# Port: 8086
# ============================================
webber:
image: git.schweitz.internal/jpmschweitzer/webber:latest
container_name: webber
restart: unless-stopped
ports:
- "8086:8086"
environment:
# App settings
- DEBUG=false
- LOG_LEVEL=INFO
- PYTHONPATH=/app
# Server config
- HOST=0.0.0.0
- PORT=8086
# CORS
- CORS_ORIGINS=["*"]
- CORS_METHODS=["*"]
- CORS_HEADERS=["*"]
# Ollama (shared service) - LLM Models hot in VRAM
- OLLAMA_URL=http://ollama:11434
- OLLAMA_AGENT_MODEL=mistral-nemo:latest
- OLLAMA_EMBED_MODEL=nomic-embed-text:latest
# Auth - Tatlock API for user validation
- TATLOCK_API_URL=http://tatlock:8000
- TATLOCK_API_KEY=${WEBBER_TATLOCK_API_KEY}
# Tool execution settings
- TOOL_TIMEOUT_SECONDS=30
- SANDBOX_ENABLED=true
- ALLOWED_PATHS=/app/sandbox,/tmp
# Session management
- SESSION_TTL_HOURS=24
- MAX_CONTEXT_TOKENS=8192
# Redis (shared service)
- REDIS_HOST=redis-shared
- REDIS_PORT=6379
- REDIS_DB=9
volumes:
- /home/jpmschweitzer/docker-data/webber/logs:/app/logs
- /home/jpmschweitzer/docker-data/webber/sandbox:/app/sandbox
networks:
- docker-dataplane
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
memory: 256M
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8086/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
docker-dataplane:
external: true
name: docker-dataplane
+7 -21
View File
@@ -20,6 +20,7 @@ services:
# Application
- APP_NAME=Core API
- DEBUG=false
- OIDC_ENABLED=true
# Server
- HOST=0.0.0.0
@@ -28,22 +29,6 @@ services:
# Logging
- LOG_LEVEL=INFO
# Model Configuration
- DEFAULT_MODEL=gemma:7b
- LIGHTWEIGHT_MODELS=gemma:2b,gemma:7b
- HEAVY_MODELS=mistral:7b,gemma2:9b,mixtral:8x7b
- CODE_MODELS=codestral:latest,codegemma:latest
# Model Aliases (OpenAI → Local)
- ALIAS_GPT35=gemma:7b
- ALIAS_GPT4=mistral:7b
- ALIAS_GPT4_TURBO=mixtral:8x7b
- ALIAS_GPT4_CODE=codestral:latest
# Agent Configuration
# Set to "false" to make agent errors explicit (500 errors) instead of silent fallback.
- AGENT_FALLBACK_ENABLED=false
# Portainer API
- PORTAINER_URL=http://192.168.86.149:8001
- PORTAINER_API_KEY=${PORTAINER_API_KEY}
@@ -69,12 +54,16 @@ services:
- HOMEASSISTANT_TOKEN=${HOMEASSISTANT_TOKEN}
# AI SYSTEM VARIABLES
- OLLAMA_BASE_URL=http://192.168.86.149:11434
- OLLAMA_TIMEOUT=300
- SEARXNG_URL=http://192.168.86.149:8080
- QDRANT_HOST=192.168.86.149
- QDRANT_PORT=6333
# Python path
- PYTHONPATH=/app
- PSUTIL_PROCFS_PATH=/host/proc
- HOSTFS_ROOT=/hostfs
volumes:
# Data volumes only - no source code
- /home/jpmschweitzer/docker-data/core-api/logs:/app/logs
@@ -83,9 +72,6 @@ services:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/hostfs:ro
environment:
- PSUTIL_PROCFS_PATH=/host/proc
- HOSTFS_ROOT=/hostfs
deploy:
resources:
+9 -45
View File
@@ -1,33 +1,13 @@
version: '3.8'
# Gitea - Self-Hosted Git Service with Database
# Gitea - Self-Hosted Git Service
# Application Layer
# Ports: 3002 (HTTP), 2222 (SSH)
# GPU: No
# Storage: SSD (repositories, database)
# Storage: SSD (repositories)
# Database: postgres-shared (gitea database, gitea_user)
services:
gitea-db:
image: postgres:14-alpine
container_name: gitea-db
restart: unless-stopped
volumes:
# Database on SSD for performance
- /home/jpmschweitzer/docker-data/gitea/db:/var/lib/postgresql/data
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=gPdM7QV4gvotE9f9lGS4yj
- POSTGRES_DB=gitea
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gitea -d gitea"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
- docker-dataplane
gitea:
image: gitea/gitea:latest
container_name: gitea
@@ -44,10 +24,10 @@ services:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=gitea-db:5432
- GITEA__database__HOST=postgres-shared:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=gPdM7QV4gvotE9f9lGS4yj
- GITEA__database__USER=gitea_user
- GITEA__database__PASSWD=cCav64d76NX1zdEEAbVOM9uvao14aY8HojjNdxsSpMM=
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://git.schweitz.internal/api/healthz || exit 1"]
@@ -55,8 +35,6 @@ services:
timeout: 10s
retries: 3
start_period: 60s
depends_on:
- gitea-db
networks:
- docker-dataplane
@@ -89,24 +67,10 @@ networks:
external: true
name: docker-dataplane
# ⚠️ SECURITY WARNING:
# Change POSTGRES_PASSWORD and GITEA__database__PASSWD before deploying!
# Use a strong, unique password.
# Database: Uses postgres-shared stack (gitea database, gitea_user)
# Ensure postgres-shared is running before deploying this stack.
#
# After Deployment:
# 1. Access http://localhost:3002
# 2. First-time setup:
# - Server domain: localhost:3002 or your domain
# - SSH server domain: localhost
# - SSH server port: 2222 (external)
# - HTTP listen port: 3000 (internal)
# - Application URL: http://localhost:3002 or https://git.schweitz.net (if using NPM)
# - Database: PostgreSQL (pre-configured via environment variables)
# - Create admin account (strong password!)
# 3. Configure additional settings:
# - Email settings (optional)
# - Enable/disable user registration
# - Configure webhooks and integrations
# Access: http://localhost:3002 or https://git.schweitz.net (via NPM)
#
# SSH Git Clone Usage:
# git clone ssh://git@localhost:2222/username/repo.git
-7
View File
@@ -38,13 +38,6 @@ services:
timeout: 10s
retries: 3
start_period: 60s
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu, video, compute, utility]
networks:
- docker-dataplane
-73
View File
@@ -1,73 +0,0 @@
version: '3.8'
# Tatlock - The Homelab Butler
# Purpose: OpenAI-compatible API server with LLM agent orchestration
# Port: 8000 (API)
# Network: docker-dataplane
# Image: git.schweitz.internal/jpmschweitzer/tatlock (internal registry)
services:
tatlock:
image: git.schweitz.internal/jpmschweitzer/tatlock:latest
container_name: tatlock
restart: unless-stopped
ports:
- "8000:8000"
environment:
- ENVIRONMENT=production
- API_HOST=0.0.0.0
- API_PORT=8000
# Ollama (shared service)
- OLLAMA_HOST=http://ollama:11434
- OLLAMA_DEFAULT_MODEL=mistral-nemo:latest
- OLLAMA_TIMEOUT=120
# SearXNG (optional, shared service)
- SEARXNG_HOST=http://searxng:8080
- SEARXNG_TIMEOUT=30
# Qdrant (vector database)
- QDRANT_HOST=qdrant
- QDRANT_PORT=6333
# Redis (shared service)
- REDIS_HOST=redis-shared
- REDIS_PORT=6379
- REDIS_MEMORY_DB=1
- REDIS_BENCHMARK_DB=6
- REDIS_TIMEOUT=5
# Features
- ENABLE_BENCHMARKS=true
- CORS_ORIGINS=["*"]
- PYTHONPATH=/app
# Library Desk API
- LIBRARY_DESK_HOST=http://library-desk:8089
- LIBRARY_DESK_API_KEY=${LIBRARY_API_KEY}
volumes:
- /home/jpmschweitzer/docker-data/tatlock/logs:/app/logs
networks:
- docker-dataplane
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
memory: 256M
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
docker-dataplane:
external: true
name: docker-dataplane