Compare commits

..
21 Commits
Author SHA1 Message Date
jpmschweitzerandClaude 2b544ae6aa chore(stacks): remove the deprecated stack copies
This repo was merged into system-admin-toj/containers/ and the copies
here kept drifting. Stale infrastructure config is not inert: agents.yml
here still carried OLLAMA_DEFAULT_MODEL=mistral-nemo:latest months after
the live stack moved to gemma4:e2b, and that setting is what caused the
2026-08-07 outage — mistral-nemo held 9,262 MiB of an 11,264 MiB card,
Whisper got 7 MiB, and Speaches returned CUDA OOM for hours while
reporting healthy. Anyone deploying from this directory would have
reproduced it exactly.

The files stay in this repo's history; README.md explains how to read one
back and points at the live location.

Two had no counterpart in system-admin-toj and were never deployed:
appwrite.yml, added in the most recent commit, and penpot.yml, whose
service is decommissioned. Both are recoverable from history if wanted;
neither should come back to this directory.

Left alone deliberately: an uncommitted CONTAINERS.md edit, which is
someone's unfinished work and would not have survived a delete.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-08 17:00:48 +02:00
jpmschweitzerandClaude Opus 4.6 57139f0f54 feat(stack): add Appwrite BaaS stack for dev/testing
Self-contained stack with 25 containers: API server, console, realtime
WebSocket server, 10 workers, 3 schedulers, maintenance task, browser,
OpenRuntimes executor, plus internal MariaDB 10.11 and Redis 7.4.
Exposed on port 8093 via bundled Traefik. Watchtower disabled for
deliberate upgrades.

