Compare commits
24
Commits
e8021307da
...
main
@@ -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": []
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,10 @@ docker-compose.override.yml
|
||||
# Local configuration (machine-specific)
|
||||
local.yml
|
||||
local.config
|
||||
.claude/settings.local.json
|
||||
|
||||
# Jupyter notebook checkpoints
|
||||
.ipynb_checkpoints/
|
||||
|
||||
# Archive files
|
||||
*.zip
|
||||
|
||||
@@ -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.
|
||||
|
||||
**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:**
|
||||
- **SSD (489GB):** Container configs, databases, Docker images - `/home/jpmschweitzer/docker-data/`
|
||||
|
||||
+144
-9
@@ -1,7 +1,7 @@
|
||||
# Container Reference - tower-of-joy Infrastructure
|
||||
|
||||
> **Last Updated:** 2026-01-09
|
||||
> **Total Services:** 31 containers across 20 stacks
|
||||
> **Last Updated:** 2026-02-26
|
||||
> **Total Services:** 36 containers across 21 stacks
|
||||
> **System:** Intel i7-6700, RTX 2080 Ti (11GB VRAM), 64GB RAM, Zorin OS 16.3
|
||||
|
||||
---
|
||||
@@ -35,12 +35,15 @@
|
||||
| **Tatlock** | 8000 | https://tatlock.schweitz.net | Internet (SSO) | No | 1, 6 | ✅ Running (external) |
|
||||
| **Webber** | 8086 | http://192.168.86.149:8086 | LAN | No | 9 | ✅ Running (external) |
|
||||
| **Library Desk** | 8089 | http://192.168.86.149:8089 | LAN | No | 4 | ✅ Running (external) |
|
||||
| **AMP (Game Server)** | Varies | http://192.168.86.149:8081 | LAN | No | - | ✅ Running |
|
||||
| **AMP (Game Server)** | 8080-8082 | https://amp.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 |
|
||||
| **Penpot** | 9001 | https://penpot.schweitz.net | Internet (SSO) | No | 10 | ✅ Running |
|
||||
| **ClamAV** | 3310 | N/A (host service) | No | No | - | ✅ Running |
|
||||
| **AdGuard Home** | 53, 3053 | http://dns.schweitz.internal | LAN (DNS) | 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)
|
||||
- **home.schweitz.net** → Tatlock UI
|
||||
@@ -53,6 +56,7 @@
|
||||
- **amp.schweitz.net** → AMP Game Server
|
||||
- **housekeeping.schweitz.net** → Home Assistant
|
||||
- **documents.schweitz.net** → Paperless-ngx
|
||||
- **penpot.schweitz.net** → Penpot (Protected by Authentik SSO)
|
||||
- **library.schweitz.net** → Wiki.js
|
||||
- **tatlock.schweitz.net** → Tatlock API (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 |
|
||||
| housekeeping.schweitz.internal | localhost | 8123 |
|
||||
| documents.schweitz.internal | 192.168.86.149 | 8091 |
|
||||
| penpot.schweitz.internal | localhost | 9001 |
|
||||
| git.schweitz.internal | localhost | 3002 |
|
||||
| library.schweitz.internal | localhost | 8088 |
|
||||
| tatlock.schweitz.internal | localhost | 8000 |
|
||||
@@ -158,8 +163,8 @@ PostgreSQL Shared is a centralized PostgreSQL 17 database server providing isola
|
||||
| **Resource Limits** | None |
|
||||
| **GPU Required** | No |
|
||||
| **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) |
|
||||
| **Database Users** | `authentik_user`, `gitea_user`, `paperless_user`, `settings` (system_settings RW), `media_user` (sonarr/radarr/prowlarr), `postgres` (superuser) |
|
||||
| **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`, `penpot_user`, `settings` (system_settings RW), `media_user` (sonarr/radarr/prowlarr), `postgres` (superuser) |
|
||||
| **Health Check** | `pg_isready -U postgres` (30s interval) |
|
||||
| **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 |
|
||||
| **GPU Required** | No |
|
||||
| **Dependencies** | docker-dataplane network |
|
||||
| **Database Allocation** | DB 0: Available, DB 1: Tatlock (memory), DB 2: Wiki.js, DB 3: Scheduler, DB 4: Library Desk, DB 5: SearXNG, DB 6: Tatlock (benchmarks), DB 7: Nextcloud, DB 8: Paperless, DB 9: 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 |
|
||||
| **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. |
|
||||
@@ -472,6 +477,35 @@ Webber is an LLM agent orchestration API that provides autonomous agent executio
|
||||
|
||||
---
|
||||
|
||||
### AMP (Game Server Manager)
|
||||
|
||||
AMP (Application Management Panel) by CubeCoders is a game server management panel that provides a web interface for deploying, configuring, and managing multiple game servers including Minecraft, Vintage Story, and many others. The ADS (Application Deployment Service) controller runs as a standalone Docker container on host network, spawning individual game server instances as separate containers. The setup uses a custom entrypoint wrapper to handle Docker socket permissions, allowing AMP to manage child containers while running as a non-root user.
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Image** | `mitchtalmadge/amp-dockerized:latest` |
|
||||
| **Container Name** | `amp-ads` |
|
||||
| **Deployment** | Standalone docker-compose (not Portainer stack) |
|
||||
| **Access URL (LAN)** | http://192.168.86.149:8080 |
|
||||
| **Access URL (Public)** | https://amp.schweitz.net |
|
||||
| **External Access** | Yes (via NPM reverse proxy with SSL) |
|
||||
| **Port Mapping** | 8080 (ADS), 8081 (justcreate01), 8082 (vine01), plus game ports |
|
||||
| **Network Mode** | Host (required for ADS to reach game containers) |
|
||||
| **Restart Policy** | `unless-stopped` |
|
||||
| **Volume Mounts** | `~/docker-data/amp:/home/amp/.ampdata` (SSD), `/mnt/media/amp/instances` (HDD via symlink) |
|
||||
| **Environment** | `UID=1013`, `GID=1014`, `TZ=Europe/Amsterdam`, `MODULE=ADS` |
|
||||
| **Resource Limits** | None |
|
||||
| **GPU Required** | No |
|
||||
| **Dependencies** | Docker socket, custom entrypoint wrapper for permissions |
|
||||
| **Compose Location** | `/home/jpmschweitzer/docker-data/amp/docker-compose.yml` |
|
||||
| **Game Instances** | justcreate01 (Minecraft NeoForge), vine01 (Vintage Story) |
|
||||
| **License** | Static MAC address (`02:42:ac:11:00:a0`) for license persistence |
|
||||
| **Health Check** | `curl -fSs http://localhost:8080` (30s interval) |
|
||||
|
||||
**Note:** Game server containers (`AMP_justcreate01`, `AMP_vine01`) are spawned dynamically by AMP and appear as standalone containers in Portainer, not grouped under a stack.
|
||||
|
||||
---
|
||||
|
||||
### Jellyfin
|
||||
|
||||
Jellyfin is a GPU-accelerated media server that organizes, streams, and transcodes video, music, and photo libraries with hardware encoding via NVIDIA NVENC, enabling smooth 4K playback across multiple simultaneous clients without taxing the CPU. It provides a Netflix-like interface accessible through web browsers, mobile apps, and smart TV clients, with automatic metadata fetching, subtitle support, and user management for family sharing. The service stores configuration and cache on the SSD for responsive browsing while accessing massive media libraries on the 3.7TB HDD, supporting direct play when possible and GPU-accelerated transcoding when format conversion is needed. Part of the unified media stack with Sonarr, Radarr, Prowlarr, and SABnzbd.
|
||||
@@ -678,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 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.
|
||||
@@ -789,6 +914,8 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is
|
||||
| **Samba** | \\\\192.168.86.149 | No | Network file shares |
|
||||
| **Home Assistant** | https://housekeeping.schweitz.net | Yes | Smart home automation |
|
||||
| **Tatlock UI** | https://home.schweitz.net | Yes | Home lab dashboard |
|
||||
| **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 |
|
||||
|
||||
---
|
||||
@@ -799,9 +926,13 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is
|
||||
|---------|-----------|-------------------|---------|
|
||||
| **Ollama** | Compute, Utility | 2-10GB (model dependent) | LLM inference |
|
||||
| **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)
|
||||
|
||||
**Note:** Stable Audio, TRELLIS, and Ollama share GPU. For best results, stop competing services during heavy generation tasks.
|
||||
|
||||
---
|
||||
|
||||
### Storage Distribution
|
||||
@@ -825,10 +956,14 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is
|
||||
| **Nextcloud** | `~/docker-data/nextcloud/` | `/mnt/media/nextcloud/data/` | Config: ~200MB, DB: ~100MB, User data: variable |
|
||||
| **Gitea** | `~/docker-data/gitea/` | N/A | Data: ~100MB, DB: ~50MB, Repos: variable |
|
||||
| **Samba** | `~/docker-data/samba/` | Mounts: `/mnt/media/` (shares) | Config: ~5MB |
|
||||
| **AMP** | `~/docker-data/amp/` | `/mnt/media/amp/instances/` (via symlink) | Config: ~1.1GB (versions), Game data: variable |
|
||||
| **Home Assistant** | `~/docker-data/home-assistant/config/` | N/A | Config: ~100MB, DB: ~50MB |
|
||||
| **Tatlock** | `~/docker-data/tatlock/logs/` | N/A | Logs: ~10MB |
|
||||
| **Webber** | `~/docker-data/webber/logs/`, `~/docker-data/webber/sandbox/` | N/A | Logs: ~10MB, Sandbox: variable |
|
||||
| **Penpot** | `~/docker-data/penpot/assets/` | N/A | Assets: variable (uploads, exports) |
|
||||
| **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)
|
||||
**HDD Usage (/mnt/media):** ~2.1TB / 3.6TB (58% used)
|
||||
@@ -841,8 +976,8 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is
|
||||
|
||||
| Network Name | Containers | Purpose |
|
||||
|--------------|------------|---------|
|
||||
| **docker-dataplane** | Ollama, Open WebUI, Core API, Qdrant, PostgreSQL Shared, Redis Shared, Headscale, Nextcloud, Gitea, Samba, Watchtower, Tatlock, Webber, Tatlock UI, Home Assistant, Jellyfin, Sonarr, Radarr, Prowlarr, SABnzbd | Unified service mesh for all containerized applications |
|
||||
| **host** | Portainer, NPM | Direct host port access for infrastructure management |
|
||||
| **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 |
|
||||
|
||||
**Benefits of Consolidation**:
|
||||
- **Service Discovery**: All services reachable via `http://container-name:port` (e.g., `http://postgres-shared:5432`)
|
||||
@@ -878,4 +1013,4 @@ redis-cli -h redis-shared # Redis connection
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2026-01-09*
|
||||
*Last Updated: 2026-02-26*
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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*
|
||||
@@ -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
@@ -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 |
|
||||
|-------|------|-------|-----|-------------|
|
||||
| **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 |
|
||||
git log --oneline -- stacks/
|
||||
git show <commit>:stacks/<name>.yml
|
||||
|
||||
### Phase 2: Networking
|
||||
## Two that had no counterpart
|
||||
|
||||
| Stack | File | Ports | GPU | Description |
|
||||
|-------|------|-------|-----|-------------|
|
||||
| **Headscale** | `headscale.yml` | 8085, 9090 | No | Self-hosted Tailscale control server |
|
||||
|
||||
### Phase 3: Optimization
|
||||
|
||||
| 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*
|
||||
`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
|
||||
add it to `system-admin-toj/containers/stacks/` rather than reviving this
|
||||
directory. Penpot is decommissioned (its Redis DB 10 is marked available again
|
||||
in `CONTAINERS.md`).
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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: 384M
|
||||
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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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/
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
# }'
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user