Also includes media.yml reformatting and Jellyfin GPU deploy block.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 12:09:42 +02:00
jpmschweitzerandClaude Opus 4.6 4520f627ba ops(scheduler): add GCS credentials mount for offsite backups
Mount secrets directory and GCS_CREDENTIALS_FILE env var to support
the new gcs_backup_executor in scheduler v1.2.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 09:28:54 +02:00
jpmschweitzerandClaude Opus 4.6 73b70629bf docs(containers): remove obsolete Shell In A Box, update counts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:39:18 +01:00
jpmschweitzerandClaude Opus 4.6 b7cca51d7a docs(stacks): update README for models stack and Matter Server
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:39:00 +01:00
jpmschweitzerandClaude Opus 4.6 a4379fe373 feat(stack): consolidate Ollama, Stable Audio, and TRELLIS into models stack
Merge three individual GPU service stacks into a unified models.yml.
All services share the RTX 2080 Ti and docker-dataplane network.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:38:52 +01:00
jpmschweitzerandClaude Opus 4.6 cfb6cea452 docs: add project handover notes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:35:09 +01:00
jpmschweitzerandClaude Opus 4.6 d0f06918ea docs(containers): add Stable Audio and TRELLIS, update service counts
- Add full profiles for Stable Audio and TRELLIS services
- Update quick reference table, GPU services, and storage distribution
- Add new stacks to README port allocation and GPU section
- Update total services to 37 across 23 stacks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:34:49 +01:00
jpmschweitzerandClaude Opus 4.6 bc1574b36e feat(stack): add Stable Audio and TRELLIS GPU service stacks
- Stable Audio Open: AI audio generation on port 11500 (~6GB VRAM)
- TRELLIS: 3D model generation on port 11510 (~6-8GB VRAM, low-VRAM fork)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:34:34 +01:00
jpmschweitzerandClaude Opus 4.6 827dffe164 fix(penpot): update assets storage config for Penpot 2.11+
Rename PENPOT_ASSETS_STORAGE_* to PENPOT_OBJECTS_STORAGE_* per upstream changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:34:27 +01:00
jpmschweitzerandClaude Opus 4.6 71cd913d05 fix(core-api): use IP address for Home Assistant URL
Container name resolution doesn't work for host-networked services.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:34:19 +01:00
jpmschweitzerandClaude Opus 4.6 aa54af8a87 config(agents): add Anthropic API config for Tatlock and Webber
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:34:14 +01:00
jpmschweitzerandClaude Opus 4.6 dd141099e1 config(ollama): reduce VRAM usage for GPU sharing with other services
Change keep-alive from infinite to 5m and max loaded models from 2 to 1,
freeing VRAM for Stable Audio, TRELLIS, and other GPU services.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:34:09 +01:00
jpmschweitzerandClaude Opus 4.6 ec8c3f5925 feat(home-assistant): switch to host networking and add Matter Server
- Switch HA from bridge to host networking for better device discovery
- Add python-matter-server container for Matter protocol support
- Both services share host network for mDNS/IPv6 multicast
- Update NPM forward hostname to use IP address
- Add Matter Server setup instructions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:33:49 +01:00
jpmschweitzerandClaude Opus 4.6 24a70d9527 fix(agents): correct RAM spec from 16GB to 64GB
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:33:42 +01:00
jpmschweitzerandClaude Opus 4.6 9ac817bb6e chore(gitignore): ignore local Claude settings and Jupyter checkpoints
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:33:35 +01:00
jpmschweitzerandClaude Opus 4.5 cd3cdad11c docs(containers): add Shell In A Box host service
Add shellinabox web-based terminal emulator running on port 4200,
accessible via https://shell.schweitz.net through NPM reverse proxy.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-12 17:55:10 +01:00
jpmschweitzerandClaude Opus 4.5 27e7d9aff0 docs(home-assistant): add MCP server setup guide for Claude integration
Documents the configuration steps for connecting Claude.ai and Claude Desktop
to Home Assistant via the Model Context Protocol (MCP) server endpoint.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 09:12:35 +01:00
jpmschweitzerandClaude Opus 4.5 653cd2771e docs(penpot): update status and correct port/healthcheck info
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 12:43:23 +01:00
jpmschweitzerandClaude Opus 4.5 f54b6cff08 fix(penpot): correct port mappings, healthchecks, and feature flags
- Fix frontend port mapping (8080, not 80)
- Fix healthcheck endpoints to use /readyz
- Add secret key to exporter service
- Disable email verification (no SMTP configured)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 12:40:19 +01:00
jpmschweitzerandClaude Opus 4.5 7d61fab2b7 feat(stack): add Penpot design platform deployment
Add self-hosted Penpot (Figma alternative) with:
- Docker stack using shared PostgreSQL and Redis infrastructure
- Authentik SSO integration (OIDC, password login disabled)
- Three services: frontend, backend, exporter
- Port 9001 for web UI, external via penpot.schweitz.net

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 11:33:03 +01:00
33 changed files with 551 additions and 2738 deletions
-17
View File
@@ -1,17 +0,0 @@
{
"permissions": {
"allow": [
"WebSearch",
"Bash(docker logs:*)",
"Bash(docker ps:*)",
"Bash(docker inspect:*)",
"Bash(tree:*)",
"Bash(nvidia-smi:*)",
"Bash(find:*)",
"Bash(grep:*)",
"Bash(cat:*)"
],
"deny": [],
"ask": []
}
}
+4
View File
@@ -70,6 +70,10 @@ docker-compose.override.yml
# Local configuration (machine-specific) # Local configuration (machine-specific)
local.yml local.yml
local.config local.config
.claude/settings.local.json
# Jupyter notebook checkpoints
.ipynb_checkpoints/
# Archive files # Archive files
*.zip *.zip
+1 -1
View File
@@ -17,7 +17,7 @@ This is the `tower-of-joy` project - a containerized home server infrastructure
**Purpose:** Self-hosted services platform with GPU-accelerated ML model serving, media streaming, cloud storage, and secure remote access. **Purpose:** Self-hosted services platform with GPU-accelerated ML model serving, media streaming, cloud storage, and secure remote access.
**System:** Intel i7-6700, RTX 2080 Ti (11GB VRAM), 16GB RAM, Zorin OS 16.3 (Ubuntu 20.04 based) **System:** Intel i7-6700, RTX 2080 Ti (11GB VRAM), 64GB RAM, Zorin OS 16.3 (Ubuntu 20.04 based)
**Storage Architecture:** **Storage Architecture:**
- **SSD (489GB):** Container configs, databases, Docker images - `/home/jpmschweitzer/docker-data/` - **SSD (489GB):** Container configs, databases, Docker images - `/home/jpmschweitzer/docker-data/`
+111 -7
View File
@@ -1,7 +1,7 @@
# Container Reference - tower-of-joy Infrastructure # Container Reference - tower-of-joy Infrastructure
> **Last Updated:** 2026-01-22 > **Last Updated:** 2026-02-26
> **Total Services:** 31 containers across 20 stacks > **Total Services:** 36 containers across 21 stacks
> **System:** Intel i7-6700, RTX 2080 Ti (11GB VRAM), 64GB RAM, Zorin OS 16.3 > **System:** Intel i7-6700, RTX 2080 Ti (11GB VRAM), 64GB RAM, Zorin OS 16.3
--- ---
@@ -38,9 +38,12 @@
| **AMP (Game Server)** | 8080-8082 | https://amp.schweitz.net | Internet | No | - | ✅ Running | | **AMP (Game Server)** | 8080-8082 | https://amp.schweitz.net | Internet | No | - | ✅ Running |
| **Home Assistant** | 8123 | https://housekeeping.schweitz.net | Internet | No | - | ✅ Running | | **Home Assistant** | 8123 | https://housekeeping.schweitz.net | Internet | No | - | ✅ Running |
| **Paperless-ngx** | 8091 | https://documents.schweitz.net | Internet | No | 8 | ✅ Running | | **Paperless-ngx** | 8091 | https://documents.schweitz.net | Internet | No | 8 | ✅ Running |
| **Penpot** | 9001 | https://penpot.schweitz.net | Internet (SSO) | No | 10 | ✅ Running |
| **ClamAV** | 3310 | N/A (host service) | No | No | - | ✅ Running | | **ClamAV** | 3310 | N/A (host service) | No | No | - | ✅ Running |
| **AdGuard Home** | 53, 3053 | http://dns.schweitz.internal | LAN (DNS) | No | - | ✅ Running | | **AdGuard Home** | 53, 3053 | http://dns.schweitz.internal | LAN (DNS) | No | - | ✅ Running |
| **Tatlock UI** | 9999 | https://home.schweitz.net | Internet | No | - | ✅ Running | | **Tatlock UI** | 9999 | https://home.schweitz.net | Internet | No | - | ✅ Running |
| **Stable Audio** | 11500 | http://192.168.86.149:11500 | LAN | Yes (RTX 2080 Ti) | - | ⏸️ Not Deployed |
| **TRELLIS** | 11510 | http://192.168.86.149:11510 | LAN | Yes (RTX 2080 Ti) | - | ✅ Running |
### External Domains (SSL via Let's Encrypt) ### External Domains (SSL via Let's Encrypt)
- **home.schweitz.net** → Tatlock UI - **home.schweitz.net** → Tatlock UI
@@ -53,6 +56,7 @@
- **amp.schweitz.net** → AMP Game Server - **amp.schweitz.net** → AMP Game Server
- **housekeeping.schweitz.net** → Home Assistant - **housekeeping.schweitz.net** → Home Assistant
- **documents.schweitz.net** → Paperless-ngx - **documents.schweitz.net** → Paperless-ngx
- **penpot.schweitz.net** → Penpot (Protected by Authentik SSO)
- **library.schweitz.net** → Wiki.js - **library.schweitz.net** → Wiki.js
- **tatlock.schweitz.net** → Tatlock API (Protected by Authentik SSO) - **tatlock.schweitz.net** → Tatlock API (Protected by Authentik SSO)
- **webui.schweitz.net** → Open WebUI (Protected by Authentik SSO) - **webui.schweitz.net** → Open WebUI (Protected by Authentik SSO)
@@ -71,6 +75,7 @@ Internal domains provide LAN-accessible URLs without SSL or Authentik, ideal for
| amp.schweitz.internal | localhost | 8080 | | amp.schweitz.internal | localhost | 8080 |
| housekeeping.schweitz.internal | localhost | 8123 | | housekeeping.schweitz.internal | localhost | 8123 |
| documents.schweitz.internal | 192.168.86.149 | 8091 | | documents.schweitz.internal | 192.168.86.149 | 8091 |
| penpot.schweitz.internal | localhost | 9001 |
| git.schweitz.internal | localhost | 3002 | | git.schweitz.internal | localhost | 3002 |
| library.schweitz.internal | localhost | 8088 | | library.schweitz.internal | localhost | 8088 |
| tatlock.schweitz.internal | localhost | 8000 | | tatlock.schweitz.internal | localhost | 8000 |
@@ -158,8 +163,8 @@ PostgreSQL Shared is a centralized PostgreSQL 17 database server providing isola
| **Resource Limits** | None | | **Resource Limits** | None |
| **GPU Required** | No | | **GPU Required** | No |
| **Dependencies** | docker-dataplane network | | **Dependencies** | docker-dataplane network |
| **Databases** | `authentik` (Authentik SSO), `gitea` (Git hosting), `paperless` (Document management), `system_settings` (Central Tatlock settings), `sonarr` (TV management), `radarr` (Movie management), `prowlarr` (Indexer management), `postgres` (default/admin) | | **Databases** | `authentik` (Authentik SSO), `gitea` (Git hosting), `paperless` (Document management), `penpot` (Design platform), `system_settings` (Central Tatlock settings), `sonarr` (TV management), `radarr` (Movie management), `prowlarr` (Indexer management), `postgres` (default/admin) |
| **Database Users** | `authentik_user`, `gitea_user`, `paperless_user`, `settings` (system_settings RW), `media_user` (sonarr/radarr/prowlarr), `postgres` (superuser) | | **Database Users** | `authentik_user`, `gitea_user`, `paperless_user`, `penpot_user`, `settings` (system_settings RW), `media_user` (sonarr/radarr/prowlarr), `postgres` (superuser) |
| **Health Check** | `pg_isready -U postgres` (30s interval) | | **Health Check** | `pg_isready -U postgres` (30s interval) |
| **Backup Strategy** | `/backups` volume for pg_dump exports | | **Backup Strategy** | `/backups` volume for pg_dump exports |
@@ -185,7 +190,7 @@ Redis Shared is a centralized Redis 7 key-value store providing cache, session s
| **Resource Limits** | None | | **Resource Limits** | None |
| **GPU Required** | No | | **GPU Required** | No |
| **Dependencies** | docker-dataplane network | | **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: Webber (sessions), DB 10-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: Penpot, DB 11-15: Available |
| **Persistence** | AOF (Append-Only File) enabled for durability | | **Persistence** | AOF (Append-Only File) enabled for durability |
| **Health Check** | `redis-cli ping` returns PONG (30s interval) | | **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. | | **Connection String** | `redis://redis-shared:6379/0` (DB 0), `redis://redis-shared:6379/1` (DB 1), etc. |
@@ -707,6 +712,97 @@ Paperless-ngx is a document management system that transforms physical documents
--- ---
### Penpot
Penpot is an open-source design and prototyping platform, serving as a self-hosted alternative to Figma with support for real-time collaboration, vector editing, prototyping, and design handoff. It provides a web-based interface for creating UI/UX designs, wireframes, and interactive prototypes with features like components, auto-layout, and multi-page documents. The service uses Authentik SSO for authentication (password login disabled), stores design assets on the SSD, and leverages shared PostgreSQL and Redis infrastructure for data persistence and real-time collaboration via WebSockets.
| Property | Value |
|----------|-------|
| **Stack** | `penpot` |
| **Containers** | `penpot-frontend` (nginx), `penpot-backend` (Clojure API), `penpot-exporter` (PDF/SVG) |
| **Access URL (LAN)** | http://192.168.86.149:9001 |
| **Access URL (Public)** | https://penpot.schweitz.net |
| **Internal Domain** | http://penpot.schweitz.internal |
| **External Access** | Yes (via NPM reverse proxy with SSL + Authentik SSO) |
| **Port Mapping** | 9001:8080 (HTTP) |
| **Network Mode** | Bridge (docker-dataplane) |
| **Restart Policy** | `unless-stopped` |
| **Volume Mounts** | `~/docker-data/penpot/assets:/opt/data/assets` (SSD - uploads, exports) |
| **Environment** | `PENPOT_PUBLIC_URI=https://penpot.schweitz.net`, `PENPOT_DATABASE_URI=postgresql://postgres-shared:5432/penpot`, `PENPOT_REDIS_URI=redis://redis-shared:6379/10`, `TZ=Europe/Amsterdam` |
| **Resource Limits** | Backend: 1GB memory, Exporter: 1GB memory |
| **GPU Required** | No |
| **Database** | PostgreSQL `penpot` on postgres-shared (user: penpot_user) |
| **Redis DB** | DB 10 (cache + WebSocket coordination) |
| **Dependencies** | PostgreSQL Shared, Redis Shared, Authentik (SSO), NPM (reverse proxy) |
| **Authentication** | Authentik OIDC only (password login disabled) |
| **Health Check** | Frontend: `curl http://localhost:8080`, Backend: `curl http://localhost:6060/readyz`, Exporter: `curl http://localhost:6061/readyz` |
| **WebSocket Support** | Required (enabled via NPM for real-time collaboration) |
| **Features** | Vector editing, prototyping, components, auto-layout, multi-page, real-time collaboration, PDF/SVG export |
---
### Stable Audio
Stable Audio Open is an AI audio generation model from Stability AI that creates music and sound effects from text prompts. It generates up to 47 seconds of audio using diffusion techniques, with a Gradio web interface for easy interaction. The service uses GPU acceleration for inference and runs as a locally-built container image, storing model weights in a HuggingFace cache on the SSD.
| Property | Value |
|----------|-------|
| **Image** | `stable-audio-open:local` (locally built) |
| **Container Name** | `stable-audio` |
| **Access URL (LAN)** | http://192.168.86.149:11500 |
| **External Access** | LAN only |
| **Port Mapping** | 11500:8000 (Gradio Web UI) |
| **Network Mode** | Bridge (docker-dataplane) |
| **Restart Policy** | `unless-stopped` |
| **Volume Mounts** | `~/docker-data/stable-audio/hf-cache:/root/.cache/huggingface` (SSD - model cache ~6GB) |
| **Environment** | `NVIDIA_VISIBLE_DEVICES=all`, `NVIDIA_DRIVER_CAPABILITIES=compute,utility`, `HF_TOKEN=<required>`, `TZ=Europe/Amsterdam` |
| **Resource Limits** | Memory: 16GB limit, 8GB reservation |
| **GPU Required** | Yes (RTX 2080 Ti - ~6GB VRAM) |
| **Dependencies** | NVIDIA Container Toolkit, HuggingFace token |
| **Health Check** | `curl -fSs http://localhost:8000/` (60s interval, 300s start_period) |
| **Build Location** | `/home/jpmschweitzer/docker-data/stable-audio-open/` |
| **Source** | https://github.com/SaladTechnologies/stable-audio-open |
| **Features** | Text-to-audio, up to 47s generation, configurable diffusion steps, CFG scale |
**Prerequisites:**
1. Accept model license: https://huggingface.co/stabilityai/stable-audio-open-1.0
2. Create HuggingFace token with read access
3. Build image: `docker build -t stable-audio-open:local .`
---
### TRELLIS
TRELLIS is Microsoft's 3D model generation system that creates 3D assets from text or image inputs. This deployment uses the low-VRAM fork (0lento/TRELLIS) optimized for 11GB GPUs, using 6-8GB VRAM instead of the standard 16GB requirement. It outputs GLB meshes with UV mappings, suitable for game asset pipelines. The Gradio web interface allows interactive generation with configurable parameters.
| Property | Value |
|----------|-------|
| **Image** | `trellis:local` (locally built) |
| **Container Name** | `trellis` |
| **Access URL (LAN)** | http://192.168.86.149:11510 |
| **External Access** | LAN only |
| **Port Mapping** | 11510:7860 (Gradio Web UI) |
| **Network Mode** | Bridge (docker-dataplane) |
| **Restart Policy** | `unless-stopped` |
| **Volume Mounts** | `~/docker-data/trellis/hf-cache:/root/.cache/huggingface` (SSD - model cache ~5GB), `/mnt/media/trellis/outputs:/app/outputs` (HDD - generated GLB files) |
| **Environment** | `NVIDIA_VISIBLE_DEVICES=all`, `NVIDIA_DRIVER_CAPABILITIES=compute,utility`, `ATTN_BACKEND=xformers`, `SPCONV_ALGO=native`, `PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True`, `HF_TOKEN=<optional>`, `TZ=Europe/Amsterdam` |
| **Resource Limits** | Memory: 16GB limit, 4GB reservation |
| **GPU Required** | Yes (RTX 2080 Ti - 6-8GB VRAM with low-VRAM fork) |
| **Dependencies** | NVIDIA Container Toolkit |
| **Health Check** | `curl -fSs http://localhost:7860/` (60s interval, 300s start_period) |
| **Build Location** | `/home/jpmschweitzer/docker-data/trellis/` |
| **Source** | https://github.com/0lento/TRELLIS (low-vram branch) |
| **Output Format** | GLB with mesh geometry, UV mappings, and textures |
| **Features** | Text-to-3D, image-to-3D, GLB export with UVs, Blender-compatible output |
**Use Case:** Game asset pipeline - generate 3D meshes with UVs, import into Blender, apply custom textures, render isometric sprites.
**Prerequisites:**
1. Create directories: `mkdir -p ~/docker-data/trellis/{hf-cache,config} /mnt/media/trellis/outputs`
2. Build image: `cd ~/docker-data/trellis && docker build -t trellis:local .` (~20-30 min)
---
### ClamAV ### ClamAV
ClamAV is an open-source antivirus engine running on the host OS, providing virus scanning capabilities for the entire server and accessible via TCP socket for container-based services like Paperless-ngx. It includes automatic virus definition updates via freshclam and can perform both on-demand and real-time scanning. Running on the host provides better security isolation than containerized scanning and allows scanning of the host filesystem directly. ClamAV is an open-source antivirus engine running on the host OS, providing virus scanning capabilities for the entire server and accessible via TCP socket for container-based services like Paperless-ngx. It includes automatic virus definition updates via freshclam and can perform both on-demand and real-time scanning. Running on the host provides better security isolation than containerized scanning and allows scanning of the host filesystem directly.
@@ -819,6 +915,7 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is
| **Home Assistant** | https://housekeeping.schweitz.net | Yes | Smart home automation | | **Home Assistant** | https://housekeeping.schweitz.net | Yes | Smart home automation |
| **Tatlock UI** | https://home.schweitz.net | Yes | Home lab dashboard | | **Tatlock UI** | https://home.schweitz.net | Yes | Home lab dashboard |
| **AMP** | https://amp.schweitz.net | Yes | Game server management | | **AMP** | https://amp.schweitz.net | Yes | Game server management |
| **Penpot** | https://penpot.schweitz.net | Yes (SSO) | Design & prototyping |
| **Watchtower** | N/A (background) | N/A | Auto-updates | | **Watchtower** | N/A (background) | N/A | Auto-updates |
--- ---
@@ -829,9 +926,13 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is
|---------|-----------|-------------------|---------| |---------|-----------|-------------------|---------|
| **Ollama** | Compute, Utility | 2-10GB (model dependent) | LLM inference | | **Ollama** | Compute, Utility | 2-10GB (model dependent) | LLM inference |
| **Jellyfin** | Video Encode/Decode | ~1-2GB (during transcode) | Media transcoding | | **Jellyfin** | Video Encode/Decode | ~1-2GB (during transcode) | Media transcoding |
| **Stable Audio** | Compute | ~6GB | AI audio generation |
| **TRELLIS** | Compute | 6-8GB (low-VRAM fork) | 3D model generation |
**Total VRAM Available:** 11GB (RTX 2080 Ti) **Total VRAM Available:** 11GB (RTX 2080 Ti)
**Note:** Stable Audio, TRELLIS, and Ollama share GPU. For best results, stop competing services during heavy generation tasks.
--- ---
### Storage Distribution ### Storage Distribution
@@ -859,7 +960,10 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is
| **Home Assistant** | `~/docker-data/home-assistant/config/` | N/A | Config: ~100MB, DB: ~50MB | | **Home Assistant** | `~/docker-data/home-assistant/config/` | N/A | Config: ~100MB, DB: ~50MB |
| **Tatlock** | `~/docker-data/tatlock/logs/` | N/A | Logs: ~10MB | | **Tatlock** | `~/docker-data/tatlock/logs/` | N/A | Logs: ~10MB |
| **Webber** | `~/docker-data/webber/logs/`, `~/docker-data/webber/sandbox/` | N/A | Logs: ~10MB, Sandbox: variable | | **Webber** | `~/docker-data/webber/logs/`, `~/docker-data/webber/sandbox/` | N/A | Logs: ~10MB, Sandbox: variable |
| **Penpot** | `~/docker-data/penpot/assets/` | N/A | Assets: variable (uploads, exports) |
| **Tatlock UI** | None (stateless) | N/A | ~0MB (static files in container) | | **Tatlock UI** | None (stateless) | N/A | ~0MB (static files in container) |
| **Stable Audio** | `~/docker-data/stable-audio/hf-cache/` | N/A | Model cache: ~6GB |
| **TRELLIS** | `~/docker-data/trellis/hf-cache/` | `/mnt/media/trellis/outputs/` | Model cache: ~5GB, Outputs: variable |
**SSD Usage (docker-data):** ~6-11GB (configs, caches, databases) **SSD Usage (docker-data):** ~6-11GB (configs, caches, databases)
**HDD Usage (/mnt/media):** ~2.1TB / 3.6TB (58% used) **HDD Usage (/mnt/media):** ~2.1TB / 3.6TB (58% used)
@@ -872,7 +976,7 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is
| Network Name | Containers | Purpose | | Network Name | Containers | Purpose |
|--------------|------------|---------| |--------------|------------|---------|
| **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 | | **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, Penpot (frontend, backend, exporter) | Unified service mesh for all containerized applications |
| **host** | Portainer, NPM, AMP (amp-ads + game containers) | Direct host port access for infrastructure and game servers | | **host** | Portainer, NPM, AMP (amp-ads + game containers) | Direct host port access for infrastructure and game servers |
**Benefits of Consolidation**: **Benefits of Consolidation**:
@@ -909,4 +1013,4 @@ redis-cli -h redis-shared # Redis connection
--- ---
*Last Updated: 2026-01-09* *Last Updated: 2026-02-26*
+119
View File
@@ -0,0 +1,119 @@
# Handover: Add Swap Limits to Containers
## Problem
System swap is at 99% (2043/2047 MB) after 60 days uptime. All containers have `mem_swappiness` and `memswap_limit` unset, meaning when they hit their memory ceiling the kernel pushes pages to swap indefinitely instead of applying back-pressure. Over time this exhausts swap.
## Root Cause Analysis (2026-02-03)
Per-process swap audit identified these containers as the top offenders:
| Container | Swap Used | Mem Limit | File |
|-----------|-----------|-----------|------|
| authentik-worker | ~865 MB (across 5 worker procs) | 1G | `stacks/authentik.yml` |
| neo4j | 384 MB | 4G | `stacks/neo4j.yml` |
| penpot-backend | 372 MB | 1G | `stacks/penpot.yml` |
| authentik-server | 248 MB (gunicorn master+worker) | 512M | `stacks/authentik.yml` |
| open-webui | 129 MB | 1G | `stacks/open-webui.yml` |
| library-desk | 130 MB | 768M | `stacks/library-desk.yml` |
Additionally, host-level `clamd` (ClamAV) uses ~410 MB swap but is not managed by Portainer.
## Required Changes
For each container listed above, add `memswap_limit` equal to the `memory` limit and set `mem_swappiness: 0` under the top-level service config (not under `deploy`). This is Docker Compose v2 compatible syntax that works alongside v3 deploy blocks.
### stacks/authentik.yml
**authentik-server** — add to the service block (sibling to `deploy`, not nested inside it):
```yaml
server:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 512M
```
**authentik-worker** — same pattern:
```yaml
worker:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 1G
```
### stacks/neo4j.yml
```yaml
neo4j:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 4G
```
### stacks/penpot.yml
**penpot-backend** only (frontend and exporter are not swap offenders):
```yaml
penpot-backend:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 1G
```
### stacks/open-webui.yml
```yaml
open-webui:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 1G
```
### stacks/library-desk.yml
```yaml
library-desk:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 768M
```
## What These Settings Do
- `memswap_limit: X` — total memory+swap budget. Setting it equal to the memory limit means zero swap allowed.
- `mem_swappiness: 0` — tells the kernel to avoid swapping for this container unless absolutely necessary.
Together, these ensure containers are constrained to their RAM allocation. If they exceed it, the OOM killer handles it properly rather than silently filling swap.
## Deployment Notes
- These are runtime container settings — each stack needs to be redeployed in Portainer (or via `docker compose up -d`) for changes to take effect.
- After deploying, clear current swap: `sudo swapoff -a && sudo swapon -a`
- Monitor with: `cd /mnt/media/Projects/system-management && ./sysmon check memory`
- If any container starts OOM-killing after this change, its memory limit may need bumping. The most likely candidate is `authentik-worker` (was using 865 MB swap on top of its 1G RAM limit — may need 1.5G or 2G).
## ClamAV (Host Service)
ClamAV (`clamd`) is the single largest swap consumer at 410 MB. It runs as a host service, not a container. To limit it, edit `/etc/clamav/clamd.conf` or the systemd unit to set a memory ceiling. This is outside the scope of portainer-core but noted for completeness.
## Verification
After all stacks are redeployed and swap is cleared:
```bash
# Confirm swap is back to healthy levels
free -h
# Run system check
cd /mnt/media/Projects/system-management && ./sysmon check memory
# Verify no container is using swap
for cid in $(docker ps -q); do
name=$(docker inspect --format '{{.Name}}' "$cid")
swap=$(docker stats --no-stream --format '{{.MemUsage}}' "$cid")
echo "$name: $swap"
done
```
+191
View File
@@ -0,0 +1,191 @@
# Home Assistant MCP Server Setup Guide
> **Created:** 2026-02-06
> **Status:** Infrastructure Ready - Manual Configuration Required
This guide documents the setup of the Home Assistant MCP (Model Context Protocol) Server integration for use with Claude.ai and Claude Desktop.
---
## Current State
| Component | Status | Details |
|-----------|--------|---------|
| Home Assistant | Running | https://housekeeping.schweitz.net |
| MCP Endpoint | Active | https://housekeeping.schweitz.net/api/mcp |
| NPM Proxy | Configured | WebSocket support enabled, SSL via Let's Encrypt |
| Authentik | Not in path | Good - allows Home Assistant's built-in IndieAuth |
### Verified Configuration
**MCP Endpoint Test Results:**
- `GET /api/mcp` → `401 Unauthorized` (expected - requires OAuth)
- `OPTIONS /api/mcp` → CORS preflight working
- Allowed methods: GET, OPTIONS, POST
**NPM Configuration (proxy host ID 15):**
- Domain: `housekeeping.schweitz.net`
- Forward: `localhost:8123`
- WebSocket upgrade: Enabled
- SSL: Let's Encrypt (certificate ID 18)
- Advanced config includes WebSocket headers
---
## Step 1: Home Assistant Configuration
### 1.1 Verify MCP Server Integration is Enabled
The MCP endpoint is already responding, which suggests the integration may already be installed. Verify in Home Assistant:
1. Go to **Settings** → **Devices & Services**
2. Look for "Model Context Protocol Server" or "MCP Server"
3. If not present, click **Add Integration** and search for "MCP"
### 1.2 Expose Entities to Voice Assistants (Required)
The MCP server only exposes entities that are enabled for voice assistants. You must configure which entities Claude can access:
1. Go to **Settings** → **Voice assistants**
2. Click on **Assist** (the default assistant)
3. Go to the **Expose** tab
4. Enable the entities you want Claude to control:
- **Lights** - for lighting control
- **Switches** - for on/off devices
- **Climate** - for thermostats and HVAC
- **Covers** - for blinds, garage doors, etc.
- **Scenes** - for activating predefined scenes
- **Scripts** - for running custom automations
- **Media players** - for controlling media devices
- **Sensors** - for reading sensor values (read-only)
**Recommendation:** Start with a few entities to test, then expand once connectivity is verified.
### 1.3 (Optional) Generate Long-Lived Access Token
Only needed if using clients that don't support OAuth:
1. Click your user profile (your name in the sidebar)
2. Scroll down to **Long-lived access tokens**
3. Click **Create Token**
4. Name it (e.g., "Claude MCP Token")
5. Copy and save the token securely - it won't be shown again
---
## Step 2: Claude.ai Configuration
### For Claude.ai (Web Interface)
1. Go to [claude.ai](https://claude.ai) and sign in
2. Navigate to **Settings** → **Integrations** or **MCP Servers**
3. Click **Add Remote MCP Server**
4. Enter the following configuration:
- **Server URL:** `https://housekeeping.schweitz.net/api/mcp`
- **Name:** Home Assistant (or your preferred name)
5. Click **Connect** or **Add**
6. You'll be redirected to Home Assistant for OAuth authentication
7. Log in to Home Assistant and authorize Claude
8. Return to Claude.ai - the connection should now be active
### For Claude Desktop
1. Open Claude Desktop application
2. Go to **Settings** or **Preferences**
3. Navigate to **MCP Servers** or **Integrations**
4. Click **Add Remote Server**
5. Enter:
- **URL:** `https://housekeeping.schweitz.net/api/mcp`
6. Click **Connect**
7. A browser window will open for Home Assistant authentication
8. Log in and authorize the connection
9. Return to Claude Desktop - the server should appear as connected
---
## Step 3: Verification
### Test MCP Endpoint Accessibility
From command line:
```bash
# Should return 401 Unauthorized (expected without auth)
curl -I https://housekeeping.schweitz.net/api/mcp
# More detailed test
curl -v https://housekeeping.schweitz.net/api/mcp
```
### Test Claude Integration
After connecting Claude to Home Assistant, try these commands:
1. **List devices:** "What Home Assistant devices are available?"
2. **Check state:** "What's the current state of the living room lights?"
3. **Control device:** "Turn on the living room lights"
4. **Get sensor value:** "What's the current temperature inside?"
### Troubleshooting
| Issue | Possible Cause | Solution |
|-------|---------------|----------|
| Connection timeout | Network/proxy issue | Check NPM logs, verify WebSocket support |
| 401 Unauthorized | OAuth not completed | Re-authenticate through Claude settings |
| No devices visible | Entities not exposed | Enable entities in Voice assistants → Assist → Expose |
| Commands fail | Entity not exposed for control | Check entity exposure settings in Home Assistant |
---
## Technical Details
### How MCP Authentication Works
Home Assistant uses IndieAuth (an OAuth 2.0-based protocol) for MCP authentication:
1. Claude identifies as client ID `https://claude.ai`
2. Home Assistant validates the request and presents login
3. User authenticates and authorizes Claude
4. Home Assistant issues an access token
5. Claude uses the token for subsequent API calls
### Protocol
The MCP Server uses "Streamable HTTP" protocol:
- Requests: POST with JSON-RPC 2.0 payload
- Responses: Server-Sent Events (SSE) or direct JSON
### NPM Configuration Details
Current advanced config for `housekeeping.schweitz.net`:
```nginx
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
```
If you experience SSE streaming issues, add:
```nginx
proxy_buffering off;
proxy_cache off;
```
---
## Security Notes
1. **Authentik is NOT in front of Home Assistant** - This is intentional and required for IndieAuth OAuth to work directly with Claude
2. **Entity exposure is granular** - Only expose entities you want Claude to access
3. **OAuth tokens are temporary** - Re-authentication may be required periodically
4. **All traffic is encrypted** - SSL/TLS via Let's Encrypt
---
## References
- [Home Assistant MCP Documentation](https://www.home-assistant.io/integrations/mcp_server/)
- [Model Context Protocol Specification](https://spec.modelcontextprotocol.io/)
- [Claude MCP Integration](https://docs.anthropic.com/claude/docs/mcp)
---
*Last Updated: 2026-02-06*
+102
View File
@@ -0,0 +1,102 @@
# Tatlock Claudification Handover - portainer-core
## Context
Tatlock (the butler) is being upgraded to use Claude as its primary LLM backend instead of Ollama. This requires infrastructure changes to support the new configuration.
**Parent Issue:** See `/mnt/media/Projects/tatlock/PROJECT_CLAUDIFICATION.md`
## Impact on portainer-core
Portainer-core manages Docker Swarm stacks. The Tatlock stack needs updated configuration.
## Required Changes
### Priority: High (Required for Claude backend)
- [ ] **Update `stacks/agents.yml`** (or wherever Tatlock is defined)
Add new environment variables:
```yaml
services:
tatlock:
environment:
# Anthropic Configuration (Claude - preferred backend)
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- ANTHROPIC_MODEL=claude-sonnet-4-20250514
- PREFER_CLOUD_BACKEND=true
# Existing Ollama config remains as fallback
- OLLAMA_HOST=http://ollama:11434
- OLLAMA_DEFAULT_MODEL=mistral-nemo:latest
```
- [ ] **Configure secrets management**
Options:
1. **Docker secrets** (recommended for Swarm):
```yaml
secrets:
anthropic_api_key:
external: true
services:
tatlock:
secrets:
- anthropic_api_key
environment:
- ANTHROPIC_API_KEY_FILE=/run/secrets/anthropic_api_key
```
2. **Environment file** (simpler but less secure):
```yaml
services:
tatlock:
env_file:
- ./secrets/tatlock.env
```
- [ ] **Update `CONTAINERS.md`**
- Document new environment variables
- Note Claude as preferred backend with Ollama fallback
- Update any API documentation
### Priority: Medium (Phase 2 - MCP Server)
- [ ] **Add `tatlock-mcp` service definition** (when Phase 2 is ready)
```yaml
tatlock-mcp:
image: git.schweitz.net/jpmschweitzer/tatlock:latest
command: ["python", "-m", "src.mcp.server"]
ports:
- "8778:8778"
environment:
- MCP_AUTH_TOKEN=${MCP_AUTH_TOKEN}
# ... other config
```
### Priority: Low (Optimization)
- [ ] **Review resource limits**
- Claude backend may have different resource profile than Ollama
- Monitor memory/CPU after deployment
- Adjust limits if needed
## Testing
After stack update:
```bash
# Check Tatlock logs for backend selection
docker service logs tatlock_tatlock 2>&1 | grep -E "claude|backend"
# Should see:
# model_backend_configured backend=claude model=claude-sonnet-4-20250514
# OR (if no API key):
# claude_health_check_skipped reason=no_api_key
# model_backend_configured backend=ollama
```
## Timeline
- **Blocking:** Yes - required for production Claude deployment
- **When to implement:** When ANTHROPIC_API_KEY is available
- **Effort:** ~1 hour for stack updates, ~30 min for secrets setup
+23 -130
View File
@@ -1,138 +1,31 @@
# Docker Compose Stacks # Docker Compose Stacks — REMOVED
This directory contains version-controlled Docker Compose files for all services in the tower-of-joy infrastructure. **These files are gone deliberately. Do not restore them and do not deploy from
this directory.**
## Deployment The live stack definitions are in the `system-admin-toj` repo:
review the http://core-api/docs openapi documentation for infrastructure management REST endpoints. system-admin-toj/containers/stacks/*.yml
## Stack Inventory `portainer-core` was merged into `system-admin-toj/containers/` and is
deprecated. The copies that lived here kept drifting out of date, and stale
infrastructure config is not harmless: this directory's `agents.yml` still
carried `OLLAMA_DEFAULT_MODEL=mistral-nemo:latest` long after the live stack had
moved to `gemma4:e2b`. That exact setting caused the 2026-08-07 outage, where
mistral-nemo held 9,262 MiB of an 11,264 MiB card and Whisper was left with 7 MiB
— Speaches returned CUDA OOM for hours while its container reported healthy.
Anyone deploying from here would have reproduced it.
### Phase 1: Foundation Removed 2026-08-08. The files remain in this repo's git history if you need to
read one:
| Stack | File | Ports | GPU | Description | git log --oneline -- stacks/
|-------|------|-------|-----|-------------| git show <commit>:stacks/<name>.yml
| **Portainer** | `portainer.yml` | 8080, 8443 | No | Container management UI |
| **Nginx Proxy Manager** | `nginx-proxy-manager.yml` | 8000, 80, 443 | No | Reverse proxy and unified web interface |
| **Ollama** | `ollama.yml` | 11434 | **Yes** | ML model serving with GPU acceleration |
### Phase 2: Networking ## Two that had no counterpart
| Stack | File | Ports | GPU | Description | `appwrite.yml` and `penpot.yml` existed only here — neither is deployed, and
|-------|------|-------|-----|-------------| neither was migrated. If Appwrite is still wanted, recover it from history and
| **Headscale** | `headscale.yml` | 8085, 9090 | No | Self-hosted Tailscale control server | add it to `system-admin-toj/containers/stacks/` rather than reviving this
directory. Penpot is decommissioned (its Redis DB 10 is marked available again
### Phase 3: Optimization in `CONTAINERS.md`).
| Stack | File | Ports | GPU | Description |
|-------|------|-------|-----|-------------|
| **Watchtower** | `watchtower.yml` | - | No | Automatic container updates |
| **Duplicati** | `duplicati.yml` | 8200 | No | Backup solution |
### Backlog: Applications
| Stack | File | Ports | GPU | Description |
|-------|------|-------|-----|-------------|
| **Jellyfin** | `jellyfin.yml` | 8096, 8920, 7359, 1900 | **Yes** | Media server with GPU transcoding |
| **Nextcloud** | `nextcloud.yml` | 8082 | No | Cloud storage (includes DB and Redis) |
| **Gitea** | `gitea.yml` | 3002, 2222 | No | Git repository hosting (includes PostgreSQL) |
| **Samba** | `samba.yml` | 139, 445 | No | Network file sharing |
| **Home Assistant** | `home-assistant.yml` | 8123 | No | Smart home automation platform |
## Port Allocation
### Infrastructure Services (8000-8099)
- 8000: Nginx Proxy Manager (unified web interface)
- 8080: Portainer
- 8081: AMP (game servers - existing)
- 8082: Nextcloud
- 8085: Headscale
- 8096: Jellyfin
### Home Automation Services (8100-8199)
- 8123: Home Assistant
### Git & Development Services
- 2222: Gitea SSH
- 3002: Gitea HTTP
### Backup Services
- 8200: Duplicati
### ML/API Services (11000+)
- 11434: Ollama
### Network Services
- 80: HTTP (NPM reverse proxy)
- 443: HTTPS (NPM reverse proxy)
- 139, 445: Samba/SMB
- 9090: Headscale metrics
## Storage Convention
All stacks follow the dual-disk strategy:
**SSD (Performance):**
- Configs: `/home/jpmschweitzer/docker-data/<service>/config`
- Cache: `/home/jpmschweitzer/docker-data/<service>/cache`
- Databases: `/home/jpmschweitzer/docker-data/<service>/db`
**HDD (Capacity):**
- User content: `/mnt/media/<service>/data`
- Media files: `/mnt/media/<service>/media`
- Backups: `/mnt/media/backups/<service>`
## GPU Services
Stacks requiring GPU access (marked with **Yes** above):
- `ollama.yml` - ML model inference
- `jellyfin.yml` - Hardware transcoding
**Prerequisites:**
- NVIDIA Container Toolkit installed
- GPU verified: `docker run --rm --gpus all nvidia/cuda:11.4.0-base-ubuntu20.04 nvidia-smi`
## Before Deploying
1. **Review environment variables** - Change default passwords!
2. **Create directories** - Ensure volume paths exist
3. **Check ports** - Verify no conflicts with existing services
4. **GPU services** - Confirm NVIDIA toolkit installed
5. **Update STATUS.md** - Mark stack as deployed when complete
## After Deploying
1. **Test service** - Access web UI or API endpoint
2. **Check logs** - `docker logs <container-name>`
3. **Verify GPU** - `docker exec <container> nvidia-smi` (if applicable)
4. **Update documentation** - Add to STATUS.md and CHANGELOG.md
5. **Configure backup** - Add to Duplicati backup job
## Maintenance
### Update a Stack
```bash
# Pull latest images
docker compose -f stacks/<stack-name>.yml pull
# Recreate containers with new images
docker compose -f stacks/<stack-name>.yml up -d
# Or let Watchtower handle it automatically
```
### Backup Stack Configuration
```bash
# Stacks are version-controlled in this directory
# Backup container data separately (see scripts/backup.sh)
```
### Troubleshooting
- Container won't start: `docker logs <container-name>`
- Port conflicts: `sudo netstat -tulpn | grep <port>`
- Permission issues: Check volume path ownership
- GPU not detected: Verify NVIDIA toolkit and restart Docker
---
*For detailed implementation instructions, see containers/implementation-plan.md*
-56
View File
@@ -1,56 +0,0 @@
version: '3.8'
# AdGuard Home - Network-wide DNS Ad Blocking
# Infrastructure Layer
# Ports: 53 (DNS), 3053 (Web UI)
# GPU: No
# Storage: SSD (configs and work data)
services:
adguard:
image: adguard/adguardhome:latest
container_name: adguard
restart: unless-stopped
ports:
- "192.168.86.149:53:53/tcp" # DNS TCP (bound to LAN IP to avoid systemd-resolved conflict)
- "192.168.86.149:53:53/udp" # DNS UDP
- "3053:3000/tcp" # Web UI
volumes:
# SSD storage for configs and query logs
- /home/jpmschweitzer/docker-data/adguard/work:/opt/adguardhome/work
- /home/jpmschweitzer/docker-data/adguard/conf:/opt/adguardhome/conf
environment:
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "nslookup localhost 127.0.0.1 || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Setup Instructions:
# 1. Deploy this stack via Portainer
# 2. Access setup wizard at http://192.168.86.149:3053
# 3. Configure:
# - Admin interface: Listen on port 3053, all interfaces
# - DNS server: Listen on port 53, all interfaces
# - Create admin username/password
# 4. After setup, configure in AdGuard Home UI:
# - Settings → DNS settings → Upstream DNS:
# https://dns.quad9.net/dns-query
# - Enable "Parallel requests"
# - Filters → DNS blocklists → Add recommended lists
# 5. Configure router DNS to 192.168.86.149
#
# Testing:
# dig @192.168.86.149 google.com # Should resolve
# dig @192.168.86.149 ads.google.com # Should be blocked
#
# Internal access: http://dns.schweitz.internal (requires NPM proxy + /etc/hosts entry)
-147
View File
@@ -1,147 +0,0 @@
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
-214
View File
@@ -1,214 +0,0 @@
version: '3.8'
# Authentik Identity Provider (SSO)
# Purpose: Centralized authentication for all homelab services
# Ports: 9000 (web UI), 9444 (standalone proxy outpost)
# GPU: No
# Storage: SSD (configs), PostgreSQL shared (user data)
# Note: Using standalone outpost - embedded outpost has issues in 2024.8.4
services:
authentik-server:
image: ghcr.io/goauthentik/server:2025.10.3 # Pinned version (2024.10 has redirect loop issues)
container_name: authentik-server
restart: unless-stopped
command: server
environment:
# External URLs (CRITICAL for redirect loop prevention)
AUTHENTIK_HOST: https://auth.schweitz.net
AUTHENTIK_HOST_BROWSER: https://auth.schweitz.net
# Cookie settings (CRITICAL for SSO across subdomains)
AUTHENTIK_COOKIE_DOMAIN: .schweitz.net
AUTHENTIK_COOKIE_SAMESITE: lax
# SSL/TLS
AUTHENTIK_INSECURE: false
# PostgreSQL (shared)
AUTHENTIK_POSTGRESQL__HOST: postgres-shared
AUTHENTIK_POSTGRESQL__PORT: 5432
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__USER: authentik_user
AUTHENTIK_POSTGRESQL__PASSWORD: F//j0ktck7cX06Vfgh0YXceONOtlSsHvadqROICeDx8=
AUTHENTIK_POSTGRESQL__CONN_MAX_AGE: 0
AUTHENTIK_POSTGRESQL__CONN_HEALTH_CHECKS: true
# Worker scaling (reduced to limit DB connections)
AUTHENTIK_WEB__WORKERS: 1
AUTHENTIK_WEB__THREADS: 2
# Secret key (generated: openssl rand -base64 32)
AUTHENTIK_SECRET_KEY: TnFaTZ//RDcO2hxVR4QGOBORd5tfXe4Vok+lcAz/AdE=
# Resource optimization
AUTHENTIK_LOG_LEVEL: warning
AUTHENTIK_ERROR_REPORTING__ENABLED: false
AUTHENTIK_AVATARS: none
AUTHENTIK_FOOTER_LINKS: '[]'
# Embedded outpost configuration
AUTHENTIK_OUTPOSTS__DOCKER_IMAGE_BASE: "ghcr.io/goauthentik/%(type)s:%(version)s"
# Timezone
TZ: Europe/Amsterdam
ports:
- "9000:9000" # Web UI + Embedded outpost (path: /outpost.goauthentik.io/*)
volumes:
- /home/jpmschweitzer/docker-data/authentik/media:/media
- /home/jpmschweitzer/docker-data/authentik/custom-templates:/templates
networks:
- docker-dataplane
healthcheck:
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:9000/-/health/live/')\" || exit 1"]
start_period: 60s
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
authentik-worker:
image: ghcr.io/goauthentik/server:2025.10.3 # Same version as server
container_name: authentik-worker
restart: unless-stopped
command: worker
environment:
# Same environment as server (MUST match exactly)
AUTHENTIK_HOST: https://auth.schweitz.net
AUTHENTIK_HOST_BROWSER: https://auth.schweitz.net
AUTHENTIK_COOKIE_DOMAIN: .schweitz.net
AUTHENTIK_COOKIE_SAMESITE: lax
AUTHENTIK_INSECURE: false
AUTHENTIK_POSTGRESQL__HOST: postgres-shared
AUTHENTIK_POSTGRESQL__PORT: 5432
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__USER: authentik_user
AUTHENTIK_POSTGRESQL__PASSWORD: F//j0ktck7cX06Vfgh0YXceONOtlSsHvadqROICeDx8=
AUTHENTIK_POSTGRESQL__CONN_MAX_AGE: 0
AUTHENTIK_POSTGRESQL__CONN_HEALTH_CHECKS: true
AUTHENTIK_SECRET_KEY: TnFaTZ//RDcO2hxVR4QGOBORd5tfXe4Vok+lcAz/AdE=
AUTHENTIK_LOG_LEVEL: warning
AUTHENTIK_ERROR_REPORTING__ENABLED: false
TZ: Europe/Amsterdam
# Worker-specific configuration
AUTHENTIK_WORKER__CONCURRENCY: 1
AUTHENTIK_WORKER__THREADS: 2
volumes:
- /home/jpmschweitzer/docker-data/authentik/media:/media
- /home/jpmschweitzer/docker-data/authentik/custom-templates:/templates
- /home/jpmschweitzer/docker-data/authentik/certs:/certs
- /var/run/docker.sock:/var/run/docker.sock # For outpost management
networks:
- docker-dataplane
depends_on:
- authentik-server
healthcheck:
test: ["CMD-SHELL", "ak healthcheck || exit 1"]
start_period: 60s
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
memory: 1G
cpus: '0.3'
reservations:
memory: 128M
authentik-proxy:
image: ghcr.io/goauthentik/proxy:2025.10.3 # Standalone outpost (embedded outpost not working in 2024.8.4)
container_name: authentik-proxy
restart: unless-stopped
environment:
# Authentik server connection
AUTHENTIK_HOST: https://auth.schweitz.net
AUTHENTIK_INSECURE: false
AUTHENTIK_TOKEN: 9blMGz71CFMJszs7AedQefgydpTnwvybjmMn0AlYilIKBV5LIq7snqnCodwX
# Logging
AUTHENTIK_LOG_LEVEL: info
# Timezone
TZ: Europe/Amsterdam
ports:
- "9444:9443" # Proxy outpost endpoint (9443 used by Portainer)
networks:
- docker-dataplane
depends_on:
- authentik-server
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:9300/outpost.goauthentik.io/ping || exit 1"]
start_period: 30s
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
memory: 256M
cpus: '0.2'
reservations:
memory: 128M
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Setup Instructions:
#
# 1. Create directories:
# mkdir -p ~/docker-data/authentik/{media,custom-templates,certs}
#
# 2. Deploy stack:
# docker-compose -f stacks/authentik.yml up -d
#
# 3. Watch logs:
# docker logs -f authentik-server
# docker logs -f authentik-worker
#
# 4. Wait for migrations to complete (~2-3 minutes):
# docker logs authentik-server 2>&1 | grep "Applying migration"
#
# 5. Access web UI:
# https://auth.schweitz.net (should show setup wizard)
#
# 6. Complete setup wizard:
# - Email: admin@schweitz.net
# - Password: <secure-password>
# - Finish setup
#
# Monitoring:
#
# Memory usage:
# docker stats authentik-server authentik-worker --no-stream
#
# Database connectivity:
# docker exec authentik-server ak check
#
# Outpost status (embedded outpost on port 9000):
# curl http://authentik-server:9000/outpost.goauthentik.io/ping
# curl http://192.168.86.149:9000/outpost.goauthentik.io/auth/nginx (should return 401, not 404)
-101
View File
@@ -1,101 +0,0 @@
version: '3.8'
# Core API - OpenAPI-compatible functions and AI orchestration for Open WebUI
# Purpose: Provides OpenAI-compatible API (/v1/chat/completions) and tool functions (web scraping)
# Port: 8083 (HTTP API)
# Network: docker-dataplane (shared infrastructure network)
#
# Container image built from: git.schweitz.internal/jpmschweitzer/core-api (internal registry)
services:
core-api:
image: git.schweitz.internal/jpmschweitzer/core-api:latest
container_name: core-api
restart: unless-stopped
ports:
- "8083:8083"
environment:
# Application
- APP_NAME=Core API
- DEBUG=false
- OIDC_ENABLED=true
# Server
- HOST=0.0.0.0
- PORT=8083
# Logging
- LOG_LEVEL=INFO
# Portainer API
- PORTAINER_URL=http://192.168.86.149:8001
- PORTAINER_API_KEY=${PORTAINER_API_KEY}
# Nginx Proxy Manager API
- NPM_URL=http://192.168.86.149:81
- NPM_EMAIL=${NPM_EMAIL}
- NPM_PASSWORD=${NPM_PASSWORD}
# Postgres Shared Database API
- POSTGRES_HOST=192.168.86.149:5432
- POSTGRES_USER=core_api
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
# Authentik Configuration
- AUTHENTIK_URL=https://auth.schweitz.net
- AUTHENTIK_USERNAME=${AUTHENTIK_USERNAME}
- AUTHENTIK_PASSWORD=${AUTHENTIK_PASSWORD}
- AUTHENTIK_CORE_API_TOKEN=${AUTHENTIK_CORE_API_TOKEN}
# HOME ASSITANT VARIABLES
- HOMEASSISTANT_URL=http://home-assistant:8123
- HOMEASSISTANT_TOKEN=${HOMEASSISTANT_TOKEN}
# AI SYSTEM VARIABLES
- 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
# Docker socket for direct container access (fallback when Portainer API incomplete)
- /var/run/docker.sock:/var/run/docker.sock:ro
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/hostfs:ro
deploy:
resources:
limits:
cpus: '2.0'
memory: 6G
reservations:
memory: 1G
devices:
- driver: nvidia
count: all
capabilities: [gpu]
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8083/health"]
interval: 30s
timeout: 20s
retries: 3
start_period: 60s
networks:
docker-dataplane:
external: true
name: docker-dataplane
-92
View File
@@ -1,92 +0,0 @@
version: '3.8'
# Gitea - Self-Hosted Git Service
# Application Layer
# Ports: 3002 (HTTP), 2222 (SSH)
# GPU: No
# Storage: SSD (repositories)
# Database: postgres-shared (gitea database, gitea_user)
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
restart: unless-stopped
ports:
- "3002:3000" # HTTP web interface
- "2222:22" # SSH git access (mapped to avoid host SSH conflict)
volumes:
# Git repositories and app config on SSD
- /home/jpmschweitzer/docker-data/gitea/data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=postgres-shared:5432
- GITEA__database__NAME=gitea
- 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"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- docker-dataplane
gitea-runner:
image: gitea/act_runner:latest
container_name: gitea-runner
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /home/jpmschweitzer/docker-data/gitea/runner:/data
environment:
- CONFIG_FILE=/data/config.yaml
- GITEA_INSTANCE_URL=http://gitea:3000
- GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_TOKEN}
- GITEA_RUNNER_NAME=docker-runner
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "pgrep -x act_runner || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
depends_on:
- gitea
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Database: Uses postgres-shared stack (gitea database, gitea_user)
# Ensure postgres-shared is running before deploying this stack.
#
# Access: http://localhost:3002 or https://git.schweitz.net (via NPM)
#
# SSH Git Clone Usage:
# git clone ssh://git@localhost:2222/username/repo.git
#
# Nginx Proxy Manager Setup (for external access):
# 1. Add proxy host: git.schweitz.net → http://gitea:3000
# 2. Enable SSL with Let's Encrypt
# 3. Update GITEA__server__ROOT_URL in environment to https://git.schweitz.net
#
# Features:
# - Git repository hosting
# - Organizations and teams
# - Issue tracking
# - Pull requests and code review
# - Wiki and project documentation
# - CI/CD integration (Gitea Actions)
# - Webhooks for automation
# - Migration from GitHub/GitLab
# - Lightweight and fast
-63
View File
@@ -1,63 +0,0 @@
version: '3.8'
# Headscale - Self-Hosted Tailscale Control Server
# Phase 2: Networking & External Access
# Ports: 8085 (Web/API), 9090 (Metrics)
# GPU: No
# Storage: SSD (config and database)
services:
headscale:
image: headscale/headscale:latest
container_name: headscale
restart: unless-stopped
ports:
- "8085:8080" # Web/API port
- "9090:9090" # Metrics port (optional)
volumes:
- /home/jpmschweitzer/docker-data/headscale/config:/etc/headscale
- /home/jpmschweitzer/docker-data/headscale/data:/var/lib/headscale
command: serve
environment:
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Setup Instructions:
# 1. Create directories:
# mkdir -p ~/docker-data/headscale/{config,data}
#
# 2. Generate config:
# docker exec headscale headscale config generate > ~/docker-data/headscale/config/config.yaml
#
# 3. Edit config (important settings):
# - server_url: http://tower-of-joy:8085 (or your IP)
# - db_type: sqlite3
# - db_path: /var/lib/headscale/db.sqlite
#
# 4. Restart container: docker restart headscale
#
# 5. Create user: docker exec headscale headscale users create homelab
#
# 6. Generate pre-auth key:
# docker exec headscale headscale preauthkeys create --user homelab --expiration 24h
#
# 7. Connect devices:
# - Install Tailscale client on devices
# - Run: tailscale up --login-server=http://tower-of-joy:8085 --authkey=<key>
#
# Verify:
# - Health check: curl http://localhost:8085/health
# - List users: docker exec headscale headscale users list
# - List nodes: docker exec headscale headscale nodes list
-79
View File
@@ -1,79 +0,0 @@
version: '3.8'
# Home Assistant - Smart Home Automation Platform
# Port: 8123 (Home Assistant default)
# GPU: No
# Storage: SSD (config and database)
services:
home-assistant:
image: ghcr.io/home-assistant/home-assistant:stable
container_name: home-assistant
restart: unless-stopped
ports:
- "8123:8123"
volumes:
# Config on SSD (includes database, automations, scripts)
- /home/jpmschweitzer/docker-data/home-assistant/config:/config
environment:
- TZ=Europe/Amsterdam
# Privileged mode for USB device access (Z-Wave, Zigbee dongles)
privileged: true
networks:
- docker-dataplane
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:8123/manifest.json || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 120s
deploy:
resources:
limits:
memory: 1G
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Setup Instructions:
# 1. Create config directory:
# mkdir -p /home/jpmschweitzer/docker-data/home-assistant/config
#
# 2. Deploy this stack via Portainer or:
# docker compose -f home-assistant.yml up -d
#
# 3. Wait for initial setup (1-2 minutes)
#
# 4. Access web interface: http://localhost:8123
# or external: https://housekeeping.schweitz.net
#
# 5. Complete the onboarding wizard:
# - Create admin account
# - Set home location
# - Configure integrations
#
# USB Device Access:
# Privileged mode is enabled for Z-Wave, Zigbee, or other USB devices.
# For specific device mapping instead, replace privileged: true with:
# devices:
# - /dev/ttyUSB0:/dev/ttyUSB0
# - /dev/ttyACM0:/dev/ttyACM0
#
# NPM Configuration:
# Domain: housekeeping.schweitz.net
# Scheme: http
# Forward Hostname/IP: home-assistant
# Forward Port: 8123
# Websockets Support: ENABLED (required for HA)
# SSL: Let's Encrypt
#
# After NPM setup, add to configuration.yaml:
#
# http:
# use_x_forwarded_for: true
# trusted_proxies:
# - 172.16.0.0/12
# - 192.168.0.0/16
# - 10.0.0.0/8
-84
View File
@@ -1,84 +0,0 @@
version: '3.8'
# Library - Front Desk API (Coordination Service)
# Application Layer
# Port: 8089 (HTTP)
# GPU: No
# Source: git.schweitz.internal/jpmschweitzer/library-desk (internal registry)
services:
library-desk:
image: git.schweitz.internal/jpmschweitzer/library-desk:latest
container_name: library-desk
restart: unless-stopped
ports:
- "8089:8089" # FastAPI HTTP
environment:
# API Configuration
- LIBRARY_API_KEY=${LIBRARY_API_KEY}
# Neo4j Configuration
- NEO4J_URI=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
# Qdrant Configuration
- QDRANT_HOST=qdrant
- QDRANT_PORT=6333
# Wiki.js Configuration
- WIKIJS_URL=http://wiki:3000
- WIKIJS_USERNAME=librarian@schweitz.net
- WIKIJS_PASSWORD=${WIKIJS_PASSWORD}
- WIKIJS_DB_PASSWORD=${WIKIJS_DB_PASSWORD}
# SearXNG Configuration
- SEARXNG_URL=http://searxng:8080
# Paperless Configuration
- PAPERLESS_URL=http://paperless:8000
- PAPERLESS_TOKEN=${PAPERLESS_TOKEN}
# Ollama Configuration (for embeddings)
- OLLAMA_URL=http://ollama:11434
- OLLAMA_MODEL=nomic-embed-text
# Redis Configuration
- REDIS_HOST=redis-shared
- REDIS_PORT=6379
- REDIS_DB=4
# Central Settings Database
- SYSTEM_SETTINGS_HOST=postgres-shared
- SYSTEM_SETTINGS_PORT=5432
- SYSTEM_SETTINGS_DB=system_settings
- SYSTEM_SETTINGS_USER=settings
- SYSTEM_SETTINGS_PASSWORD=${SYSTEM_SETTINGS_PASSWORD}
# Scheduler Service
- SCHEDULER_URL=http://scheduler:8090
# Python Configuration
- PYTHONUNBUFFERED=1
- TZ=${TZ:-Europe/Amsterdam}
labels:
- "com.centurylinklabs.watchtower.enable=true"
networks:
- docker-dataplane
deploy:
resources:
reservations:
memory: 256M
limits:
memory: 768M
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8089/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
docker-dataplane:
external: true
name: docker-dataplane
-254
View File
@@ -1,254 +0,0 @@
version: '3.8'
# Media Stack - Unified Media Management & Streaming
# Services: Jellyfin, Sonarr, Radarr, Prowlarr, SABnzbd
# Ports: 8096 (Jellyfin), 8989 (Sonarr), 7878 (Radarr), 9696 (Prowlarr), 8880 (SABnzbd)
# GPU: YES (Jellyfin only) - Requires NVIDIA Container Toolkit
# Storage: SSD (configs), HDD (media, downloads)
# Database: PostgreSQL (postgres-shared) for Sonarr, Radarr, Prowlarr
services:
# ============================================================
# JELLYFIN - Media Server with GPU Transcoding
# ============================================================
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
user: 1000:1000
restart: unless-stopped
ports:
- "8096:8096" # HTTP web interface
- "8920:8920" # HTTPS web interface
- "7359:7359/udp" # Auto-discovery
- "1900:1900/udp" # DLNA
volumes:
# Config and cache on SSD (performance-critical)
- /home/jpmschweitzer/docker-data/jellyfin/config:/config
- /home/jpmschweitzer/docker-data/jellyfin/cache:/cache
# Media files on HDD (read-only for safety)
- /mnt/media/jellyfin/movies:/media/movies:ro
- /mnt/media/jellyfin/series:/media/series:ro
environment:
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=all
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:8096/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- docker-dataplane
# ============================================================
# SONARR - TV Show Management & Automation
# ============================================================
sonarr:
image: linuxserver/sonarr:latest
container_name: sonarr
restart: unless-stopped
ports:
- "8989:8989"
volumes:
# Config on SSD
- /home/jpmschweitzer/docker-data/sonarr:/config
# Media and downloads on HDD
- /mnt/media/jellyfin/series:/tv
- /mnt/media/downloads:/downloads
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
# PostgreSQL (postgres-shared)
- SONARR__POSTGRES__HOST=postgres-shared
- SONARR__POSTGRES__PORT=5432
- SONARR__POSTGRES__USER=media_user
- SONARR__POSTGRES__PASSWORD=${MEDIA_DB_PASSWORD}
- SONARR__POSTGRES__MAINDB=sonarr
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:8989/ping || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
depends_on:
- sabnzbd
- prowlarr
networks:
- docker-dataplane
# ============================================================
# RADARR - Movie Management & Automation
# ============================================================
radarr:
image: linuxserver/radarr:latest
container_name: radarr
restart: unless-stopped
ports:
- "7878:7878"
volumes:
# Config on SSD
- /home/jpmschweitzer/docker-data/radarr:/config
# Media and downloads on HDD
- /mnt/media/jellyfin/movies:/movies
- /mnt/media/downloads:/downloads
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
# PostgreSQL (postgres-shared)
- RADARR__POSTGRES__HOST=postgres-shared
- RADARR__POSTGRES__PORT=5432
- RADARR__POSTGRES__USER=media_user
- RADARR__POSTGRES__PASSWORD=${MEDIA_DB_PASSWORD}
- RADARR__POSTGRES__MAINDB=radarr
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:7878/ping || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
depends_on:
- sabnzbd
- prowlarr
networks:
- docker-dataplane
# ============================================================
# PROWLARR - Indexer Management
# ============================================================
prowlarr:
image: linuxserver/prowlarr:latest
container_name: prowlarr
restart: unless-stopped
ports:
- "9696:9696"
volumes:
# Config on SSD
- /home/jpmschweitzer/docker-data/prowlarr:/config
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
# PostgreSQL (postgres-shared)
- PROWLARR__POSTGRES__HOST=postgres-shared
- PROWLARR__POSTGRES__PORT=5432
- PROWLARR__POSTGRES__USER=media_user
- PROWLARR__POSTGRES__PASSWORD=${MEDIA_DB_PASSWORD}
- PROWLARR__POSTGRES__MAINDB=prowlarr
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:9696/ping || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- docker-dataplane
# ============================================================
# SABNZBD - Usenet Download Client
# ============================================================
sabnzbd:
image: linuxserver/sabnzbd:latest
container_name: sabnzbd
restart: unless-stopped
ports:
- "8880:8080" # Web UI (remapped to avoid Portainer conflict)
volumes:
# Config on SSD
- /home/jpmschweitzer/docker-data/sabnzbd:/config
# Downloads on HDD
- /mnt/media/downloads:/downloads
- /mnt/media/downloads/incomplete:/incomplete-downloads
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
- HOST_WHITELIST=sabnzbd,localhost,192.168.86.149,tower-of-joy
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:8080/api?mode=version || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# ============================================================
# SETUP GUIDE
# ============================================================
#
# 0. Create PostgreSQL databases (run once):
# docker exec -it postgres-shared psql -U postgres -c "
# CREATE USER media_user WITH PASSWORD '<MEDIA_DB_PASSWORD>';
# CREATE DATABASE sonarr OWNER media_user;
# CREATE DATABASE radarr OWNER media_user;
# CREATE DATABASE prowlarr OWNER media_user;
# GRANT ALL PRIVILEGES ON DATABASE sonarr TO media_user;
# GRANT ALL PRIVILEGES ON DATABASE radarr TO media_user;
# GRANT ALL PRIVILEGES ON DATABASE prowlarr TO media_user;
# "
#
# 1. Create download directories:
# mkdir -p /mnt/media/downloads/complete /mnt/media/downloads/incomplete /mnt/media/downloads/usenet
# chown -R 1000:1000 /mnt/media/downloads
#
# 2. Create config directories:
# mkdir -p ~/docker-data/sonarr ~/docker-data/radarr ~/docker-data/prowlarr ~/docker-data/sabnzbd
# chown -R 1000:1000 ~/docker-data/sonarr ~/docker-data/radarr ~/docker-data/prowlarr ~/docker-data/sabnzbd
#
# 3. Set MEDIA_DB_PASSWORD environment variable in Portainer stack or export before deploy
#
# 4. Deploy the stack via Portainer or:
# docker stack deploy -c media.yml media
#
# 5. Configure services in order:
#
# a) SABnzbd (http://localhost:8880):
# - Complete setup wizard
# - Add Usenet server credentials
# - Configure download categories: tv, movies
# - Note the API key for Sonarr/Radarr
#
# b) Prowlarr (http://localhost:9696):
# - Add indexers (NZBgeek, DrunkenSlug, etc.)
# - Add Sonarr as application: http://sonarr:8989
# - Add Radarr as application: http://radarr:7878
# - Sync indexers to apps
#
# c) Sonarr (http://localhost:8989):
# - Settings → Media Management → Root Folder: /tv
# - Settings → Download Clients → Add SABnzbd:
# Host: sabnzbd, Port: 8080, API Key: <from SABnzbd>
# - Indexers will be synced from Prowlarr
#
# d) Radarr (http://localhost:7878):
# - Settings → Media Management → Root Folder: /movies
# - Settings → Download Clients → Add SABnzbd:
# Host: sabnzbd, Port: 8080, API Key: <from SABnzbd>
# - Indexers will be synced from Prowlarr
#
# e) Jellyfin (http://localhost:8096):
# - Already configured (migrated from previous stack)
# - Libraries should auto-detect new content
#
# 5. Inter-service communication (use container names):
# - SABnzbd from Sonarr/Radarr: http://sabnzbd:8080
# - Prowlarr from Sonarr: http://prowlarr:9696
# - All services on docker-dataplane network
#
# STORAGE LAYOUT:
# /mnt/media/
# ├── downloads/
# │ ├── complete/ # Completed downloads
# │ ├── incomplete/ # In-progress downloads
# │ └── usenet/ # Usenet-specific
# └── jellyfin/
# ├── movies/ # Radarr imports here
# └── series/ # Sonarr imports here
-77
View File
@@ -1,77 +0,0 @@
version: '3.8'
# Library - Neo4j Knowledge Graph
# Storage Layer
# Ports: 7474 (Browser), 7687 (Bolt)
# GPU: No
# Storage: SSD (graph database)
services:
neo4j:
image: neo4j:5-community
container_name: neo4j
restart: unless-stopped
ports:
- "7474:7474" # Neo4j Browser (web UI)
- "7687:7687" # Bolt protocol (API)
volumes:
# Graph database on SSD for performance
- /home/jpmschweitzer/docker-data/library-neo4j/data:/data
- /home/jpmschweitzer/docker-data/library-neo4j/logs:/logs
- /home/jpmschweitzer/docker-data/library-neo4j/plugins:/plugins
environment:
- NEO4J_AUTH=neo4j/${NEO4J_PASSWORD}
- NEO4J_PLUGINS=["apoc"]
- NEO4J_dbms_memory_heap_initial__size=512m
- NEO4J_dbms_memory_heap_max__size=2g
- NEO4J_dbms_memory_pagecache_size=512m
- NEO4J_apoc_export_file_enabled=true
- NEO4J_apoc_import_file_enabled=true
- NEO4J_apoc_import_file_use__neo4j__config=true
- TZ=${TZ:-Europe/Amsterdam}
networks:
- docker-dataplane
deploy:
resources:
reservations:
memory: 1G
limits:
memory: 4G
healthcheck:
test: ["CMD", "cypher-shell", "-u", "neo4j", "-p", "${NEO4J_PASSWORD}", "RETURN 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
docker-dataplane:
external: true
name: docker-dataplane
# ⚠️ SECURITY WARNING:
# Set NEO4J_PASSWORD in environment variables before deploying!
# Use a strong, unique password.
#
# After Deployment:
# 1. Access Neo4j Browser: http://192.168.86.149:7474
# 2. Login: neo4j / <NEO4J_PASSWORD>
# 3. Run schema initialization (see DEPLOYMENT.md Phase 4.1)
# 4. Install APOC plugin (should auto-install from NEO4J_PLUGINS setting)
#
# Features:
# - Knowledge graph for entities, relationships, versions
# - APOC procedures for advanced graph operations
# - Cypher query language for graph traversal
# - Mind map generation for Wiki.js
# - Version tracking for documentation
# - Compatibility relationships between projects
#
# Memory Configuration:
# - Heap: 512MB initial → 2GB max
# - Page cache: 512MB
# - Reserved: 1GB, Limit: 4GB
#
# Backups:
# - Managed by Scheduler (weekly, Sunday 03:00)
# - Location: /mnt/media/backups/library/neo4j/
-111
View File
@@ -1,111 +0,0 @@
services:
nextcloud:
image: nextcloud:stable
container_name: nextcloud
restart: unless-stopped
ports:
- "8082:80"
volumes:
# Fresh config directory
- /home/jpmschweitzer/docker-data/nextcloud/config:/var/www/html/config
# Fresh user data directory
- /mnt/media/nextcloud/data:/var/www/html/data
environment:
# PostgreSQL configuration
- POSTGRES_HOST=postgres-shared
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud_user
- POSTGRES_PASSWORD=${NEXTCLOUD_DB_PASSWORD}
# Redis configuration (Database 7)
- REDIS_HOST=redis-shared
- REDIS_HOST_PORT=6379
- REDIS_DB_INDEX=7
# Timezone
- TZ=Europe/Amsterdam
networks:
- docker-dataplane
deploy:
resources:
limits:
memory: 1G
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Nextcloud - Personal Cloud Storage (Using Shared Infrastructure)
# Port: 8082
# GPU: No
# Dependencies: postgres-shared, redis-shared
#
# Prerequisites:
#
# 1. Shared infrastructure must be running:
# docker ps | grep -E 'postgres-shared|redis-shared'
#
# 2. Database and user already created in postgres-shared:
# - Database: nextcloud
# - User: nextcloud_user
# - Redis DB: 7
#
# 3. Create .env file with:
# NEXTCLOUD_DB_PASSWORD=<password from shared infrastructure setup>
#
# 4. Deploy this stack:
# cd /mnt/media/Projects/portainer-core/stacks
# docker compose -f nextcloud-shared.yml --env-file .env.nextcloud-shared up -d
#
# After Deployment:
#
# 1. Wait for initialization (2-3 minutes)
#
# 2. Access web interface: http://localhost:8082 or https://cloud.schweitz.net
#
# 3. First-time setup wizard:
# - Admin username: admin
# - Admin password: <STRONG_PASSWORD>
# - Data folder: /var/www/html/data (default)
# - Database: PostgreSQL
# - Database user: nextcloud_user
# - Database password: <FROM_ENV_FILE>
# - Database name: nextcloud
# - Database host: postgres-shared
#
# 4. Configure trusted domains:
# docker exec -u www-data nextcloud php occ config:system:set trusted_domains 1 --value=cloud.schweitz.net
# docker exec -u www-data nextcloud php occ config:system:set trusted_domains 2 --value=192.168.86.149
#
# 5. Configure Redis caching:
# docker exec -u www-data nextcloud php occ config:system:set redis host --value=redis-shared
# docker exec -u www-data nextcloud php occ config:system:set redis port --value=6379
# docker exec -u www-data nextcloud php occ config:system:set redis dbindex --value=7
# docker exec -u www-data nextcloud php occ config:system:set memcache.local --value='\\OC\\Memcache\\APCu'
# docker exec -u www-data nextcloud php occ config:system:set memcache.distributed --value='\\OC\\Memcache\\Redis'
# docker exec -u www-data nextcloud php occ config:system:set memcache.locking --value='\\OC\\Memcache\\Redis'
#
# 6. Optimize database:
# docker exec -u www-data nextcloud php occ db:add-missing-indices
# docker exec -u www-data nextcloud php occ db:convert-filecache-bigint
#
# 7. Configure background jobs:
# docker exec -u www-data nextcloud php occ background:cron
#
# Connection Details:
#
# Database:
# - Host: postgres-shared (from containers) / localhost (from host)
# - Port: 5432
# - Database: nextcloud
# - User: nextcloud_user
#
# Cache:
# - Host: redis-shared (from containers) / localhost (from host)
# - Port: 6379
# - Database: 7
#
# Resource Usage:
# - Nextcloud: 1GB RAM limit
# - Savings: ~110-120 MB RAM + 2 fewer containers (MariaDB + Redis removed)
-42
View File
@@ -1,42 +0,0 @@
version: '3.8'
# Nginx Proxy Manager - Reverse Proxy & Unified Web Interface
# Phase 1: Foundation Setup
# Ports: 8000 (Admin UI), 80 (HTTP), 443 (HTTPS)
# GPU: No
# Storage: SSD (configs and SSL certificates)
services:
nginx-proxy-manager:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "8000:81" # Admin web interface (unified entry point)
- "80:80" # HTTP reverse proxy traffic
- "443:443" # HTTPS reverse proxy traffic
volumes:
# SSD storage for configs and certificates (performance-critical)
- /home/jpmschweitzer/docker-data/nginx-proxy-manager/data:/data
- /home/jpmschweitzer/docker-data/nginx-proxy-manager/letsencrypt:/etc/letsencrypt
environment:
- DB_SQLITE_FILE=/data/database.sqlite
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:81/api/ || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# Setup Instructions:
# 1. Deploy this stack
# 2. Access http://localhost:8000
# 3. Default login: admin@example.com / changeme
# 4. IMPORTANT: Change admin credentials immediately!
# 5. Add proxy hosts for your services (Portainer, Jellyfin, etc.)
#
# Example Proxy Host Configuration:
# - Domain: portainer.tower-of-joy.local
# - Forward to: portainer:9000
# - Enable SSL with Let's Encrypt (optional)
-78
View File
@@ -1,78 +0,0 @@
version: '3.8'
# Ollama - GPU-Accelerated ML Model Serving
# Phase 1: Foundation Setup
# Ports: 11434 (API)
# GPU: YES - Requires NVIDIA Container Toolkit
# Storage: SSD or HDD for models (models are 2-15GB each)
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
restart: unless-stopped
ports:
- "11434:11434" # Ollama API endpoint
volumes:
# Model storage - choose based on available space:
# SSD (faster load times): /home/jpmschweitzer/docker-data/ollama/models
# HDD (more space): /mnt/media/ollama/models
- /home/jpmschweitzer/docker-data/ollama/models:/root/.ollama
environment:
- TZ=Europe/Amsterdam
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=all
# Process requests sequentially to avoid batch overflow panics
- OLLAMA_NUM_PARALLEL=1
# Keep models loaded in VRAM (don't unload after idle)
- OLLAMA_KEEP_ALIVE=-1
# Load multiple models concurrently (mistral-nemo + nomic-embed-text)
- OLLAMA_MAX_LOADED_MODELS=2
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:11434/api/tags || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
deploy:
resources:
limits:
memory: 8G
reservations:
memory: 1G
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# GPU Requirements:
# - RTX 2080 Ti (11GB VRAM)
# - Suitable for 3B-13B parameter models
# - NVIDIA Container Toolkit must be installed
#
# After Deployment:
# 1. Verify GPU access: docker exec ollama nvidia-smi
# 2. Pull a model: docker exec ollama ollama pull llama3.2:3b
# 3. List models: docker exec ollama ollama list
# 4. Test inference: docker exec ollama ollama run llama3.2:3b "Hello"
# 5. Monitor GPU during inference: watch -n 1 nvidia-smi
#
# Recommended Models for RTX 2080 Ti (11GB VRAM):
# - llama3.2:3b (2GB) - Fast, general purpose
# - mistral:7b (4GB) - High quality, coding
# - codellama:7b (4GB) - Code-specialized
# - phi3:mini (2GB) - Fast reasoning
#
# API Usage:
# curl http://localhost:11434/api/generate -d '{
# "model": "llama3.2:3b",
# "prompt": "Why is the sky blue?",
# "stream": false
# }'
-71
View File
@@ -1,71 +0,0 @@
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
ports:
- "82:8080"
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
environment:
# Ollama connection (direct - fallback)
- OLLAMA_BASE_URL=http://192.168.86.149:11434
# Tatlock AI Orchestrator (OpenAI-compatible endpoint)
- OPENAI_API_BASE_URLS=http://core-api:8083/v1
- OPENAI_API_KEYS=dummy
# Default model
- DEFAULT_MODELS=gemma3:12b
# Enable features
- ENABLE_RAG_WEB_SEARCH=true
- ENABLE_OLLAMA_API=true
- WEBUI_AUTH=true
# Web search configuration
- RAG_WEB_SEARCH_ENGINE=duckduckgo
# RAG & Vector Database - Qdrant for conversation memory
- VECTOR_DB=qdrant
- QDRANT_URI=http://qdrant:6333
- RAG_EMBEDDING_ENGINE=ollama
- RAG_EMBEDDING_MODEL=nomic-embed-text
- RAG_EMBEDDING_MODEL_AUTO_UPDATE=true
# Enable native memory feature
- ENABLE_MEMORY=true
- MEMORY_COLLECTION_NAME=open-webui_memories
# Session settings
- WEBUI_SESSION_COOKIE_SAME_SITE=lax
- WEBUI_SESSION_COOKIE_SECURE=false
volumes:
- /home/jpmschweitzer/docker-data/open-webui:/app/backend/data
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
memory: 512M
networks:
- docker-dataplane
labels:
- "com.centurylinklabs.watchtower.enable=true"
networks:
docker-dataplane:
external: true
name: docker-dataplane
-110
View File
@@ -1,110 +0,0 @@
version: '3.8'
# Paperless-ngx - Document Management System
# Port: 8091 (HTTP)
# GPU: No
# External: documents.schweitz.net
# Storage: Configs on SSD (backed up), documents on HDD
services:
paperless:
image: ghcr.io/paperless-ngx/paperless-ngx:latest
container_name: paperless
restart: unless-stopped
ports:
- "8091:8000"
volumes:
# SSD - configs and database (backed up)
- /home/jpmschweitzer/docker-data/paperless/data:/usr/src/paperless/data
# HDD - document storage
- /mnt/media/paperless/media:/usr/src/paperless/media
- /mnt/media/paperless/consume:/usr/src/paperless/consume
- /mnt/media/paperless/export:/usr/src/paperless/export
environment:
# Database (shared PostgreSQL)
PAPERLESS_DBENGINE: postgresql
PAPERLESS_DBHOST: postgres-shared
PAPERLESS_DBPORT: 5432
PAPERLESS_DBNAME: paperless
PAPERLESS_DBUSER: paperless_user
PAPERLESS_DBPASS: ${PAPERLESS_DB_PASSWORD}
# Redis (shared, DB 8)
PAPERLESS_REDIS: redis://redis-shared:6379/8
# Security
PAPERLESS_SECRET_KEY: ${PAPERLESS_SECRET_KEY}
# URLs
PAPERLESS_URL: https://documents.schweitz.net
PAPERLESS_ALLOWED_HOSTS: "*"
PAPERLESS_CORS_ALLOWED_HOSTS: "http://localhost:8091,https://documents.schweitz.net"
# OCR Settings
PAPERLESS_OCR_LANGUAGE: eng
PAPERLESS_OCR_LANGUAGES: nld # Install Dutch on first startup
PAPERLESS_OCR_MODE: skip
PAPERLESS_OCR_OUTPUT_TYPE: pdfa
# Webhooks (for Library Desk integration)
PAPERLESS_WEBHOOKS_ALLOW_INTERNAL_REQUESTS: "true"
# Admin user (created on first run)
PAPERLESS_ADMIN_USER: admin
PAPERLESS_ADMIN_PASSWORD: ${PAPERLESS_ADMIN_PASSWORD}
# Timezone
TZ: Europe/Amsterdam
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
deploy:
resources:
limits:
memory: 4G
reservations:
memory: 512M
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# =============================================================================
# DEPLOYMENT INSTRUCTIONS
# =============================================================================
#
# 1. Environment variables required in Portainer:
# PAPERLESS_DB_PASSWORD=<openssl rand -hex 32>
# PAPERLESS_SECRET_KEY=<openssl rand -base64 32>
# PAPERLESS_ADMIN_PASSWORD=<your-admin-password>
#
# 2. Database already created in postgres-shared:
# Database: paperless
# User: paperless_user
#
# 3. Redis using shared instance DB 8
#
# 4. After deployment, configure NPM:
# Domain: documents.schweitz.net
# Forward: paperless:8000 or 192.168.86.149:8091
# SSL: Let's Encrypt
# Websockets: Enable
#
# 5. Post-deployment in Paperless UI:
# - Create API token (My Profile → API Token)
# - Create custom fields: source_url, library_indexed, library_doc_id, collection
# - Create webhook workflow to http://library-desk:8089/documents/webhook
#
# 6. ClamAV is running on host at 192.168.86.149:3310
# Configure Library Desk with:
# CLAMAV_HOST=192.168.86.149
# CLAMAV_PORT=3310
# CLAMAV_ENABLED=true
-31
View File
@@ -1,31 +0,0 @@
version: '3.8'
# Portainer - Container Management UI
# Phase 1: Foundation Setup
# Ports: 8080 (HTTP), 8443 (HTTPS)
# GPU: No
# Storage: Docker volume (portainer_data)
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: always
ports:
- "8080:9000" # Main Portainer web UI
- "8443:9443" # Portainer HTTPS access
volumes:
- /var/run/docker.sock:/var/run/docker.sock # Docker socket for container management
- portainer_data:/data # Persistent data storage
environment:
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:9000/api/system/status || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
volumes:
portainer_data:
name: portainer_data
-137
View File
@@ -1,137 +0,0 @@
version: '3.8'
# Shared PostgreSQL Database
# Purpose: Centralized database for all homelab applications
# Port: 5432
# GPU: No
# Storage: SSD (PostgreSQL data and backups)
services:
postgres-shared:
image: postgres:16-alpine
container_name: postgres-shared
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
start_period: 20s
interval: 30s
retries: 5
timeout: 5s
ports:
- "5432:5432"
volumes:
- /home/jpmschweitzer/docker-data/postgres-shared/data:/var/lib/postgresql/data
- /home/jpmschweitzer/docker-data/postgres-shared/backups:/backups
environment:
POSTGRES_PASSWORD: ${POSTGRES_ADMIN_PASSWORD:?admin password required}
TZ: Europe/Amsterdam
# Performance tuning (adjust based on available RAM)
# Shared buffers: 25% of RAM allocated to PostgreSQL
POSTGRES_SHARED_BUFFERS: 512MB
# Effective cache: 50-75% of RAM allocated to PostgreSQL
POSTGRES_EFFECTIVE_CACHE_SIZE: 2GB
# Max connections: adjust based on number of applications
POSTGRES_MAX_CONNECTIONS: 200
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
memory: 512M
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Setup Instructions:
#
# 1. Create directories:
# mkdir -p ~/docker-data/postgres-shared/{data,backups}
#
# 2. Deploy stack via core-api (recommended) or docker-compose
#
# 3. Initialize databases (run ONCE after first deployment):
# docker exec -i postgres-shared psql -U postgres <<'EOF'
# -- Authentik database
# CREATE DATABASE authentik;
# CREATE USER authentik_user WITH PASSWORD 'F//j0ktck7cX06Vfgh0YXceONOtlSsHvadqROICeDx8=';
# GRANT ALL PRIVILEGES ON DATABASE authentik TO authentik_user;
# \c authentik
# GRANT ALL ON SCHEMA public TO authentik_user;
# ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO authentik_user;
# ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authentik_user;
#
# -- Gitea database
# \c postgres
# CREATE DATABASE gitea;
# CREATE USER gitea_user WITH PASSWORD 'cCav64d76NX1zdEEAbVOM9uvao14aY8HojjNdxsSpMM=';
# GRANT ALL PRIVILEGES ON DATABASE gitea TO gitea_user;
# \c gitea
# GRANT ALL ON SCHEMA public TO gitea_user;
# ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO gitea_user;
# ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO gitea_user;
# EOF
#
# 4. Verify deployment:
# docker exec postgres-shared pg_isready
# docker exec postgres-shared psql -U postgres -c '\l'
#
# Database Connection Examples:
#
# From containers on docker-dataplane network:
# Host: postgres-shared
# Port: 5432
# Database: authentik (or gitea, etc.)
# User: authentik_user (or gitea_user, etc.)
# Password: <app-specific-password>
#
# From host machine:
# psql -h localhost -U authentik_user -d authentik
#
# Monitoring:
#
# Active connections per database:
# docker exec postgres-shared psql -U postgres -c \
# "SELECT datname, numbackends FROM pg_stat_database;"
#
# Database sizes:
# docker exec postgres-shared psql -U postgres -c \
# "SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database;"
#
# Backup:
#
# All databases:
# docker exec postgres-shared pg_dumpall -U postgres | \
# gzip > ~/docker-data/postgres-shared/backups/all-$(date +%Y%m%d).sql.gz
#
# Single database:
# docker exec postgres-shared pg_dump -U postgres authentik | \
# gzip > ~/docker-data/postgres-shared/backups/authentik-$(date +%Y%m%d).sql.gz
#
# Restore:
# gunzip < backup.sql.gz | docker exec -i postgres-shared psql -U postgres
#
# Maintenance:
#
# Vacuum analyze (optimize performance):
# docker exec postgres-shared psql -U postgres -c "VACUUM ANALYZE;"
#
# Reindex (if queries slow):
# docker exec postgres-shared psql -U postgres -d authentik -c "REINDEX DATABASE authentik;"
#
# Resource Usage (expected):
# CPU: ~0.5-1.5 cores (depends on query load)
# RAM: ~500MB-1.5GB (depends on active connections and cache)
# Storage: Grows with data (monitor with: df -h ~/docker-data/postgres-shared)
#
# Applications Using This Database:
# - Authentik (identity provider)
# - Gitea (git hosting) - migrated from dedicated instance
# - Nextcloud (personal cloud storage) - migrated from MariaDB
# - Paperless-ngx (document management) - DB: paperless, User: paperless_user
# - Future applications as needed
-69
View File
@@ -1,69 +0,0 @@
version: '3.8'
# Qdrant Vector Database
# Purpose: Efficient vector storage for Open WebUI RAG (conversation memory & documents)
# Ports: 6333 (HTTP API), 6334 (gRPC)
# GPU: NO - CPU-based vector operations are efficient
# Storage: SSD for vector data (performance-critical)
services:
qdrant:
image: qdrant/qdrant:latest
container_name: qdrant
restart: unless-stopped
ports:
- "6333:6333" # HTTP API
- "6334:6334" # gRPC API
volumes:
# Vector storage on SSD for performance
- /home/jpmschweitzer/docker-data/qdrant/storage:/qdrant/storage
# Snapshots for backups
- /home/jpmschweitzer/docker-data/qdrant/snapshots:/qdrant/snapshots
environment:
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:6333/readyz || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
resources:
limits:
cpus: '1.0'
memory: 768M
reservations:
memory: 256M
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Qdrant Performance Notes:
# - Optimized for high-dimensional vectors (embeddings)
# - Supports HNSW indexing for fast similarity search
# - Efficient memory usage (~1-2GB for thousands of documents)
# - No GPU required (CPU operations are fast enough)
#
# Storage Estimates:
# - ~1KB per conversation turn (with embedding)
# - 10,000 turns = ~10MB
# - Very efficient for conversation memory
#
# After Deployment:
# 1. Check logs: docker logs qdrant
# 2. Access UI: http://localhost:6333/dashboard
# 3. Verify API: curl http://localhost:6333/collections
#
# Integration with Open WebUI:
# - Set VECTOR_DB=qdrant in Open WebUI
# - Set QDRANT_URL=http://qdrant:6333
# - Open WebUI will automatically create collections
#
# Collections Created:
# - Documents: User-uploaded files for RAG
# - Conversations: Chat history for memory
# - Web search results: Cached search results
-159
View File
@@ -1,159 +0,0 @@
version: '3.8'
# Shared Redis Cache
# Purpose: Centralized cache and session store for all homelab applications
# Port: 6379
# GPU: No
# Storage: SSD (Redis persistence - AOF and RDB)
services:
redis-shared:
image: redis:alpine
container_name: redis-shared
restart: unless-stopped
command: >
redis-server
--appendonly yes
--appendfsync everysec
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--save 60 1000
--save 300 100
--save 900 1
--loglevel warning
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
ports:
- "6379:6379"
volumes:
- /home/jpmschweitzer/docker-data/redis-shared/data:/data
environment:
TZ: Europe/Amsterdam
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
memory: 128M
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Setup Instructions:
#
# 1. Create directories:
# mkdir -p ~/docker-data/redis-shared/data
#
# 2. Deploy stack:
# docker-compose -f redis-shared.yml up -d
#
# 3. Verify deployment:
# docker exec redis-shared redis-cli ping
#
# Database Allocation:
#
# Redis supports 16 databases (0-15). Assign one per application:
#
# DB 0: Authentik (sessions, cache, message queue)
# 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 (file locking, distributed cache, sessions)
# DB 8: Paperless (task queue, cache)
# DB 9-15: Reserved for future applications
#
# Connection Examples:
#
# From containers on docker-dataplane network:
# redis://redis-shared:6379/1 (Authentik, DB 1)
# redis://redis-shared:6379/2 (Gitea, DB 2)
#
# From host machine:
# redis-cli -h localhost
# SELECT 1 (switch to database 1)
#
# Monitoring:
#
# General info:
# docker exec redis-shared redis-cli INFO
#
# Memory usage:
# docker exec redis-shared redis-cli INFO memory
#
# Keyspace (keys per database):
# docker exec redis-shared redis-cli INFO keyspace
#
# Stats:
# docker exec redis-shared redis-cli INFO stats
#
# Per-database keys:
# docker exec redis-shared redis-cli -n 1 DBSIZE (database 1)
# docker exec redis-shared redis-cli -n 2 DBSIZE (database 2)
#
# Backup:
#
# Trigger background save:
# docker exec redis-shared redis-cli BGSAVE
#
# Copy RDB file:
# cp ~/docker-data/redis-shared/data/dump.rdb \
# ~/backups/redis-$(date +%Y%m%d).rdb
#
# Backup AOF (append-only file):
# cp ~/docker-data/redis-shared/data/appendonly.aof \
# ~/backups/redis-aof-$(date +%Y%m%d).aof
#
# Restore:
# docker stop redis-shared
# cp backup-dump.rdb ~/docker-data/redis-shared/data/dump.rdb
# docker start redis-shared
#
# Maintenance:
#
# Clear specific database (DANGER - data loss!):
# docker exec redis-shared redis-cli -n 1 FLUSHDB
#
# Clear all databases (DANGER - total data loss!):
# docker exec redis-shared redis-cli FLUSHALL
#
# Rewrite AOF (compact log file):
# docker exec redis-shared redis-cli BGREWRITEAOF
#
# Configuration Details:
#
# Persistence strategy (dual):
# - AOF (Append Only File): Real-time durability, fsync every second
# - RDB Snapshots: Periodic snapshots (every 60s if 1000+ keys changed)
#
# Memory policy:
# - Max memory: 512MB
# - Eviction: allkeys-lru (Least Recently Used eviction when full)
#
# Resource Usage (expected):
# CPU: ~0.1-0.3 cores (low CPU, very efficient)
# RAM: ~100-400MB (depends on data, capped at 512MB)
# Storage: ~50-200MB (AOF + RDB files)
#
# Applications Using This Cache:
# - Authentik (sessions, policies, background tasks)
# - Gitea (sessions, cache, queues) - if migrated
# - Nextcloud (file locking, distributed cache, sessions)
# - Future applications as needed
#
# Performance Tips:
# - Use pipeline commands for bulk operations
# - Set appropriate TTL (Time To Live) on cached keys
# - Monitor memory usage to prevent eviction storms
# - Use database numbers to isolate application data
-86
View File
@@ -1,86 +0,0 @@
version: '3.8'
# Samba - Network File Sharing (SMB/CIFS)
# Backlog: Application Deployment
# Ports: 139, 445
# GPU: No
# Storage: HDD (shares from media drive)
services:
samba:
image: dperson/samba
container_name: samba
restart: unless-stopped
ports:
- "139:139"
- "445:445"
environment:
- TZ=Europe/Amsterdam
- USERID=1000 # Your user ID (run: id -u)
- GROUPID=1000 # Your group ID (run: id -g)
volumes:
# Config on SSD
- /home/jpmschweitzer/docker-data/samba:/share/config
# Shares from HDD
- /mnt/media/jellyfin:/share/media # Media files (read/write)
- /mnt/media/downloads:/share/downloads # Downloads folder
- /mnt/media/backups:/share/backups:ro # Backups (read-only)
command: >
-s "Media;/share/media;yes;no;yes;all"
-s "Downloads;/share/downloads;yes;no;no;all"
-s "Backups;/share/backups;yes;no;yes;all"
-u "jpmschweitzer;IG3omTybtVW3pVmmBi1D5FjnQ0MnZLUG"
-p
healthcheck:
test: ["CMD-SHELL", "pgrep smbd || exit 1"]
interval: 10m
timeout: 10s
retries: 3
start_period: 30s
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# ⚠️ SECURITY WARNING:
# Change CHANGEME_SAMBA_PASSWORD before deploying!
#
# Share Configuration Format:
# -s "ShareName;/path;browseable;readonly;guest;users"
#
# Current Shares:
# 1. Media - Read/write access to Jellyfin media
# 2. Downloads - Read/write downloads folder (guest: no)
# 3. Backups - Read-only access to backups
#
# Note: Nextcloud files accessible via web interface at https://cloud.schweitz.net
#
# Access from Clients:
#
# Windows:
# 1. Open File Explorer
# 2. Address bar: \\tower-of-joy\Media
# 3. Enter credentials: jpmschweitzer / (your password)
#
# Mac:
# 1. Finder → Go → Connect to Server
# 2. Enter: smb://tower-of-joy/Media
# 3. Enter credentials
#
# Linux:
# 1. Install smbclient: sudo apt install smbclient
# 2. List shares: smbclient -L tower-of-joy -U jpmschweitzer
# 3. Connect: smbclient //tower-of-joy/Media -U jpmschweitzer
# Or mount: sudo mount -t cifs //tower-of-joy/Media /mnt/media -o username=jpmschweitzer
#
# Mobile (iOS/Android):
# Use file manager apps that support SMB (e.g., FE File Explorer, Solid Explorer)
#
# Firewall Configuration:
# If using UFW, allow Samba:
# sudo ufw allow 139/tcp
# sudo ufw allow 445/tcp
-85
View File
@@ -1,85 +0,0 @@
version: '3.8'
# The Scheduler
# Purpose: System-wide maintenance orchestration - backups, doc mirroring, cleanup, task automation
# Port: 8090 (API + UI)
# Network: docker-dataplane
# Image: git.schweitz.internal/jpmschweitzer/scheduler (internal registry)
services:
scheduler:
image: git.schweitz.internal/jpmschweitzer/scheduler:latest
container_name: scheduler
restart: unless-stopped
ports:
- "8090:8090"
environment:
- APP_NAME=The Scheduler
- DEBUG=true
- HOST=0.0.0.0
- PORT=8090
- LOG_LEVEL=INFO
- POSTGRES_HOST=postgres-shared
- POSTGRES_PORT=5432
- POSTGRES_DB=scheduler
- POSTGRES_USER=scheduler_user
- POSTGRES_PASSWORD=${SCHEDULER_DB_PASSWORD}
- REDIS_HOST=redis-shared
- REDIS_PORT=6379
- REDIS_DB=3
- GITEA_URL=http://gitea:3000
- GITEA_USER=${GITEA_LIBRARY_USER}
- GITEA_PASSWORD=${GITEA_LIBRARY_PASSWORD}
- GITEA_SSH_HOST=gitea
- GITEA_SSH_PORT=22
- GITEA_TOKEN=${GITEA_TOKEN}
- BACKUP_RETENTION_DAILY=7
- BACKUP_RETENTION_WEEKLY=4
- BACKUP_RETENTION_MONTHLY=12
- DOCS_MIRROR_PATH=/docs-mirror
- DOCS_CHECK_INTERVAL=21600
- SCHEDULER_API_KEY=${SCHEDULER_API_KEY}
- PYTHONPATH=/app
volumes:
# Data volumes only - source code is baked into image
- /home/jpmschweitzer/docker-data/scheduler/logs:/app/logs
- /home/jpmschweitzer/docker-data/scheduler/task-data:/app/task-data
- /home/jpmschweitzer/docker-data/scheduler/ssh:/root/.ssh:ro
- /mnt/media/library/docs-mirror:/docs-mirror
- /mnt/media/backups/library:/backups
- /var/run/docker.sock:/var/run/docker.sock:ro
- /home/jpmschweitzer/docker-data/postgres-shared:/postgres-data:ro
# For config backups (read-only sources)
- /home/jpmschweitzer/docker-data:/data/docker-data:ro
- /home/jpmschweitzer/.config/code-server:/data/code-server-config:ro
# For config backups (write destination)
- /mnt/media/backups/docker-configs:/backups/docker-configs
networks:
- docker-dataplane
deploy:
resources:
limits:
cpus: '0.5'
memory: 1G
reservations:
memory: 256M
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8090/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 90s
networks:
docker-dataplane:
external: true
name: docker-dataplane
-226
View File
@@ -1,226 +0,0 @@
version: '3.8'
# SearXNG Metasearch Engine
# Purpose: Privacy-focused metasearch aggregating 246+ search engines
# Port: 8087 (HTTP API)
# GPU: NO - Pure CPU application
# Storage: Minimal (config only, no data persistence)
# Integration: Core-AI service tool for web search capability
services:
searxng:
image: searxng/searxng:latest
container_name: searxng
restart: unless-stopped
ports:
- "8087:8080" # HTTP API (JSON format enabled)
volumes:
# Mount config directory (will be auto-populated on first run)
- /home/jpmschweitzer/docker-data/searxng:/etc/searxng:rw
environment:
- TZ=Europe/Amsterdam
- SEARXNG_BASE_URL=http://searxng:8087/
# Valkey/Redis configuration for result caching (using shared Redis DB 5)
- SEARXNG_VALKEY_URL=redis://redis-shared:6379/5
# Enabled output formats (JSON required for API access)
- SEARXNG_SETTINGS_FORMATS=html,json
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
memory: 128M
networks:
- docker-dataplane
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
docker-dataplane:
external: true
name: docker-dataplane
# SearXNG Overview:
# - Metasearch engine: Aggregates results from 246+ search sources
# - Privacy-first: No tracking, no profiling, no data collection
# - Multi-format: HTML (web UI), JSON (API), CSV, RSS
# - Customizable: Enable/disable specific engines per category
#
# Search Categories:
# - general: Web search (Google, Bing, DuckDuckGo, etc.)
# - images: Image search
# - videos: Video search
# - news: News articles
# - map: Geographic/location
# - music: Music/audio
# - it: Programming/technical (StackOverflow, GitHub, docs)
# - science: Academic (arXiv, PubMed, Semantic Scholar)
# - files: File repositories
# - social media: Social platforms
#
# Setup Instructions:
#
# 1. Create configuration directory:
# mkdir -p ~/docker-data/searxng/config
#
# 2. Deploy stack:
# docker-compose -f searxng.yml up -d
#
# Note: First run will auto-generate settings.yml with secret key
#
# 3. Stop container to edit config:
# docker stop searxng
#
# 4. Enable JSON format in settings.yml:
# Edit ~/docker-data/searxng/config/settings.yml
# Find the 'search:' section and set:
# formats:
# - html
# - json
#
# 5. Restart container:
# docker start searxng
#
# 6. Verify deployment:
# # Web UI test:
# curl http://localhost:8087/
#
# # JSON API test:
# curl "http://localhost:8087/search?q=python&format=json" | jq '.results[0]'
#
# # Health check:
# curl http://localhost:8087/healthz
#
# API Usage:
#
# Basic search:
# GET http://searxng:8080/search?q=query&format=json
#
# With category filter:
# GET http://searxng:8080/search?q=machine+learning&format=json&categories=science
#
# With language:
# GET http://searxng:8080/search?q=query&format=json&language=en
#
# With time range:
# GET http://searxng:8080/search?q=news&format=json&time_range=day
#
# Available categories:
# general, images, videos, news, map, music, it, science, files, social_media
#
# Time ranges:
# day, week, month, year
#
# Response format (JSON):
# {
# "query": "search term",
# "results": [
# {
# "url": "https://example.com",
# "title": "Page title",
# "content": "Description snippet",
# "engine": "google",
# "score": 1.0
# }
# ],
# "suggestions": ["related", "searches"],
# "number_of_results": 42
# }
#
# Integration with Core-AI:
#
# Add to services/core-ai/src/tools/local.py:
#
# @register_tool
# async def web_search(query: str, category: str = "general") -> str:
# """Search the web using SearXNG metasearch engine."""
# response = await httpx.get(
# "http://searxng:8080/search",
# params={"q": query, "format": "json", "categories": category},
# timeout=10.0
# )
# results = response.json()["results"][:5]
# return "\n\n".join([
# f"[{r['title']}]({r['url']})\n{r.get('content', '')}"
# for r in results
# ])
#
# Performance Tuning:
#
# Response times: 2-5 seconds (aggregating multiple sources)
# Redis caching: Reduces duplicate queries (DB 5 on redis-shared)
# Engine selection: Disable slow engines to improve speed
#
# Edit settings.yml to disable slow engines:
# engines:
# - name: slowengine
# disabled: true
#
# Resource Usage (expected):
# CPU: ~0.2-0.5 cores (varies with query load)
# RAM: ~150-300MB (depends on cache size)
# Disk: ~10-50MB (config only, no data storage)
# Network: Variable (depends on upstream engine responses)
#
# Redis Database Allocation:
# DB 5: SearXNG result cache
#
# Security:
# - Dropped all capabilities except essential (CHOWN, SETGID, SETUID)
# - No data persistence (privacy by design)
# - Can run behind Nginx Proxy Manager for HTTPS
# - Optional Tor support (requires additional config)
#
# Monitoring:
#
# View logs:
# docker logs -f searxng
#
# Check engine stats:
# curl http://localhost:8080/stats
#
# Check health:
# docker exec searxng wget -q -O- http://localhost:8080/healthz
#
# Engine Configuration Tips:
#
# To optimize for AI/LLM use cases, consider enabling these engines:
# - General: google, bing, duckduckgo, brave
# - Technical: stackoverflow, github, devdocs, mdn
# - Academic: arxiv, pubmed, semantic_scholar, google_scholar
# - Documentation: readthedocs, man (Linux man pages)
#
# Disable to improve speed:
# - Slow engines (check /stats page)
# - Engines you don't need (social media if not relevant)
# - Engines with frequent timeouts
#
# Advanced Configuration:
#
# Custom engines can be added to settings.yml
# See: https://docs.searxng.org/dev/engines/index.html
#
# Limiter (rate limiting) can be configured to prevent abuse
# See: https://docs.searxng.org/admin/engines/settings.html#limiter
#
# After Deployment:
# 1. Access UI: http://localhost:8080
# 2. Test JSON API: curl "http://localhost:8080/search?q=test&format=json"
# 3. Review engine stats: http://localhost:8080/stats
# 4. Integrate with core-ai service
-44
View File
@@ -1,44 +0,0 @@
version: '3.8'
# Tatlock UI - Home Lab Dashboard (Flutter Web)
# Port: 9999 (HTTP)
# GPU: No
# External: home.schweitz.net
# Storage: None (stateless static web app)
#
# Container image built from: git.schweitz.internal/jpmschweitzer/tatlock-ui
# See CONTAINERS.md for port allocation reference
services:
tatlock-ui:
image: git.schweitz.internal/jpmschweitzer/tatlock-ui:latest
container_name: tatlock-ui
restart: unless-stopped
ports:
- "9999:80"
networks:
- docker-dataplane
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:80/ || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
deploy:
resources:
limits:
memory: 128M
reservations:
memory: 32M
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Access:
# - Internal: http://tower-of-joy:9999
# - Mesh VPN: http://10.99.0.1:9999
# - External: https://home.schweitz.net
-70
View File
@@ -1,70 +0,0 @@
version: '3.8'
# Watchtower - Automatic Container Updates
# Phase 4: Optimization & Security
# Ports: None (runs as background service)
# GPU: No
# Storage: None (reads Docker socket)
services:
watchtower:
image: containrrr/watchtower:latest
container_name: watchtower
restart: unless-stopped
ports:
- "8070:8080" # HTTP API for triggering updates
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_CLEANUP=true # Remove old images after update
- WATCHTOWER_SCHEDULE=0 0 4 * * * # Run at 4 AM daily (cron format)
- TZ=Europe/Amsterdam
# HTTP API for CI/CD triggered updates
- WATCHTOWER_HTTP_API_UPDATE=true
- WATCHTOWER_HTTP_API_TOKEN=${WATCHTOWER_API_TOKEN}
- WATCHTOWER_HTTP_API_PERIODIC_POLLS=true # Allow triggering periodic poll via API
# Optional: Enable notifications
# - WATCHTOWER_NOTIFICATIONS=shoutrrr
# - WATCHTOWER_NOTIFICATION_URL= # Add notification URL (Discord, Slack, etc.)
# Optional: Monitor only specific containers
# - WATCHTOWER_LABEL_ENABLE=true # Only update containers with label com.centurylinklabs.watchtower.enable=true
healthcheck:
test: ["CMD-SHELL", "pgrep watchtower || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Schedule Format (cron):
# - 0 0 4 * * * = Daily at 4 AM
# - 0 0 4 * * SUN = Weekly on Sunday at 4 AM
# - 0 0 */6 * * * = Every 6 hours
#
# Manual Trigger:
# docker exec watchtower watchtower --run-once
#
# HTTP API Trigger (for CI/CD):
# curl -H "Authorization: Bearer $WATCHTOWER_API_TOKEN" http://localhost:8070/v1/update
#
# Exclude Specific Containers:
# Add label to container: com.centurylinklabs.watchtower.enable=false
#
# Monitor Watchtower Activity:
# docker logs watchtower
#
# Security Note:
# Watchtower has full Docker socket access. Review updates in logs.
# Consider excluding critical services and updating them manually.
#
# Environment Variables (set in Portainer):
# - WATCHTOWER_API_TOKEN: Secret token for HTTP API authentication
-97
View File
@@ -1,97 +0,0 @@
version: '3.8'
# Library - Wiki.js (Knowledge Wiki)
# Application Layer
# Port: 3000 (HTTP)
# GPU: No
# Storage: PostgreSQL (shared), SSD (uploads)
services:
wiki:
image: ghcr.io/requarks/wiki:2
container_name: wiki
restart: unless-stopped
ports:
- "3000:3000" # HTTP web interface
volumes:
# Uploads and backups on HDD
- /mnt/media/library/wiki:/wiki/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
environment:
# Database configuration (PostgreSQL shared)
- DB_TYPE=postgres
- DB_HOST=postgres-shared
- DB_PORT=5432
- DB_NAME=library
- DB_USER=library_user
- DB_PASS=${LIBRARY_DB_PASSWORD}
# Redis cache configuration (DB 2)
- REDIS_HOST=redis-shared
- REDIS_PORT=6379
- REDIS_DB=2
# Application configuration
- WIKI_ADMIN_EMAIL=admin@schweitz.net
- HA_ACTIVE=false
- TZ=${TZ:-Europe/Amsterdam}
networks:
- docker-dataplane
deploy:
resources:
reservations:
memory: 256M
limits:
memory: 1G
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
docker-dataplane:
external: true
name: docker-dataplane
# ⚠️ SECURITY WARNING:
# Set LIBRARY_DB_PASSWORD in environment variables before deploying!
# This should match the password created in PostgreSQL (see DEPLOYMENT.md Phase 1.1)
#
# After Deployment:
# 1. Access http://wiki:3000
# 2. Complete initial setup wizard:
# - Admin account (use strong password!)
# - Site URL: http://wiki:3000 or https://library.schweitz.net
# - Telemetry: Optional
# 3. Enable API Access:
# - Administration → API Access
# - Generate New Key → Save to .env.library as WIKIJS_API_KEY
# 4. Configure storage:
# - Administration → Storage
# - Enable Git storage (optional, for version control)
#
# Features:
# - Markdown editing with live preview
# - Cross-dossier linking (wikilinks)
# - Full-text search
# - Version history
# - User authentication and authorization
# - API for Front Desk integration
# - Mind map embedding (via Front Desk)
#
# Integration:
# - Front Desk proxies CRUD operations via Wiki.js API
# - Content changes trigger re-indexing in Qdrant
# - Entity extraction updates Neo4j graph
#
# Backups:
# - Database: Managed by Scheduler (daily, 02:00)
# - Content export: Managed by Scheduler (daily, 02:00)
# - Location: /mnt/media/backups/library/wikijs/
#
# External Access (Optional):
# - Nginx Proxy Manager: library.schweitz.net → library-wiki:3000
# - SSL: Let's Encrypt via NPM