chore(stacks): remove the deprecated stack copies

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

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

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

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-08 17:00:48 +02:00
co-authored by Claude
parent 57139f0f54
commit 2b544ae6aa
28 changed files with 23 additions and 3816 deletions
+23 -132
View File
@@ -1,140 +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 |
| **Models** | `models.yml` | 11434, 11500, 11510 | **Yes** | GPU model serving (Ollama, Stable Audio, TRELLIS) |
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, 5580 | No | Smart home automation + Matter Server |
## 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
- 11500: Stable Audio Open
- 11510: TRELLIS (3D model generation)
### 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):
- `models.yml` - Ollama (LLM inference), Stable Audio (~6GB VRAM), TRELLIS (~6-8GB VRAM)
- `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`).
-56
View File
@@ -1,56 +0,0 @@
version: '3.8'
# AdGuard Home - Network-wide DNS Ad Blocking
# Infrastructure Layer
# Ports: 53 (DNS), 3053 (Web UI)
# GPU: No
# Storage: SSD (configs and work data)
services:
adguard:
image: adguard/adguardhome:latest
container_name: adguard
restart: unless-stopped
ports:
- "192.168.86.149:53:53/tcp" # DNS TCP (bound to LAN IP to avoid systemd-resolved conflict)
- "192.168.86.149:53:53/udp" # DNS UDP
- "3053:3000/tcp" # Web UI
volumes:
# SSD storage for configs and query logs
- /home/jpmschweitzer/docker-data/adguard/work:/opt/adguardhome/work
- /home/jpmschweitzer/docker-data/adguard/conf:/opt/adguardhome/conf
environment:
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "nslookup localhost 127.0.0.1 || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Setup Instructions:
# 1. Deploy this stack via Portainer
# 2. Access setup wizard at http://192.168.86.149:3053
# 3. Configure:
# - Admin interface: Listen on port 3053, all interfaces
# - DNS server: Listen on port 53, all interfaces
# - Create admin username/password
# 4. After setup, configure in AdGuard Home UI:
# - Settings → DNS settings → Upstream DNS:
# https://dns.quad9.net/dns-query
# - Enable "Parallel requests"
# - Filters → DNS blocklists → Add recommended lists
# 5. Configure router DNS to 192.168.86.149
#
# Testing:
# dig @192.168.86.149 google.com # Should resolve
# dig @192.168.86.149 ads.google.com # Should be blocked
#
# Internal access: http://dns.schweitz.internal (requires NPM proxy + /etc/hosts entry)
-155
View File
@@ -1,155 +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)
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- ANTHROPIC_MODEL=claude-sonnet-4-20250514
- PREFER_CLOUD_BACKEND=truec
# 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)
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- ANTHROPIC_MODEL=claude-sonnet-4-20250514
- PREFER_CLOUD_BACKEND=truec
# 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
-670
View File
@@ -1,670 +0,0 @@
version: '3.8'
# Appwrite - Backend-as-a-Service Platform
# Purpose: Self-hosted BaaS for development and testing (auth, database, storage, functions)
# Port: 8093 (console + API via Traefik), 8094 (HTTPS)
# GPU: No
# External: LAN only (dev/testing)
# Storage: SSD (all data), internal MariaDB + Redis
# Containers: ~20 (API, console, realtime, workers, schedulers, executor, MariaDB, Redis)
x-logging: &x-logging
logging:
driver: 'json-file'
options:
max-file: '5'
max-size: '10m'
x-appwrite-env: &appwrite-env
_APP_ENV: production
_APP_WORKER_PER_CORE: 6
_APP_LOCALE: en
_APP_DOMAIN: 192.168.86.149
_APP_DOMAIN_TARGET_CNAME: 192.168.86.149
_APP_DOMAIN_TARGET_A: 192.168.86.149
_APP_DOMAIN_FUNCTIONS: functions.localhost
_APP_DOMAIN_SITES: sites.localhost
_APP_CONSOLE_WHITELIST_ROOT: enabled
_APP_OPENSSL_KEY_V1: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163
_APP_EXECUTOR_SECRET: gbnq4JwfnXBm4J7h7kUNEWt+OhwV3agu
_APP_EXECUTOR_HOST: http://openruntimes-executor/v1
_APP_DB_ADAPTER: mariadb
_APP_DB_HOST: appwrite-mariadb
_APP_DB_PORT: '3306'
_APP_DB_SCHEMA: appwrite
_APP_DB_USER: appwrite
_APP_DB_PASS: ML8QbYgD05+7ZDHuRfmSTgDBr5VusxLi87ifyO5RlJg=
_APP_DB_ROOT_PASS: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163
_APP_REDIS_HOST: appwrite-redis
_APP_REDIS_PORT: '6379'
_APP_REDIS_USER: ''
_APP_REDIS_PASS: ''
_APP_STORAGE_LIMIT: '30000000'
_APP_STORAGE_PREVIEW_LIMIT: '20000000'
_APP_STORAGE_ANTIVIRUS: disabled
_APP_STORAGE_DEVICE: local
_APP_USAGE_STATS: enabled
_APP_OPTIONS_ABUSE: enabled
_APP_OPTIONS_FORCE_HTTPS: disabled
_APP_OPTIONS_ROUTER_PROTECTION: disabled
_APP_OPTIONS_ROUTER_FORCE_HTTPS: disabled
_APP_SYSTEM_EMAIL_NAME: Appwrite
_APP_SYSTEM_EMAIL_ADDRESS: appwrite@schweitz.net
_APP_EMAIL_SECURITY: ''
_APP_SYSTEM_RESPONSE_FORMAT: ''
_APP_GRAPHQL_INTROSPECTION: disabled
_APP_GRAPHQL_MAX_BATCH_SIZE: '10'
_APP_GRAPHQL_MAX_COMPLEXITY: '250'
_APP_GRAPHQL_MAX_DEPTH: '3'
_APP_MAINTENANCE_INTERVAL: '86400'
_APP_MAINTENANCE_RETENTION_EXECUTION: '1209600'
_APP_MAINTENANCE_RETENTION_CACHE: '2592000'
_APP_MAINTENANCE_RETENTION_ABUSE: '86400'
_APP_MAINTENANCE_RETENTION_AUDIT: '1209600'
_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE: '1209600'
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY: '8640000'
_APP_MAINTENANCE_RETENTION_SCHEDULES: '86400'
_APP_FUNCTIONS_TIMEOUT: '900'
_APP_SITES_TIMEOUT: '900'
_APP_COMPUTE_BUILD_TIMEOUT: '900'
_APP_COMPUTE_CPUS: '0'
_APP_COMPUTE_MEMORY: '0'
_APP_COMPUTE_SIZE_LIMIT: '30000000'
_APP_FUNCTIONS_RUNTIMES: node-22.0,python-3.12,php-8.3,dart-3.5,bun-1.1
_APP_SITES_RUNTIMES: static-1.0
_APP_COMPUTE_RUNTIMES_NETWORK: runtimes
_APP_COMPUTE_INACTIVE_THRESHOLD: '60'
_APP_COMPUTE_MAINTENANCE_INTERVAL: '60'
_APP_LOGGING_CONFIG: ''
_APP_DNS: ''
_APP_COMPRESSION_MIN_SIZE_BYTES: '1024'
TZ: Europe/Amsterdam
services:
# ============================================
# Traefik - Internal Reverse Proxy
# Port: 8093 (HTTP), 8094 (HTTPS)
# Routes /console to console, /v1/realtime to realtime, everything else to API
# ============================================
appwrite-traefik:
image: traefik:3.6
container_name: appwrite-traefik
<<: *x-logging
restart: unless-stopped
command:
- --providers.file.directory=/storage/config
- --providers.file.watch=true
- --providers.docker=true
- --providers.docker.exposedByDefault=false
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
- --entrypoints.appwrite_web.address=:80
- --entrypoints.appwrite_websecure.address=:443
ports:
- "8093:80"
- "8094:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /home/jpmschweitzer/docker-data/appwrite/config:/storage/config:ro
- /home/jpmschweitzer/docker-data/appwrite/certificates:/storage/certificates:ro
depends_on:
- appwrite
networks:
- appwrite
- docker-dataplane
# ============================================
# Appwrite API Server
# Internal only - served via Traefik
# ============================================
appwrite:
image: appwrite/appwrite:1.9.0
container_name: appwrite
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
labels:
- traefik.enable=true
- traefik.constraint-label-stack=appwrite
- traefik.docker.network=appwrite
- traefik.http.services.appwrite_api.loadbalancer.server.port=80
- traefik.http.routers.appwrite_api_http.entrypoints=appwrite_web
- traefik.http.routers.appwrite_api_http.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite_api_http.service=appwrite_api
- traefik.http.routers.appwrite_api_https.entrypoints=appwrite_websecure
- traefik.http.routers.appwrite_api_https.rule=PathPrefix(`/`)
- traefik.http.routers.appwrite_api_https.service=appwrite_api
- traefik.http.routers.appwrite_api_https.tls=true
- "com.centurylinklabs.watchtower.enable=false"
volumes:
- /home/jpmschweitzer/docker-data/appwrite/uploads:/storage/uploads:rw
- /home/jpmschweitzer/docker-data/appwrite/imports:/storage/imports:rw
- /home/jpmschweitzer/docker-data/appwrite/cache:/storage/cache:rw
- /home/jpmschweitzer/docker-data/appwrite/config:/storage/config:rw
- /home/jpmschweitzer/docker-data/appwrite/certificates:/storage/certificates:rw
- /home/jpmschweitzer/docker-data/appwrite/functions:/storage/functions:rw
- /home/jpmschweitzer/docker-data/appwrite/sites:/storage/sites:rw
- /home/jpmschweitzer/docker-data/appwrite/builds:/storage/builds:rw
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
# ============================================
# Appwrite Console - Web UI
# Internal only - served via Traefik at /console
# ============================================
appwrite-console:
image: appwrite/console:7.8.26
container_name: appwrite-console
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
labels:
- traefik.enable=true
- traefik.constraint-label-stack=appwrite
- traefik.docker.network=appwrite
- traefik.http.services.appwrite_console.loadbalancer.server.port=80
- traefik.http.routers.appwrite_console_http.entrypoints=appwrite_web
- traefik.http.routers.appwrite_console_http.rule=PathPrefix(`/console`)
- traefik.http.routers.appwrite_console_http.service=appwrite_console
- traefik.http.routers.appwrite_console_https.entrypoints=appwrite_websecure
- traefik.http.routers.appwrite_console_https.rule=PathPrefix(`/console`)
- traefik.http.routers.appwrite_console_https.service=appwrite_console
- traefik.http.routers.appwrite_console_https.tls=true
- "com.centurylinklabs.watchtower.enable=false"
# ============================================
# Appwrite Realtime - WebSocket Server
# Internal only - served via Traefik at /v1/realtime
# ============================================
appwrite-realtime:
image: appwrite/appwrite:1.9.0
entrypoint: realtime
container_name: appwrite-realtime
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
labels:
- traefik.enable=true
- traefik.constraint-label-stack=appwrite
- traefik.docker.network=appwrite
- traefik.http.services.appwrite_realtime.loadbalancer.server.port=80
- traefik.http.routers.appwrite_realtime_ws.entrypoints=appwrite_web
- traefik.http.routers.appwrite_realtime_ws.rule=PathPrefix(`/v1/realtime`)
- traefik.http.routers.appwrite_realtime_ws.service=appwrite_realtime
- traefik.http.routers.appwrite_realtime_wss.entrypoints=appwrite_websecure
- traefik.http.routers.appwrite_realtime_wss.rule=PathPrefix(`/v1/realtime`)
- traefik.http.routers.appwrite_realtime_wss.service=appwrite_realtime
- traefik.http.routers.appwrite_realtime_wss.tls=true
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
# ============================================
# Workers - Background Processing
# ============================================
appwrite-worker-audits:
image: appwrite/appwrite:1.9.0
entrypoint: worker-audits
container_name: appwrite-worker-audits
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-webhooks:
image: appwrite/appwrite:1.9.0
entrypoint: worker-webhooks
container_name: appwrite-worker-webhooks
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-deletes:
image: appwrite/appwrite:1.9.0
entrypoint: worker-deletes
container_name: appwrite-worker-deletes
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- /home/jpmschweitzer/docker-data/appwrite/uploads:/storage/uploads:rw
- /home/jpmschweitzer/docker-data/appwrite/cache:/storage/cache:rw
- /home/jpmschweitzer/docker-data/appwrite/functions:/storage/functions:rw
- /home/jpmschweitzer/docker-data/appwrite/sites:/storage/sites:rw
- /home/jpmschweitzer/docker-data/appwrite/builds:/storage/builds:rw
- /home/jpmschweitzer/docker-data/appwrite/certificates:/storage/certificates:rw
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-databases:
image: appwrite/appwrite:1.9.0
entrypoint: worker-databases
container_name: appwrite-worker-databases
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-builds:
image: appwrite/appwrite:1.9.0
entrypoint: worker-builds
container_name: appwrite-worker-builds
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- /home/jpmschweitzer/docker-data/appwrite/functions:/storage/functions:rw
- /home/jpmschweitzer/docker-data/appwrite/sites:/storage/sites:rw
- /home/jpmschweitzer/docker-data/appwrite/builds:/storage/builds:rw
- /home/jpmschweitzer/docker-data/appwrite/uploads:/storage/uploads:rw
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-certificates:
image: appwrite/appwrite:1.9.0
entrypoint: worker-certificates
container_name: appwrite-worker-certificates
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- /home/jpmschweitzer/docker-data/appwrite/config:/storage/config:rw
- /home/jpmschweitzer/docker-data/appwrite/certificates:/storage/certificates:rw
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-functions:
image: appwrite/appwrite:1.9.0
entrypoint: worker-functions
container_name: appwrite-worker-functions
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
openruntimes-executor:
condition: service_started
environment:
<<: *appwrite-env
appwrite-worker-mails:
image: appwrite/appwrite:1.9.0
entrypoint: worker-mails
container_name: appwrite-worker-mails
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-messaging:
image: appwrite/appwrite:1.9.0
entrypoint: worker-messaging
container_name: appwrite-worker-messaging
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- /home/jpmschweitzer/docker-data/appwrite/uploads:/storage/uploads:rw
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-migrations:
image: appwrite/appwrite:1.9.0
entrypoint: worker-migrations
container_name: appwrite-worker-migrations
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- /home/jpmschweitzer/docker-data/appwrite/imports:/storage/imports:rw
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
# ============================================
# Stats Workers
# ============================================
appwrite-task-stats-resources:
image: appwrite/appwrite:1.9.0
entrypoint: stats-resources
container_name: appwrite-task-stats-resources
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-stats-resources:
image: appwrite/appwrite:1.9.0
entrypoint: worker-stats-resources
container_name: appwrite-worker-stats-resources
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-worker-stats-usage:
image: appwrite/appwrite:1.9.0
entrypoint: worker-stats-usage
container_name: appwrite-worker-stats-usage
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
# ============================================
# Task Schedulers
# ============================================
appwrite-task-scheduler-functions:
image: appwrite/appwrite:1.9.0
entrypoint: schedule-functions
container_name: appwrite-task-scheduler-functions
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-task-scheduler-executions:
image: appwrite/appwrite:1.9.0
entrypoint: schedule-executions
container_name: appwrite-task-scheduler-executions
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
appwrite-task-scheduler-messages:
image: appwrite/appwrite:1.9.0
entrypoint: schedule-messages
container_name: appwrite-task-scheduler-messages
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
# ============================================
# Maintenance Task
# ============================================
appwrite-task-maintenance:
image: appwrite/appwrite:1.9.0
entrypoint: maintenance
container_name: appwrite-task-maintenance
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
appwrite-redis:
condition: service_healthy
appwrite-mariadb:
condition: service_healthy
environment:
<<: *appwrite-env
# ============================================
# Browser Service (SSR rendering)
# ============================================
appwrite-browser:
image: appwrite/browser:0.3.2
container_name: appwrite-browser
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
# ============================================
# OpenRuntimes - Serverless Function Executor
# Requires Docker socket for spawning runtime containers
# ============================================
openruntimes-executor:
image: openruntimes/executor:0.7.22
container_name: openruntimes-executor
hostname: exc1
<<: *x-logging
restart: unless-stopped
stop_signal: SIGINT
networks:
- appwrite
- runtimes
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /home/jpmschweitzer/docker-data/appwrite/builds:/storage/builds:rw
- /home/jpmschweitzer/docker-data/appwrite/functions:/storage/functions:rw
- /home/jpmschweitzer/docker-data/appwrite/sites:/storage/sites:rw
- /tmp:/tmp:rw
environment:
- OPR_EXECUTOR_INACTIVE_TRESHOLD=${_APP_COMPUTE_INACTIVE_THRESHOLD:-60}
- OPR_EXECUTOR_MAINTENANCE_INTERVAL=${_APP_COMPUTE_MAINTENANCE_INTERVAL:-60}
- OPR_EXECUTOR_NETWORK=runtimes
- OPR_EXECUTOR_ENV=production
- OPR_EXECUTOR_RUNTIMES=node-22.0,python-3.12,php-8.3,dart-3.5,bun-1.1,static-1.0
- OPR_EXECUTOR_SECRET=gbnq4JwfnXBm4J7h7kUNEWt+OhwV3agu
- OPR_EXECUTOR_RUNTIME_VERSIONS=v5
- OPR_EXECUTOR_LOGGING_CONFIG=
- OPR_EXECUTOR_STORAGE_DEVICE=local
# ============================================
# MariaDB - Internal Database (Appwrite core component)
# NOT exposed to host - Appwrite-only
# ============================================
appwrite-mariadb:
image: mariadb:10.11
container_name: appwrite-mariadb
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- /home/jpmschweitzer/docker-data/appwrite/mariadb:/var/lib/mysql:rw
environment:
MYSQL_ROOT_PASSWORD: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163
MYSQL_DATABASE: appwrite
MYSQL_USER: appwrite
MYSQL_PASSWORD: ML8QbYgD05+7ZDHuRfmSTgDBr5VusxLi87ifyO5RlJg=
MARIADB_AUTO_UPGRADE: '1'
TZ: Europe/Amsterdam
command: 'mysqld --innodb-flush-method=fsync'
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 10s
retries: 10
start_period: 30s
# ============================================
# Redis - Internal Cache/Queue (Appwrite core component)
# NOT exposed to host - lives and dies with the stack
# ============================================
appwrite-redis:
image: redis:7.4-alpine
container_name: appwrite-redis
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
command: >
redis-server
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--maxmemory-samples 5
volumes:
- /home/jpmschweitzer/docker-data/appwrite/redis:/data:rw
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
networks:
appwrite:
name: appwrite
runtimes:
name: runtimes
docker-dataplane:
external: true
name: docker-dataplane
# =============================================================================
# DEPLOYMENT INSTRUCTIONS
# =============================================================================
#
# Phase 1: Data Directories (DONE)
# Created at ~/docker-data/appwrite/{uploads,imports,cache,config,certificates,
# functions,sites,builds,mariadb,redis}
#
# Phase 2: Deploy Stack
# Deploy via Portainer as stack "appwrite"
# All environment variables are inline - no .env file needed
#
# Phase 3: Initial Setup
# 1. Access http://192.168.86.149:8093/console
# 2. Create admin account (first user becomes admin)
# 3. Create a project for development
#
# Phase 4: (Optional) NPM Proxy Configuration
# If external access is needed later:
# 1. Create proxy host: appwrite.schweitz.net -> 192.168.86.149:8093
# 2. Enable: Block Common Exploits, Websockets Support
# 3. SSL: Let's Encrypt, Force SSL, HTTP/2
#
# =============================================================================
# CREDENTIALS
# =============================================================================
# MariaDB:
# Host: appwrite-mariadb:3306 (internal only)
# Database: appwrite
# User: appwrite
# Password: ML8QbYgD05+7ZDHuRfmSTgDBr5VusxLi87ifyO5RlJg=
# Root Password: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163
#
# Redis:
# Host: appwrite-redis:6379 (internal only, no password)
#
# OpenSSL Key: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163
# Executor Secret: gbnq4JwfnXBm4J7h7kUNEWt+OhwV3agu
#
# =============================================================================
# ARCHITECTURE NOTES
# =============================================================================
# - MariaDB and Redis are internal to the stack (not exposed to host)
# - Traefik handles routing: /console -> console, /v1/realtime -> realtime, / -> API
# - OpenRuntimes executor needs Docker socket to spawn function containers
# - The 'runtimes' network is used for executor <-> function container communication
# - Watchtower is disabled (label) - Appwrite upgrades should be deliberate
# - All 20+ containers share the same appwrite/appwrite:1.9.0 image with different entrypoints
-214
View File
@@ -1,214 +0,0 @@
version: '3.8'
# Authentik Identity Provider (SSO)
# Purpose: Centralized authentication for all homelab services
# Ports: 9000 (web UI), 9444 (standalone proxy outpost)
# GPU: No
# Storage: SSD (configs), PostgreSQL shared (user data)
# Note: Using standalone outpost - embedded outpost has issues in 2024.8.4
services:
authentik-server:
image: ghcr.io/goauthentik/server:2025.10.3 # Pinned version (2024.10 has redirect loop issues)
container_name: authentik-server
restart: unless-stopped
command: server
environment:
# External URLs (CRITICAL for redirect loop prevention)
AUTHENTIK_HOST: https://auth.schweitz.net
AUTHENTIK_HOST_BROWSER: https://auth.schweitz.net
# Cookie settings (CRITICAL for SSO across subdomains)
AUTHENTIK_COOKIE_DOMAIN: .schweitz.net
AUTHENTIK_COOKIE_SAMESITE: lax
# SSL/TLS
AUTHENTIK_INSECURE: false
# PostgreSQL (shared)
AUTHENTIK_POSTGRESQL__HOST: postgres-shared
AUTHENTIK_POSTGRESQL__PORT: 5432
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__USER: authentik_user
AUTHENTIK_POSTGRESQL__PASSWORD: F//j0ktck7cX06Vfgh0YXceONOtlSsHvadqROICeDx8=
AUTHENTIK_POSTGRESQL__CONN_MAX_AGE: 0
AUTHENTIK_POSTGRESQL__CONN_HEALTH_CHECKS: true
# Worker scaling (reduced to limit DB connections)
AUTHENTIK_WEB__WORKERS: 1
AUTHENTIK_WEB__THREADS: 2
# Secret key (generated: openssl rand -base64 32)
AUTHENTIK_SECRET_KEY: TnFaTZ//RDcO2hxVR4QGOBORd5tfXe4Vok+lcAz/AdE=
# Resource optimization
AUTHENTIK_LOG_LEVEL: warning
AUTHENTIK_ERROR_REPORTING__ENABLED: false
AUTHENTIK_AVATARS: none
AUTHENTIK_FOOTER_LINKS: '[]'
# Embedded outpost configuration
AUTHENTIK_OUTPOSTS__DOCKER_IMAGE_BASE: "ghcr.io/goauthentik/%(type)s:%(version)s"
# Timezone
TZ: Europe/Amsterdam
ports:
- "9000:9000" # Web UI + Embedded outpost (path: /outpost.goauthentik.io/*)
volumes:
- /home/jpmschweitzer/docker-data/authentik/media:/media
- /home/jpmschweitzer/docker-data/authentik/custom-templates:/templates
networks:
- docker-dataplane
healthcheck:
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:9000/-/health/live/')\" || exit 1"]
start_period: 60s
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
authentik-worker:
image: ghcr.io/goauthentik/server:2025.10.3 # Same version as server
container_name: authentik-worker
restart: unless-stopped
command: worker
environment:
# Same environment as server (MUST match exactly)
AUTHENTIK_HOST: https://auth.schweitz.net
AUTHENTIK_HOST_BROWSER: https://auth.schweitz.net
AUTHENTIK_COOKIE_DOMAIN: .schweitz.net
AUTHENTIK_COOKIE_SAMESITE: lax
AUTHENTIK_INSECURE: false
AUTHENTIK_POSTGRESQL__HOST: postgres-shared
AUTHENTIK_POSTGRESQL__PORT: 5432
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__USER: authentik_user
AUTHENTIK_POSTGRESQL__PASSWORD: F//j0ktck7cX06Vfgh0YXceONOtlSsHvadqROICeDx8=
AUTHENTIK_POSTGRESQL__CONN_MAX_AGE: 0
AUTHENTIK_POSTGRESQL__CONN_HEALTH_CHECKS: true
AUTHENTIK_SECRET_KEY: TnFaTZ//RDcO2hxVR4QGOBORd5tfXe4Vok+lcAz/AdE=
AUTHENTIK_LOG_LEVEL: warning
AUTHENTIK_ERROR_REPORTING__ENABLED: false
TZ: Europe/Amsterdam
# Worker-specific configuration
AUTHENTIK_WORKER__CONCURRENCY: 1
AUTHENTIK_WORKER__THREADS: 2
volumes:
- /home/jpmschweitzer/docker-data/authentik/media:/media
- /home/jpmschweitzer/docker-data/authentik/custom-templates:/templates
- /home/jpmschweitzer/docker-data/authentik/certs:/certs
- /var/run/docker.sock:/var/run/docker.sock # For outpost management
networks:
- docker-dataplane
depends_on:
- authentik-server
healthcheck:
test: ["CMD-SHELL", "ak healthcheck || exit 1"]
start_period: 60s
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
memory: 1G
cpus: '0.3'
reservations:
memory: 128M
authentik-proxy:
image: ghcr.io/goauthentik/proxy:2025.10.3 # Standalone outpost (embedded outpost not working in 2024.8.4)
container_name: authentik-proxy
restart: unless-stopped
environment:
# Authentik server connection
AUTHENTIK_HOST: https://auth.schweitz.net
AUTHENTIK_INSECURE: false
AUTHENTIK_TOKEN: 9blMGz71CFMJszs7AedQefgydpTnwvybjmMn0AlYilIKBV5LIq7snqnCodwX
# Logging
AUTHENTIK_LOG_LEVEL: info
# Timezone
TZ: Europe/Amsterdam
ports:
- "9444:9443" # Proxy outpost endpoint (9443 used by Portainer)
networks:
- docker-dataplane
depends_on:
- authentik-server
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:9300/outpost.goauthentik.io/ping || exit 1"]
start_period: 30s
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
memory: 256M
cpus: '0.2'
reservations:
memory: 128M
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Setup Instructions:
#
# 1. Create directories:
# mkdir -p ~/docker-data/authentik/{media,custom-templates,certs}
#
# 2. Deploy stack:
# docker-compose -f stacks/authentik.yml up -d
#
# 3. Watch logs:
# docker logs -f authentik-server
# docker logs -f authentik-worker
#
# 4. Wait for migrations to complete (~2-3 minutes):
# docker logs authentik-server 2>&1 | grep "Applying migration"
#
# 5. Access web UI:
# https://auth.schweitz.net (should show setup wizard)
#
# 6. Complete setup wizard:
# - Email: admin@schweitz.net
# - Password: <secure-password>
# - Finish setup
#
# Monitoring:
#
# Memory usage:
# docker stats authentik-server authentik-worker --no-stream
#
# Database connectivity:
# docker exec authentik-server ak check
#
# Outpost status (embedded outpost on port 9000):
# curl http://authentik-server:9000/outpost.goauthentik.io/ping
# curl http://192.168.86.149:9000/outpost.goauthentik.io/auth/nginx (should return 401, not 404)
-101
View File
@@ -1,101 +0,0 @@
version: '3.8'
# Core API - OpenAPI-compatible functions and AI orchestration for Open WebUI
# Purpose: Provides OpenAI-compatible API (/v1/chat/completions) and tool functions (web scraping)
# Port: 8083 (HTTP API)
# Network: docker-dataplane (shared infrastructure network)
#
# Container image built from: git.schweitz.internal/jpmschweitzer/core-api (internal registry)
services:
core-api:
image: git.schweitz.internal/jpmschweitzer/core-api:latest
container_name: core-api
restart: unless-stopped
ports:
- "8083:8083"
environment:
# Application
- APP_NAME=Core API
- DEBUG=false
- OIDC_ENABLED=true
# Server
- HOST=0.0.0.0
- PORT=8083
# Logging
- LOG_LEVEL=INFO
# Portainer API
- PORTAINER_URL=http://192.168.86.149:8001
- PORTAINER_API_KEY=${PORTAINER_API_KEY}
# Nginx Proxy Manager API
- NPM_URL=http://192.168.86.149:81
- NPM_EMAIL=${NPM_EMAIL}
- NPM_PASSWORD=${NPM_PASSWORD}
# Postgres Shared Database API
- POSTGRES_HOST=192.168.86.149:5432
- POSTGRES_USER=core_api
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
# Authentik Configuration
- AUTHENTIK_URL=https://auth.schweitz.net
- AUTHENTIK_USERNAME=${AUTHENTIK_USERNAME}
- AUTHENTIK_PASSWORD=${AUTHENTIK_PASSWORD}
- AUTHENTIK_CORE_API_TOKEN=${AUTHENTIK_CORE_API_TOKEN}
# HOME ASSITANT VARIABLES
- HOMEASSISTANT_URL=http://192.168.86.149:8123
- HOMEASSISTANT_TOKEN=${HOMEASSISTANT_TOKEN}
# AI SYSTEM VARIABLES
- SEARXNG_URL=http://192.168.86.149:8080
- QDRANT_HOST=192.168.86.149
- QDRANT_PORT=6333
# Python path
- PYTHONPATH=/app
- PSUTIL_PROCFS_PATH=/host/proc
- HOSTFS_ROOT=/hostfs
volumes:
# Data volumes only - no source code
- /home/jpmschweitzer/docker-data/core-api/logs:/app/logs
# Docker socket for direct container access (fallback when Portainer API incomplete)
- /var/run/docker.sock:/var/run/docker.sock:ro
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/hostfs:ro
deploy:
resources:
limits:
cpus: '2.0'
memory: 6G
reservations:
memory: 1G
devices:
- driver: nvidia
count: all
capabilities: [gpu]
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8083/health"]
interval: 30s
timeout: 20s
retries: 3
start_period: 60s
networks:
docker-dataplane:
external: true
name: docker-dataplane
-92
View File
@@ -1,92 +0,0 @@
version: '3.8'
# Gitea - Self-Hosted Git Service
# Application Layer
# Ports: 3002 (HTTP), 2222 (SSH)
# GPU: No
# Storage: SSD (repositories)
# Database: postgres-shared (gitea database, gitea_user)
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
restart: unless-stopped
ports:
- "3002:3000" # HTTP web interface
- "2222:22" # SSH git access (mapped to avoid host SSH conflict)
volumes:
# Git repositories and app config on SSD
- /home/jpmschweitzer/docker-data/gitea/data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=postgres-shared:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea_user
- GITEA__database__PASSWD=cCav64d76NX1zdEEAbVOM9uvao14aY8HojjNdxsSpMM=
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://git.schweitz.internal/api/healthz || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- docker-dataplane
gitea-runner:
image: gitea/act_runner:latest
container_name: gitea-runner
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /home/jpmschweitzer/docker-data/gitea/runner:/data
environment:
- CONFIG_FILE=/data/config.yaml
- GITEA_INSTANCE_URL=http://gitea:3000
- GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_TOKEN}
- GITEA_RUNNER_NAME=docker-runner
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "pgrep -x act_runner || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
depends_on:
- gitea
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Database: Uses postgres-shared stack (gitea database, gitea_user)
# Ensure postgres-shared is running before deploying this stack.
#
# Access: http://localhost:3002 or https://git.schweitz.net (via NPM)
#
# SSH Git Clone Usage:
# git clone ssh://git@localhost:2222/username/repo.git
#
# Nginx Proxy Manager Setup (for external access):
# 1. Add proxy host: git.schweitz.net → http://gitea:3000
# 2. Enable SSL with Let's Encrypt
# 3. Update GITEA__server__ROOT_URL in environment to https://git.schweitz.net
#
# Features:
# - Git repository hosting
# - Organizations and teams
# - Issue tracking
# - Pull requests and code review
# - Wiki and project documentation
# - CI/CD integration (Gitea Actions)
# - Webhooks for automation
# - Migration from GitHub/GitLab
# - Lightweight and fast
-63
View File
@@ -1,63 +0,0 @@
version: '3.8'
# Headscale - Self-Hosted Tailscale Control Server
# Phase 2: Networking & External Access
# Ports: 8085 (Web/API), 9090 (Metrics)
# GPU: No
# Storage: SSD (config and database)
services:
headscale:
image: headscale/headscale:latest
container_name: headscale
restart: unless-stopped
ports:
- "8085:8080" # Web/API port
- "9090:9090" # Metrics port (optional)
volumes:
- /home/jpmschweitzer/docker-data/headscale/config:/etc/headscale
- /home/jpmschweitzer/docker-data/headscale/data:/var/lib/headscale
command: serve
environment:
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Setup Instructions:
# 1. Create directories:
# mkdir -p ~/docker-data/headscale/{config,data}
#
# 2. Generate config:
# docker exec headscale headscale config generate > ~/docker-data/headscale/config/config.yaml
#
# 3. Edit config (important settings):
# - server_url: http://tower-of-joy:8085 (or your IP)
# - db_type: sqlite3
# - db_path: /var/lib/headscale/db.sqlite
#
# 4. Restart container: docker restart headscale
#
# 5. Create user: docker exec headscale headscale users create homelab
#
# 6. Generate pre-auth key:
# docker exec headscale headscale preauthkeys create --user homelab --expiration 24h
#
# 7. Connect devices:
# - Install Tailscale client on devices
# - Run: tailscale up --login-server=http://tower-of-joy:8085 --authkey=<key>
#
# Verify:
# - Health check: curl http://localhost:8085/health
# - List users: docker exec headscale headscale users list
# - List nodes: docker exec headscale headscale nodes list
-114
View File
@@ -1,114 +0,0 @@
version: '3.8'
# Home Assistant - Smart Home Automation Platform
# Ports: 8123 (Home Assistant), 5580 (Matter Server WebSocket)
# 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
network_mode: host
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
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
matter-server:
image: ghcr.io/matter-js/python-matter-server:stable
container_name: matter-server
restart: unless-stopped
network_mode: host
security_opt:
- apparmor:unconfined
volumes:
# Matter fabric data on SSD (BACK UP before upgrades - contains device credentials)
- /home/jpmschweitzer/docker-data/matter-server/data:/data
# D-Bus socket for Bluetooth commissioning
- /run/dbus:/run/dbus:ro
environment:
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:5580 || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
deploy:
resources:
limits:
memory: 512M
# Note: Both services use network_mode: host for LAN device discovery (mDNS, SSDP, Matter)
# Home Assistant reaches Matter Server at ws://localhost:5580/ws
# Other containers should reach HA via http://192.168.86.149:8123
# 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: 192.168.86.149
# 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
#
# Matter Server Setup:
# 1. Create data directory:
# mkdir -p /home/jpmschweitzer/docker-data/matter-server/data
#
# 2. After deploying, connect from Home Assistant:
# Settings > Devices & Services > Add Integration > Matter
# Deselect "Install the official Matter Server app"
# Enter URL: ws://localhost:5580/ws
#
# 3. Commission devices via the HA Companion app (uses phone Bluetooth)
# or add --bluetooth-adapter 0 to the matter-server command for local BT
#
# Source: https://github.com/matter-js/python-matter-server
-84
View File
@@ -1,84 +0,0 @@
version: '3.8'
# Library - Front Desk API (Coordination Service)
# Application Layer
# Port: 8089 (HTTP)
# GPU: No
# Source: git.schweitz.internal/jpmschweitzer/library-desk (internal registry)
services:
library-desk:
image: git.schweitz.internal/jpmschweitzer/library-desk:latest
container_name: library-desk
restart: unless-stopped
ports:
- "8089:8089" # FastAPI HTTP
environment:
# API Configuration
- LIBRARY_API_KEY=${LIBRARY_API_KEY}
# Neo4j Configuration
- NEO4J_URI=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
# Qdrant Configuration
- QDRANT_HOST=qdrant
- QDRANT_PORT=6333
# Wiki.js Configuration
- WIKIJS_URL=http://wiki:3000
- WIKIJS_USERNAME=librarian@schweitz.net
- WIKIJS_PASSWORD=${WIKIJS_PASSWORD}
- WIKIJS_DB_PASSWORD=${WIKIJS_DB_PASSWORD}
# SearXNG Configuration
- SEARXNG_URL=http://searxng:8080
# Paperless Configuration
- PAPERLESS_URL=http://paperless:8000
- PAPERLESS_TOKEN=${PAPERLESS_TOKEN}
# Ollama Configuration (for embeddings)
- OLLAMA_URL=http://ollama:11434
- OLLAMA_MODEL=nomic-embed-text
# Redis Configuration
- REDIS_HOST=redis-shared
- REDIS_PORT=6379
- REDIS_DB=4
# Central Settings Database
- SYSTEM_SETTINGS_HOST=postgres-shared
- SYSTEM_SETTINGS_PORT=5432
- SYSTEM_SETTINGS_DB=system_settings
- SYSTEM_SETTINGS_USER=settings
- SYSTEM_SETTINGS_PASSWORD=${SYSTEM_SETTINGS_PASSWORD}
# Scheduler Service
- SCHEDULER_URL=http://scheduler:8090
# Python Configuration
- PYTHONUNBUFFERED=1
- TZ=${TZ:-Europe/Amsterdam}
labels:
- "com.centurylinklabs.watchtower.enable=true"
networks:
- docker-dataplane
deploy:
resources:
reservations:
memory: 256M
limits:
memory: 768M
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8089/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
docker-dataplane:
external: true
name: docker-dataplane
-275
View File
@@ -1,275 +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
deploy:
resources:
limits:
memory: 16G
reservations:
memory: 8G
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
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
-199
View File
@@ -1,199 +0,0 @@
version: '3.8'
# Models Stack - GPU-Accelerated ML/AI Model Services
# Purpose: All GPU model serving (LLM inference, audio generation, 3D generation)
# Ports: 11434 (Ollama API), 11500 (Stable Audio UI), 11510 (TRELLIS UI)
# GPU: YES - Shared RTX 2080 Ti (11GB VRAM)
# Network: docker-dataplane
services:
# ============================================
# Ollama - LLM Inference Server
# API: http://localhost:11434
# ============================================
ollama:
image: ollama/ollama:latest
container_name: ollama
restart: unless-stopped
ports:
- "11434:11434"
volumes:
# Model storage on SSD for fast load times
- /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
# Unload LLMs after 5 minutes idle (keeps VRAM free for other services)
- OLLAMA_KEEP_ALIVE=5m
# Only keep one model loaded at a time (embedding model stays, LLMs swap)
- OLLAMA_MAX_LOADED_MODELS=1
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
# ============================================
# Stable Audio Open - AI Audio Generation
# Web UI: http://localhost:11500
# Image: locally built
# ============================================
stable-audio:
image: stable-audio-open:local
container_name: stable-audio
restart: unless-stopped
ports:
- "11500:8000"
volumes:
# HuggingFace cache for model weights (~6GB)
- /home/jpmschweitzer/docker-data/stable-audio/hf-cache:/root/.cache/huggingface
environment:
- TZ=Europe/Amsterdam
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
- HF_TOKEN=${HF_TOKEN}
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:8000/ || exit 1"]
interval: 60s
timeout: 30s
retries: 3
start_period: 300s
deploy:
resources:
limits:
memory: 16G
reservations:
memory: 8G
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
labels:
- "com.centurylinklabs.watchtower.enable=false"
networks:
- docker-dataplane
# ============================================
# TRELLIS - 3D Model Generation (Low-VRAM Fork)
# Web UI: http://localhost:11510
# Image: locally built
# ============================================
trellis:
image: trellis:local
container_name: trellis
restart: unless-stopped
ports:
- "11510:7860"
volumes:
# SSD: HuggingFace model cache (~5GB)
- /home/jpmschweitzer/docker-data/trellis/hf-cache:/root/.cache/huggingface
# HDD: Generated GLB files
- /mnt/media/trellis/outputs:/app/outputs
# Entrypoint script (patches Gradio bugs without rebuilding image)
- /home/jpmschweitzer/docker-data/trellis/entrypoint.sh:/app/entrypoint.sh:ro
command: ["bash", "/app/entrypoint.sh"]
environment:
- TZ=Europe/Amsterdam
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
# VRAM Optimization (for 11GB card)
- ATTN_BACKEND=xformers
- SPCONV_ALGO=native
- PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
# Gradio must bind to 0.0.0.0 inside Docker
- GRADIO_SERVER_NAME=0.0.0.0
# HuggingFace token for model downloads (set in Portainer)
- HF_TOKEN=${HF_TOKEN:-}
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:7860/ || exit 1"]
interval: 60s
timeout: 30s
retries: 3
start_period: 300s
memswap_limit: 20G
deploy:
resources:
limits:
memory: 20G
reservations:
memory: 4G
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
labels:
- "com.centurylinklabs.watchtower.enable=false"
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# =============================================================================
# GPU CONTENTION
# =============================================================================
#
# All three services share the RTX 2080 Ti (11GB VRAM).
# Ollama unloads models after 5 min idle to free VRAM.
# For heavy Stable Audio or TRELLIS generations, consider stopping Ollama:
# docker stop ollama && <generate> && docker start ollama
#
# =============================================================================
# OLLAMA
# =============================================================================
#
# 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"
#
# 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
#
# =============================================================================
# STABLE AUDIO
# =============================================================================
#
# Prerequisites:
# 1. Build image: cd ~/docker-data/stable-audio-open && docker build -t stable-audio-open:local .
# 2. Create HuggingFace token: https://huggingface.co/settings/tokens (read access)
# 3. Accept model license: https://huggingface.co/stabilityai/stable-audio-open-1.0
# 4. mkdir -p ~/docker-data/stable-audio/hf-cache
# 5. Set HF_TOKEN in Portainer environment variables
#
# =============================================================================
# TRELLIS
# =============================================================================
#
# Prerequisites:
# 1. Build image: cd ~/docker-data/trellis && docker build -t trellis:local . (~20-30 min)
# 2. mkdir -p ~/docker-data/trellis/hf-cache /mnt/media/trellis/outputs
#
# Game Asset Pipeline:
# 1. Open Gradio UI at http://tower-of-joy:11510
# 2. Upload reference image or enter text prompt
# 3. Generate 3D model (uses 6-8GB VRAM)
# 4. Download GLB file from outputs
# 5. Import into Blender, apply textures, render sprites
-77
View File
@@ -1,77 +0,0 @@
version: '3.8'
# Library - Neo4j Knowledge Graph
# Storage Layer
# Ports: 7474 (Browser), 7687 (Bolt)
# GPU: No
# Storage: SSD (graph database)
services:
neo4j:
image: neo4j:5-community
container_name: neo4j
restart: unless-stopped
ports:
- "7474:7474" # Neo4j Browser (web UI)
- "7687:7687" # Bolt protocol (API)
volumes:
# Graph database on SSD for performance
- /home/jpmschweitzer/docker-data/library-neo4j/data:/data
- /home/jpmschweitzer/docker-data/library-neo4j/logs:/logs
- /home/jpmschweitzer/docker-data/library-neo4j/plugins:/plugins
environment:
- NEO4J_AUTH=neo4j/${NEO4J_PASSWORD}
- NEO4J_PLUGINS=["apoc"]
- NEO4J_dbms_memory_heap_initial__size=512m
- NEO4J_dbms_memory_heap_max__size=2g
- NEO4J_dbms_memory_pagecache_size=512m
- NEO4J_apoc_export_file_enabled=true
- NEO4J_apoc_import_file_enabled=true
- NEO4J_apoc_import_file_use__neo4j__config=true
- TZ=${TZ:-Europe/Amsterdam}
networks:
- docker-dataplane
deploy:
resources:
reservations:
memory: 1G
limits:
memory: 4G
healthcheck:
test: ["CMD", "cypher-shell", "-u", "neo4j", "-p", "${NEO4J_PASSWORD}", "RETURN 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
docker-dataplane:
external: true
name: docker-dataplane
# ⚠️ SECURITY WARNING:
# Set NEO4J_PASSWORD in environment variables before deploying!
# Use a strong, unique password.
#
# After Deployment:
# 1. Access Neo4j Browser: http://192.168.86.149:7474
# 2. Login: neo4j / <NEO4J_PASSWORD>
# 3. Run schema initialization (see DEPLOYMENT.md Phase 4.1)
# 4. Install APOC plugin (should auto-install from NEO4J_PLUGINS setting)
#
# Features:
# - Knowledge graph for entities, relationships, versions
# - APOC procedures for advanced graph operations
# - Cypher query language for graph traversal
# - Mind map generation for Wiki.js
# - Version tracking for documentation
# - Compatibility relationships between projects
#
# Memory Configuration:
# - Heap: 512MB initial → 2GB max
# - Page cache: 512MB
# - Reserved: 1GB, Limit: 4GB
#
# Backups:
# - Managed by Scheduler (weekly, Sunday 03:00)
# - Location: /mnt/media/backups/library/neo4j/
-111
View File
@@ -1,111 +0,0 @@
services:
nextcloud:
image: nextcloud:stable
container_name: nextcloud
restart: unless-stopped
ports:
- "8082:80"
volumes:
# Fresh config directory
- /home/jpmschweitzer/docker-data/nextcloud/config:/var/www/html/config
# Fresh user data directory
- /mnt/media/nextcloud/data:/var/www/html/data
environment:
# PostgreSQL configuration
- POSTGRES_HOST=postgres-shared
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud_user
- POSTGRES_PASSWORD=${NEXTCLOUD_DB_PASSWORD}
# Redis configuration (Database 7)
- REDIS_HOST=redis-shared
- REDIS_HOST_PORT=6379
- REDIS_DB_INDEX=7
# Timezone
- TZ=Europe/Amsterdam
networks:
- docker-dataplane
deploy:
resources:
limits:
memory: 1G
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Nextcloud - Personal Cloud Storage (Using Shared Infrastructure)
# Port: 8082
# GPU: No
# Dependencies: postgres-shared, redis-shared
#
# Prerequisites:
#
# 1. Shared infrastructure must be running:
# docker ps | grep -E 'postgres-shared|redis-shared'
#
# 2. Database and user already created in postgres-shared:
# - Database: nextcloud
# - User: nextcloud_user
# - Redis DB: 7
#
# 3. Create .env file with:
# NEXTCLOUD_DB_PASSWORD=<password from shared infrastructure setup>
#
# 4. Deploy this stack:
# cd /mnt/media/Projects/portainer-core/stacks
# docker compose -f nextcloud-shared.yml --env-file .env.nextcloud-shared up -d
#
# After Deployment:
#
# 1. Wait for initialization (2-3 minutes)
#
# 2. Access web interface: http://localhost:8082 or https://cloud.schweitz.net
#
# 3. First-time setup wizard:
# - Admin username: admin
# - Admin password: <STRONG_PASSWORD>
# - Data folder: /var/www/html/data (default)
# - Database: PostgreSQL
# - Database user: nextcloud_user
# - Database password: <FROM_ENV_FILE>
# - Database name: nextcloud
# - Database host: postgres-shared
#
# 4. Configure trusted domains:
# docker exec -u www-data nextcloud php occ config:system:set trusted_domains 1 --value=cloud.schweitz.net
# docker exec -u www-data nextcloud php occ config:system:set trusted_domains 2 --value=192.168.86.149
#
# 5. Configure Redis caching:
# docker exec -u www-data nextcloud php occ config:system:set redis host --value=redis-shared
# docker exec -u www-data nextcloud php occ config:system:set redis port --value=6379
# docker exec -u www-data nextcloud php occ config:system:set redis dbindex --value=7
# docker exec -u www-data nextcloud php occ config:system:set memcache.local --value='\\OC\\Memcache\\APCu'
# docker exec -u www-data nextcloud php occ config:system:set memcache.distributed --value='\\OC\\Memcache\\Redis'
# docker exec -u www-data nextcloud php occ config:system:set memcache.locking --value='\\OC\\Memcache\\Redis'
#
# 6. Optimize database:
# docker exec -u www-data nextcloud php occ db:add-missing-indices
# docker exec -u www-data nextcloud php occ db:convert-filecache-bigint
#
# 7. Configure background jobs:
# docker exec -u www-data nextcloud php occ background:cron
#
# Connection Details:
#
# Database:
# - Host: postgres-shared (from containers) / localhost (from host)
# - Port: 5432
# - Database: nextcloud
# - User: nextcloud_user
#
# Cache:
# - Host: redis-shared (from containers) / localhost (from host)
# - Port: 6379
# - Database: 7
#
# Resource Usage:
# - Nextcloud: 1GB RAM limit
# - Savings: ~110-120 MB RAM + 2 fewer containers (MariaDB + Redis removed)
-42
View File
@@ -1,42 +0,0 @@
version: '3.8'
# Nginx Proxy Manager - Reverse Proxy & Unified Web Interface
# Phase 1: Foundation Setup
# Ports: 8000 (Admin UI), 80 (HTTP), 443 (HTTPS)
# GPU: No
# Storage: SSD (configs and SSL certificates)
services:
nginx-proxy-manager:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "8000:81" # Admin web interface (unified entry point)
- "80:80" # HTTP reverse proxy traffic
- "443:443" # HTTPS reverse proxy traffic
volumes:
# SSD storage for configs and certificates (performance-critical)
- /home/jpmschweitzer/docker-data/nginx-proxy-manager/data:/data
- /home/jpmschweitzer/docker-data/nginx-proxy-manager/letsencrypt:/etc/letsencrypt
environment:
- DB_SQLITE_FILE=/data/database.sqlite
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:81/api/ || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# Setup Instructions:
# 1. Deploy this stack
# 2. Access http://localhost:8000
# 3. Default login: admin@example.com / changeme
# 4. IMPORTANT: Change admin credentials immediately!
# 5. Add proxy hosts for your services (Portainer, Jellyfin, etc.)
#
# Example Proxy Host Configuration:
# - Domain: portainer.tower-of-joy.local
# - Forward to: portainer:9000
# - Enable SSL with Let's Encrypt (optional)
-71
View File
@@ -1,71 +0,0 @@
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
ports:
- "82:8080"
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
environment:
# Ollama connection (direct - fallback)
- OLLAMA_BASE_URL=http://192.168.86.149:11434
# Tatlock AI Orchestrator (OpenAI-compatible endpoint)
- OPENAI_API_BASE_URLS=http://core-api:8083/v1
- OPENAI_API_KEYS=dummy
# Default model
- DEFAULT_MODELS=gemma3:12b
# Enable features
- ENABLE_RAG_WEB_SEARCH=true
- ENABLE_OLLAMA_API=true
- WEBUI_AUTH=true
# Web search configuration
- RAG_WEB_SEARCH_ENGINE=duckduckgo
# RAG & Vector Database - Qdrant for conversation memory
- VECTOR_DB=qdrant
- QDRANT_URI=http://qdrant:6333
- RAG_EMBEDDING_ENGINE=ollama
- RAG_EMBEDDING_MODEL=nomic-embed-text
- RAG_EMBEDDING_MODEL_AUTO_UPDATE=true
# Enable native memory feature
- ENABLE_MEMORY=true
- MEMORY_COLLECTION_NAME=open-webui_memories
# Session settings
- WEBUI_SESSION_COOKIE_SAME_SITE=lax
- WEBUI_SESSION_COOKIE_SECURE=false
volumes:
- /home/jpmschweitzer/docker-data/open-webui:/app/backend/data
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
memory: 512M
networks:
- docker-dataplane
labels:
- "com.centurylinklabs.watchtower.enable=true"
networks:
docker-dataplane:
external: true
name: docker-dataplane
-110
View File
@@ -1,110 +0,0 @@
version: '3.8'
# Paperless-ngx - Document Management System
# Port: 8091 (HTTP)
# GPU: No
# External: documents.schweitz.net
# Storage: Configs on SSD (backed up), documents on HDD
services:
paperless:
image: ghcr.io/paperless-ngx/paperless-ngx:latest
container_name: paperless
restart: unless-stopped
ports:
- "8091:8000"
volumes:
# SSD - configs and database (backed up)
- /home/jpmschweitzer/docker-data/paperless/data:/usr/src/paperless/data
# HDD - document storage
- /mnt/media/paperless/media:/usr/src/paperless/media
- /mnt/media/paperless/consume:/usr/src/paperless/consume
- /mnt/media/paperless/export:/usr/src/paperless/export
environment:
# Database (shared PostgreSQL)
PAPERLESS_DBENGINE: postgresql
PAPERLESS_DBHOST: postgres-shared
PAPERLESS_DBPORT: 5432
PAPERLESS_DBNAME: paperless
PAPERLESS_DBUSER: paperless_user
PAPERLESS_DBPASS: ${PAPERLESS_DB_PASSWORD}
# Redis (shared, DB 8)
PAPERLESS_REDIS: redis://redis-shared:6379/8
# Security
PAPERLESS_SECRET_KEY: ${PAPERLESS_SECRET_KEY}
# URLs
PAPERLESS_URL: https://documents.schweitz.net
PAPERLESS_ALLOWED_HOSTS: "*"
PAPERLESS_CORS_ALLOWED_HOSTS: "http://localhost:8091,https://documents.schweitz.net"
# OCR Settings
PAPERLESS_OCR_LANGUAGE: eng
PAPERLESS_OCR_LANGUAGES: nld # Install Dutch on first startup
PAPERLESS_OCR_MODE: skip
PAPERLESS_OCR_OUTPUT_TYPE: pdfa
# Webhooks (for Library Desk integration)
PAPERLESS_WEBHOOKS_ALLOW_INTERNAL_REQUESTS: "true"
# Admin user (created on first run)
PAPERLESS_ADMIN_USER: admin
PAPERLESS_ADMIN_PASSWORD: ${PAPERLESS_ADMIN_PASSWORD}
# Timezone
TZ: Europe/Amsterdam
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
deploy:
resources:
limits:
memory: 4G
reservations:
memory: 512M
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# =============================================================================
# DEPLOYMENT INSTRUCTIONS
# =============================================================================
#
# 1. Environment variables required in Portainer:
# PAPERLESS_DB_PASSWORD=<openssl rand -hex 32>
# PAPERLESS_SECRET_KEY=<openssl rand -base64 32>
# PAPERLESS_ADMIN_PASSWORD=<your-admin-password>
#
# 2. Database already created in postgres-shared:
# Database: paperless
# User: paperless_user
#
# 3. Redis using shared instance DB 8
#
# 4. After deployment, configure NPM:
# Domain: documents.schweitz.net
# Forward: paperless:8000 or 192.168.86.149:8091
# SSL: Let's Encrypt
# Websockets: Enable
#
# 5. Post-deployment in Paperless UI:
# - Create API token (My Profile → API Token)
# - Create custom fields: source_url, library_indexed, library_doc_id, collection
# - Create webhook workflow to http://library-desk:8089/documents/webhook
#
# 6. ClamAV is running on host at 192.168.86.149:3310
# Configure Library Desk with:
# CLAMAV_HOST=192.168.86.149
# CLAMAV_PORT=3310
# CLAMAV_ENABLED=true
-243
View File
@@ -1,243 +0,0 @@
version: '3.8'
# Penpot - Open Source Design Platform
# Purpose: Self-hosted Figma alternative for design and prototyping
# Port: 9001 (web UI)
# GPU: No
# External: penpot.schweitz.net
# Storage: SSD (assets), PostgreSQL shared, Redis shared (DB 10)
services:
# ============================================
# Penpot Frontend - Web UI (nginx)
# Port: 9001 (exposed)
# ============================================
penpot-frontend:
image: penpotapp/frontend:latest
container_name: penpot-frontend
restart: unless-stopped
ports:
- "9001:8080"
volumes:
- /home/jpmschweitzer/docker-data/penpot/assets:/opt/data/assets
environment:
# Feature flags
PENPOT_FLAGS: enable-login-with-oidc disable-login-with-password disable-registration enable-oidc-registration disable-email-verification
# Backend connection (internal DNS)
PENPOT_BACKEND_URI: http://penpot-backend:6060
PENPOT_EXPORTER_URI: http://penpot-exporter:6061
# Timezone
TZ: Europe/Amsterdam
networks:
- docker-dataplane
depends_on:
- penpot-backend
- penpot-exporter
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# ============================================
# Penpot Backend - API Server (Clojure)
# Internal only - connects to postgres-shared + redis-shared
# ============================================
penpot-backend:
image: penpotapp/backend:latest
container_name: penpot-backend
restart: unless-stopped
volumes:
- /home/jpmschweitzer/docker-data/penpot/assets:/opt/data/assets
environment:
# Public URI (for callbacks and links)
PENPOT_PUBLIC_URI: https://penpot.schweitz.net
# Feature flags (same as frontend)
PENPOT_FLAGS: enable-login-with-oidc disable-login-with-password disable-registration enable-oidc-registration disable-email-verification
# PostgreSQL (shared)
PENPOT_DATABASE_URI: postgresql://postgres-shared:5432/penpot
PENPOT_DATABASE_USERNAME: penpot_user
PENPOT_DATABASE_PASSWORD: YLmawuhcm0TLEMztkg/2Rzw+oAV6KPgHa3dZEYTkWr8=
# Redis (shared, DB 10)
PENPOT_REDIS_URI: redis://redis-shared:6379/10
# Assets storage (updated for Penpot 2.11+)
PENPOT_OBJECTS_STORAGE_BACKEND: fs
PENPOT_OBJECTS_STORAGE_FS_DIRECTORY: /opt/data/assets
# Secret key (512-bit base64)
PENPOT_SECRET_KEY: +g+fSbAzBWVA2gE8ZnJUcpgq9L1BCqtuB+h9w73iF9qcgxhjMF1QU23XUjQ5ZdxpPmwoyuuQEm/frtKUV2M6pA==
# Telemetry (disabled)
PENPOT_TELEMETRY_ENABLED: false
# ============================================
# Authentik OIDC Configuration
# ============================================
PENPOT_OIDC_CLIENT_ID: ${PENPOT_OIDC_CLIENT_ID}
PENPOT_OIDC_CLIENT_SECRET: ${PENPOT_OIDC_CLIENT_SECRET}
PENPOT_OIDC_BASE_URI: https://auth.schweitz.net/application/o/penpot/
PENPOT_OIDC_AUTH_URI: https://auth.schweitz.net/application/o/authorize/
PENPOT_OIDC_TOKEN_URI: https://auth.schweitz.net/application/o/token/
PENPOT_OIDC_USER_URI: https://auth.schweitz.net/application/o/userinfo/
PENPOT_OIDC_SCOPES: openid profile email
PENPOT_OIDC_NAME_ATTR: name
PENPOT_OIDC_EMAIL_ATTR: email
# SMTP Configuration (optional - for notifications)
# PENPOT_SMTP_DEFAULT_FROM: penpot@schweitz.net
# PENPOT_SMTP_DEFAULT_REPLY_TO: penpot@schweitz.net
# PENPOT_SMTP_HOST: smtp.example.com
# PENPOT_SMTP_PORT: 587
# PENPOT_SMTP_USERNAME: username
# PENPOT_SMTP_PASSWORD: password
# PENPOT_SMTP_TLS: true
# Timezone
TZ: Europe/Amsterdam
networks:
- docker-dataplane
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:6060/readyz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
deploy:
resources:
limits:
memory: 1G
reservations:
memory: 256M
# ============================================
# Penpot Exporter - PDF/SVG Export Worker
# Internal only
# ============================================
penpot-exporter:
image: penpotapp/exporter:latest
container_name: penpot-exporter
restart: unless-stopped
environment:
# Public URI (for rendering)
PENPOT_PUBLIC_URI: https://penpot.schweitz.net
# Redis (shared, DB 10)
PENPOT_REDIS_URI: redis://redis-shared:6379/10
# Secret key (must match backend)
PENPOT_SECRET_KEY: +g+fSbAzBWVA2gE8ZnJUcpgq9L1BCqtuB+h9w73iF9qcgxhjMF1QU23XUjQ5ZdxpPmwoyuuQEm/frtKUV2M6pA==
# Timezone
TZ: Europe/Amsterdam
networks:
- docker-dataplane
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:6061/readyz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
resources:
limits:
memory: 1G
reservations:
memory: 256M
networks:
docker-dataplane:
external: true
name: docker-dataplane
# =============================================================================
# DEPLOYMENT INSTRUCTIONS
# =============================================================================
#
# Phase 1: Database Setup (DONE)
# Database 'penpot' and user 'penpot_user' created in postgres-shared
#
# Phase 2: Authentik OIDC Configuration
# 1. Go to https://auth.schweitz.net/if/admin/#/core/providers
# 2. Create new OAuth2/OpenID Provider:
# - Name: Penpot
# - Authorization flow: default-provider-authorization-implicit-consent
# - Client type: Confidential
# - Client ID: (auto-generated, copy to Portainer env vars)
# - Client Secret: (auto-generated, copy to Portainer env vars)
# - Redirect URIs/Origins: https://penpot.schweitz.net/api/auth/oauth/oidc/callback
# - Scopes: openid profile email
# - Signing Key: authentik Self-signed Certificate
#
# 3. Create Application:
# - Name: Penpot
# - Slug: penpot
# - Provider: Penpot (created above)
# - Launch URL: https://penpot.schweitz.net
#
# 4. Add environment variables in Portainer:
# PENPOT_OIDC_CLIENT_ID=<from-authentik>
# PENPOT_OIDC_CLIENT_SECRET=<from-authentik>
#
# Phase 3: Deploy Stack
# Deploy via Portainer with the OIDC environment variables
#
# Phase 4: NPM Proxy Configuration
# 1. Create new proxy host in NPM (http://192.168.86.149:81)
# 2. Domain: penpot.schweitz.net
# 3. Forward Host: 192.168.86.149
# 4. Forward Port: 9001
# 5. Enable: Block Common Exploits, Websockets Support
# 6. SSL: Request new Let's Encrypt certificate, Force SSL, HTTP/2
#
# Phase 5: Verify Deployment
# 1. Access https://penpot.schweitz.net
# 2. Click "Login with OIDC" - should redirect to auth.schweitz.net
# 3. Login with Authentik credentials
# 4. Verify redirect back to Penpot with authenticated session
# 5. Create a test project to verify functionality
#
# =============================================================================
# CREDENTIALS (stored in Portainer env vars)
# =============================================================================
# PostgreSQL:
# Host: postgres-shared:5432
# Database: penpot
# User: penpot_user
# Password: YLmawuhcm0TLEMztkg/2Rzw+oAV6KPgHa3dZEYTkWr8=
#
# Redis:
# Host: redis-shared:6379
# Database: 10
#
# Secret Key: +g+fSbAzBWVA2gE8ZnJUcpgq9L1BCqtuB+h9w73iF9qcgxhjMF1QU23XUjQ5ZdxpPmwoyuuQEm/frtKUV2M6pA==
-31
View File
@@ -1,31 +0,0 @@
version: '3.8'
# Portainer - Container Management UI
# Phase 1: Foundation Setup
# Ports: 8080 (HTTP), 8443 (HTTPS)
# GPU: No
# Storage: Docker volume (portainer_data)
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: always
ports:
- "8080:9000" # Main Portainer web UI
- "8443:9443" # Portainer HTTPS access
volumes:
- /var/run/docker.sock:/var/run/docker.sock # Docker socket for container management
- portainer_data:/data # Persistent data storage
environment:
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:9000/api/system/status || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
volumes:
portainer_data:
name: portainer_data
-137
View File
@@ -1,137 +0,0 @@
version: '3.8'
# Shared PostgreSQL Database
# Purpose: Centralized database for all homelab applications
# Port: 5432
# GPU: No
# Storage: SSD (PostgreSQL data and backups)
services:
postgres-shared:
image: postgres:16-alpine
container_name: postgres-shared
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
start_period: 20s
interval: 30s
retries: 5
timeout: 5s
ports:
- "5432:5432"
volumes:
- /home/jpmschweitzer/docker-data/postgres-shared/data:/var/lib/postgresql/data
- /home/jpmschweitzer/docker-data/postgres-shared/backups:/backups
environment:
POSTGRES_PASSWORD: ${POSTGRES_ADMIN_PASSWORD:?admin password required}
TZ: Europe/Amsterdam
# Performance tuning (adjust based on available RAM)
# Shared buffers: 25% of RAM allocated to PostgreSQL
POSTGRES_SHARED_BUFFERS: 512MB
# Effective cache: 50-75% of RAM allocated to PostgreSQL
POSTGRES_EFFECTIVE_CACHE_SIZE: 2GB
# Max connections: adjust based on number of applications
POSTGRES_MAX_CONNECTIONS: 200
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
memory: 512M
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Setup Instructions:
#
# 1. Create directories:
# mkdir -p ~/docker-data/postgres-shared/{data,backups}
#
# 2. Deploy stack via core-api (recommended) or docker-compose
#
# 3. Initialize databases (run ONCE after first deployment):
# docker exec -i postgres-shared psql -U postgres <<'EOF'
# -- Authentik database
# CREATE DATABASE authentik;
# CREATE USER authentik_user WITH PASSWORD 'F//j0ktck7cX06Vfgh0YXceONOtlSsHvadqROICeDx8=';
# GRANT ALL PRIVILEGES ON DATABASE authentik TO authentik_user;
# \c authentik
# GRANT ALL ON SCHEMA public TO authentik_user;
# ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO authentik_user;
# ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authentik_user;
#
# -- Gitea database
# \c postgres
# CREATE DATABASE gitea;
# CREATE USER gitea_user WITH PASSWORD 'cCav64d76NX1zdEEAbVOM9uvao14aY8HojjNdxsSpMM=';
# GRANT ALL PRIVILEGES ON DATABASE gitea TO gitea_user;
# \c gitea
# GRANT ALL ON SCHEMA public TO gitea_user;
# ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO gitea_user;
# ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO gitea_user;
# EOF
#
# 4. Verify deployment:
# docker exec postgres-shared pg_isready
# docker exec postgres-shared psql -U postgres -c '\l'
#
# Database Connection Examples:
#
# From containers on docker-dataplane network:
# Host: postgres-shared
# Port: 5432
# Database: authentik (or gitea, etc.)
# User: authentik_user (or gitea_user, etc.)
# Password: <app-specific-password>
#
# From host machine:
# psql -h localhost -U authentik_user -d authentik
#
# Monitoring:
#
# Active connections per database:
# docker exec postgres-shared psql -U postgres -c \
# "SELECT datname, numbackends FROM pg_stat_database;"
#
# Database sizes:
# docker exec postgres-shared psql -U postgres -c \
# "SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database;"
#
# Backup:
#
# All databases:
# docker exec postgres-shared pg_dumpall -U postgres | \
# gzip > ~/docker-data/postgres-shared/backups/all-$(date +%Y%m%d).sql.gz
#
# Single database:
# docker exec postgres-shared pg_dump -U postgres authentik | \
# gzip > ~/docker-data/postgres-shared/backups/authentik-$(date +%Y%m%d).sql.gz
#
# Restore:
# gunzip < backup.sql.gz | docker exec -i postgres-shared psql -U postgres
#
# Maintenance:
#
# Vacuum analyze (optimize performance):
# docker exec postgres-shared psql -U postgres -c "VACUUM ANALYZE;"
#
# Reindex (if queries slow):
# docker exec postgres-shared psql -U postgres -d authentik -c "REINDEX DATABASE authentik;"
#
# Resource Usage (expected):
# CPU: ~0.5-1.5 cores (depends on query load)
# RAM: ~500MB-1.5GB (depends on active connections and cache)
# Storage: Grows with data (monitor with: df -h ~/docker-data/postgres-shared)
#
# Applications Using This Database:
# - Authentik (identity provider)
# - Gitea (git hosting) - migrated from dedicated instance
# - Nextcloud (personal cloud storage) - migrated from MariaDB
# - Paperless-ngx (document management) - DB: paperless, User: paperless_user
# - Future applications as needed
-69
View File
@@ -1,69 +0,0 @@
version: '3.8'
# Qdrant Vector Database
# Purpose: Efficient vector storage for Open WebUI RAG (conversation memory & documents)
# Ports: 6333 (HTTP API), 6334 (gRPC)
# GPU: NO - CPU-based vector operations are efficient
# Storage: SSD for vector data (performance-critical)
services:
qdrant:
image: qdrant/qdrant:latest
container_name: qdrant
restart: unless-stopped
ports:
- "6333:6333" # HTTP API
- "6334:6334" # gRPC API
volumes:
# Vector storage on SSD for performance
- /home/jpmschweitzer/docker-data/qdrant/storage:/qdrant/storage
# Snapshots for backups
- /home/jpmschweitzer/docker-data/qdrant/snapshots:/qdrant/snapshots
environment:
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:6333/readyz || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
resources:
limits:
cpus: '1.0'
memory: 768M
reservations:
memory: 256M
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Qdrant Performance Notes:
# - Optimized for high-dimensional vectors (embeddings)
# - Supports HNSW indexing for fast similarity search
# - Efficient memory usage (~1-2GB for thousands of documents)
# - No GPU required (CPU operations are fast enough)
#
# Storage Estimates:
# - ~1KB per conversation turn (with embedding)
# - 10,000 turns = ~10MB
# - Very efficient for conversation memory
#
# After Deployment:
# 1. Check logs: docker logs qdrant
# 2. Access UI: http://localhost:6333/dashboard
# 3. Verify API: curl http://localhost:6333/collections
#
# Integration with Open WebUI:
# - Set VECTOR_DB=qdrant in Open WebUI
# - Set QDRANT_URL=http://qdrant:6333
# - Open WebUI will automatically create collections
#
# Collections Created:
# - Documents: User-uploaded files for RAG
# - Conversations: Chat history for memory
# - Web search results: Cached search results
-159
View File
@@ -1,159 +0,0 @@
version: '3.8'
# Shared Redis Cache
# Purpose: Centralized cache and session store for all homelab applications
# Port: 6379
# GPU: No
# Storage: SSD (Redis persistence - AOF and RDB)
services:
redis-shared:
image: redis:alpine
container_name: redis-shared
restart: unless-stopped
command: >
redis-server
--appendonly yes
--appendfsync everysec
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--save 60 1000
--save 300 100
--save 900 1
--loglevel warning
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
ports:
- "6379:6379"
volumes:
- /home/jpmschweitzer/docker-data/redis-shared/data:/data
environment:
TZ: Europe/Amsterdam
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
memory: 128M
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Setup Instructions:
#
# 1. Create directories:
# mkdir -p ~/docker-data/redis-shared/data
#
# 2. Deploy stack:
# docker-compose -f redis-shared.yml up -d
#
# 3. Verify deployment:
# docker exec redis-shared redis-cli ping
#
# Database Allocation:
#
# Redis supports 16 databases (0-15). Assign one per application:
#
# DB 0: Authentik (sessions, cache, message queue)
# DB 1: Tatlock (memory)
# DB 2: Wiki.js
# DB 3: Scheduler
# DB 4: Library Desk
# DB 5: SearXNG
# DB 6: Tatlock (benchmarks)
# DB 7: Nextcloud (file locking, distributed cache, sessions)
# DB 8: Paperless (task queue, cache)
# DB 9-15: Reserved for future applications
#
# Connection Examples:
#
# From containers on docker-dataplane network:
# redis://redis-shared:6379/1 (Authentik, DB 1)
# redis://redis-shared:6379/2 (Gitea, DB 2)
#
# From host machine:
# redis-cli -h localhost
# SELECT 1 (switch to database 1)
#
# Monitoring:
#
# General info:
# docker exec redis-shared redis-cli INFO
#
# Memory usage:
# docker exec redis-shared redis-cli INFO memory
#
# Keyspace (keys per database):
# docker exec redis-shared redis-cli INFO keyspace
#
# Stats:
# docker exec redis-shared redis-cli INFO stats
#
# Per-database keys:
# docker exec redis-shared redis-cli -n 1 DBSIZE (database 1)
# docker exec redis-shared redis-cli -n 2 DBSIZE (database 2)
#
# Backup:
#
# Trigger background save:
# docker exec redis-shared redis-cli BGSAVE
#
# Copy RDB file:
# cp ~/docker-data/redis-shared/data/dump.rdb \
# ~/backups/redis-$(date +%Y%m%d).rdb
#
# Backup AOF (append-only file):
# cp ~/docker-data/redis-shared/data/appendonly.aof \
# ~/backups/redis-aof-$(date +%Y%m%d).aof
#
# Restore:
# docker stop redis-shared
# cp backup-dump.rdb ~/docker-data/redis-shared/data/dump.rdb
# docker start redis-shared
#
# Maintenance:
#
# Clear specific database (DANGER - data loss!):
# docker exec redis-shared redis-cli -n 1 FLUSHDB
#
# Clear all databases (DANGER - total data loss!):
# docker exec redis-shared redis-cli FLUSHALL
#
# Rewrite AOF (compact log file):
# docker exec redis-shared redis-cli BGREWRITEAOF
#
# Configuration Details:
#
# Persistence strategy (dual):
# - AOF (Append Only File): Real-time durability, fsync every second
# - RDB Snapshots: Periodic snapshots (every 60s if 1000+ keys changed)
#
# Memory policy:
# - Max memory: 512MB
# - Eviction: allkeys-lru (Least Recently Used eviction when full)
#
# Resource Usage (expected):
# CPU: ~0.1-0.3 cores (low CPU, very efficient)
# RAM: ~100-400MB (depends on data, capped at 512MB)
# Storage: ~50-200MB (AOF + RDB files)
#
# Applications Using This Cache:
# - Authentik (sessions, policies, background tasks)
# - Gitea (sessions, cache, queues) - if migrated
# - Nextcloud (file locking, distributed cache, sessions)
# - Future applications as needed
#
# Performance Tips:
# - Use pipeline commands for bulk operations
# - Set appropriate TTL (Time To Live) on cached keys
# - Monitor memory usage to prevent eviction storms
# - Use database numbers to isolate application data
-86
View File
@@ -1,86 +0,0 @@
version: '3.8'
# Samba - Network File Sharing (SMB/CIFS)
# Backlog: Application Deployment
# Ports: 139, 445
# GPU: No
# Storage: HDD (shares from media drive)
services:
samba:
image: dperson/samba
container_name: samba
restart: unless-stopped
ports:
- "139:139"
- "445:445"
environment:
- TZ=Europe/Amsterdam
- USERID=1000 # Your user ID (run: id -u)
- GROUPID=1000 # Your group ID (run: id -g)
volumes:
# Config on SSD
- /home/jpmschweitzer/docker-data/samba:/share/config
# Shares from HDD
- /mnt/media/jellyfin:/share/media # Media files (read/write)
- /mnt/media/downloads:/share/downloads # Downloads folder
- /mnt/media/backups:/share/backups:ro # Backups (read-only)
command: >
-s "Media;/share/media;yes;no;yes;all"
-s "Downloads;/share/downloads;yes;no;no;all"
-s "Backups;/share/backups;yes;no;yes;all"
-u "jpmschweitzer;IG3omTybtVW3pVmmBi1D5FjnQ0MnZLUG"
-p
healthcheck:
test: ["CMD-SHELL", "pgrep smbd || exit 1"]
interval: 10m
timeout: 10s
retries: 3
start_period: 30s
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# ⚠️ SECURITY WARNING:
# Change CHANGEME_SAMBA_PASSWORD before deploying!
#
# Share Configuration Format:
# -s "ShareName;/path;browseable;readonly;guest;users"
#
# Current Shares:
# 1. Media - Read/write access to Jellyfin media
# 2. Downloads - Read/write downloads folder (guest: no)
# 3. Backups - Read-only access to backups
#
# Note: Nextcloud files accessible via web interface at https://cloud.schweitz.net
#
# Access from Clients:
#
# Windows:
# 1. Open File Explorer
# 2. Address bar: \\tower-of-joy\Media
# 3. Enter credentials: jpmschweitzer / (your password)
#
# Mac:
# 1. Finder → Go → Connect to Server
# 2. Enter: smb://tower-of-joy/Media
# 3. Enter credentials
#
# Linux:
# 1. Install smbclient: sudo apt install smbclient
# 2. List shares: smbclient -L tower-of-joy -U jpmschweitzer
# 3. Connect: smbclient //tower-of-joy/Media -U jpmschweitzer
# Or mount: sudo mount -t cifs //tower-of-joy/Media /mnt/media -o username=jpmschweitzer
#
# Mobile (iOS/Android):
# Use file manager apps that support SMB (e.g., FE File Explorer, Solid Explorer)
#
# Firewall Configuration:
# If using UFW, allow Samba:
# sudo ufw allow 139/tcp
# sudo ufw allow 445/tcp
-88
View File
@@ -1,88 +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}
- GCS_CREDENTIALS_FILE=/secrets/gcs-sa-key.json
- 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
# GCS service account credentials
- /home/jpmschweitzer/docker-data/scheduler/secrets:/secrets:ro
networks:
- docker-dataplane
deploy:
resources:
limits:
cpus: '0.5'
memory: 1G
reservations:
memory: 256M
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8090/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 90s
networks:
docker-dataplane:
external: true
name: docker-dataplane
-226
View File
@@ -1,226 +0,0 @@
version: '3.8'
# SearXNG Metasearch Engine
# Purpose: Privacy-focused metasearch aggregating 246+ search engines
# Port: 8087 (HTTP API)
# GPU: NO - Pure CPU application
# Storage: Minimal (config only, no data persistence)
# Integration: Core-AI service tool for web search capability
services:
searxng:
image: searxng/searxng:latest
container_name: searxng
restart: unless-stopped
ports:
- "8087:8080" # HTTP API (JSON format enabled)
volumes:
# Mount config directory (will be auto-populated on first run)
- /home/jpmschweitzer/docker-data/searxng:/etc/searxng:rw
environment:
- TZ=Europe/Amsterdam
- SEARXNG_BASE_URL=http://searxng:8087/
# Valkey/Redis configuration for result caching (using shared Redis DB 5)
- SEARXNG_VALKEY_URL=redis://redis-shared:6379/5
# Enabled output formats (JSON required for API access)
- SEARXNG_SETTINGS_FORMATS=html,json
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
memory: 128M
networks:
- docker-dataplane
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
docker-dataplane:
external: true
name: docker-dataplane
# SearXNG Overview:
# - Metasearch engine: Aggregates results from 246+ search sources
# - Privacy-first: No tracking, no profiling, no data collection
# - Multi-format: HTML (web UI), JSON (API), CSV, RSS
# - Customizable: Enable/disable specific engines per category
#
# Search Categories:
# - general: Web search (Google, Bing, DuckDuckGo, etc.)
# - images: Image search
# - videos: Video search
# - news: News articles
# - map: Geographic/location
# - music: Music/audio
# - it: Programming/technical (StackOverflow, GitHub, docs)
# - science: Academic (arXiv, PubMed, Semantic Scholar)
# - files: File repositories
# - social media: Social platforms
#
# Setup Instructions:
#
# 1. Create configuration directory:
# mkdir -p ~/docker-data/searxng/config
#
# 2. Deploy stack:
# docker-compose -f searxng.yml up -d
#
# Note: First run will auto-generate settings.yml with secret key
#
# 3. Stop container to edit config:
# docker stop searxng
#
# 4. Enable JSON format in settings.yml:
# Edit ~/docker-data/searxng/config/settings.yml
# Find the 'search:' section and set:
# formats:
# - html
# - json
#
# 5. Restart container:
# docker start searxng
#
# 6. Verify deployment:
# # Web UI test:
# curl http://localhost:8087/
#
# # JSON API test:
# curl "http://localhost:8087/search?q=python&format=json" | jq '.results[0]'
#
# # Health check:
# curl http://localhost:8087/healthz
#
# API Usage:
#
# Basic search:
# GET http://searxng:8080/search?q=query&format=json
#
# With category filter:
# GET http://searxng:8080/search?q=machine+learning&format=json&categories=science
#
# With language:
# GET http://searxng:8080/search?q=query&format=json&language=en
#
# With time range:
# GET http://searxng:8080/search?q=news&format=json&time_range=day
#
# Available categories:
# general, images, videos, news, map, music, it, science, files, social_media
#
# Time ranges:
# day, week, month, year
#
# Response format (JSON):
# {
# "query": "search term",
# "results": [
# {
# "url": "https://example.com",
# "title": "Page title",
# "content": "Description snippet",
# "engine": "google",
# "score": 1.0
# }
# ],
# "suggestions": ["related", "searches"],
# "number_of_results": 42
# }
#
# Integration with Core-AI:
#
# Add to services/core-ai/src/tools/local.py:
#
# @register_tool
# async def web_search(query: str, category: str = "general") -> str:
# """Search the web using SearXNG metasearch engine."""
# response = await httpx.get(
# "http://searxng:8080/search",
# params={"q": query, "format": "json", "categories": category},
# timeout=10.0
# )
# results = response.json()["results"][:5]
# return "\n\n".join([
# f"[{r['title']}]({r['url']})\n{r.get('content', '')}"
# for r in results
# ])
#
# Performance Tuning:
#
# Response times: 2-5 seconds (aggregating multiple sources)
# Redis caching: Reduces duplicate queries (DB 5 on redis-shared)
# Engine selection: Disable slow engines to improve speed
#
# Edit settings.yml to disable slow engines:
# engines:
# - name: slowengine
# disabled: true
#
# Resource Usage (expected):
# CPU: ~0.2-0.5 cores (varies with query load)
# RAM: ~150-300MB (depends on cache size)
# Disk: ~10-50MB (config only, no data storage)
# Network: Variable (depends on upstream engine responses)
#
# Redis Database Allocation:
# DB 5: SearXNG result cache
#
# Security:
# - Dropped all capabilities except essential (CHOWN, SETGID, SETUID)
# - No data persistence (privacy by design)
# - Can run behind Nginx Proxy Manager for HTTPS
# - Optional Tor support (requires additional config)
#
# Monitoring:
#
# View logs:
# docker logs -f searxng
#
# Check engine stats:
# curl http://localhost:8080/stats
#
# Check health:
# docker exec searxng wget -q -O- http://localhost:8080/healthz
#
# Engine Configuration Tips:
#
# To optimize for AI/LLM use cases, consider enabling these engines:
# - General: google, bing, duckduckgo, brave
# - Technical: stackoverflow, github, devdocs, mdn
# - Academic: arxiv, pubmed, semantic_scholar, google_scholar
# - Documentation: readthedocs, man (Linux man pages)
#
# Disable to improve speed:
# - Slow engines (check /stats page)
# - Engines you don't need (social media if not relevant)
# - Engines with frequent timeouts
#
# Advanced Configuration:
#
# Custom engines can be added to settings.yml
# See: https://docs.searxng.org/dev/engines/index.html
#
# Limiter (rate limiting) can be configured to prevent abuse
# See: https://docs.searxng.org/admin/engines/settings.html#limiter
#
# After Deployment:
# 1. Access UI: http://localhost:8080
# 2. Test JSON API: curl "http://localhost:8080/search?q=test&format=json"
# 3. Review engine stats: http://localhost:8080/stats
# 4. Integrate with core-ai service
-44
View File
@@ -1,44 +0,0 @@
version: '3.8'
# Tatlock UI - Home Lab Dashboard (Flutter Web)
# Port: 9999 (HTTP)
# GPU: No
# External: home.schweitz.net
# Storage: None (stateless static web app)
#
# Container image built from: git.schweitz.internal/jpmschweitzer/tatlock-ui
# See CONTAINERS.md for port allocation reference
services:
tatlock-ui:
image: git.schweitz.internal/jpmschweitzer/tatlock-ui:latest
container_name: tatlock-ui
restart: unless-stopped
ports:
- "9999:80"
networks:
- docker-dataplane
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:80/ || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
deploy:
resources:
limits:
memory: 128M
reservations:
memory: 32M
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Access:
# - Internal: http://tower-of-joy:9999
# - Mesh VPN: http://10.99.0.1:9999
# - External: https://home.schweitz.net
-70
View File
@@ -1,70 +0,0 @@
version: '3.8'
# Watchtower - Automatic Container Updates
# Phase 4: Optimization & Security
# Ports: None (runs as background service)
# GPU: No
# Storage: None (reads Docker socket)
services:
watchtower:
image: containrrr/watchtower:latest
container_name: watchtower
restart: unless-stopped
ports:
- "8070:8080" # HTTP API for triggering updates
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_CLEANUP=true # Remove old images after update
- WATCHTOWER_SCHEDULE=0 0 4 * * * # Run at 4 AM daily (cron format)
- TZ=Europe/Amsterdam
# HTTP API for CI/CD triggered updates
- WATCHTOWER_HTTP_API_UPDATE=true
- WATCHTOWER_HTTP_API_TOKEN=${WATCHTOWER_API_TOKEN}
- WATCHTOWER_HTTP_API_PERIODIC_POLLS=true # Allow triggering periodic poll via API
# Optional: Enable notifications
# - WATCHTOWER_NOTIFICATIONS=shoutrrr
# - WATCHTOWER_NOTIFICATION_URL= # Add notification URL (Discord, Slack, etc.)
# Optional: Monitor only specific containers
# - WATCHTOWER_LABEL_ENABLE=true # Only update containers with label com.centurylinklabs.watchtower.enable=true
healthcheck:
test: ["CMD-SHELL", "pgrep watchtower || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# Schedule Format (cron):
# - 0 0 4 * * * = Daily at 4 AM
# - 0 0 4 * * SUN = Weekly on Sunday at 4 AM
# - 0 0 */6 * * * = Every 6 hours
#
# Manual Trigger:
# docker exec watchtower watchtower --run-once
#
# HTTP API Trigger (for CI/CD):
# curl -H "Authorization: Bearer $WATCHTOWER_API_TOKEN" http://localhost:8070/v1/update
#
# Exclude Specific Containers:
# Add label to container: com.centurylinklabs.watchtower.enable=false
#
# Monitor Watchtower Activity:
# docker logs watchtower
#
# Security Note:
# Watchtower has full Docker socket access. Review updates in logs.
# Consider excluding critical services and updating them manually.
#
# Environment Variables (set in Portainer):
# - WATCHTOWER_API_TOKEN: Secret token for HTTP API authentication
-97
View File
@@ -1,97 +0,0 @@
version: '3.8'
# Library - Wiki.js (Knowledge Wiki)
# Application Layer
# Port: 3000 (HTTP)
# GPU: No
# Storage: PostgreSQL (shared), SSD (uploads)
services:
wiki:
image: ghcr.io/requarks/wiki:2
container_name: wiki
restart: unless-stopped
ports:
- "3000:3000" # HTTP web interface
volumes:
# Uploads and backups on HDD
- /mnt/media/library/wiki:/wiki/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
environment:
# Database configuration (PostgreSQL shared)
- DB_TYPE=postgres
- DB_HOST=postgres-shared
- DB_PORT=5432
- DB_NAME=library
- DB_USER=library_user
- DB_PASS=${LIBRARY_DB_PASSWORD}
# Redis cache configuration (DB 2)
- REDIS_HOST=redis-shared
- REDIS_PORT=6379
- REDIS_DB=2
# Application configuration
- WIKI_ADMIN_EMAIL=admin@schweitz.net
- HA_ACTIVE=false
- TZ=${TZ:-Europe/Amsterdam}
networks:
- docker-dataplane
deploy:
resources:
reservations:
memory: 256M
limits:
memory: 1G
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
docker-dataplane:
external: true
name: docker-dataplane
# ⚠️ SECURITY WARNING:
# Set LIBRARY_DB_PASSWORD in environment variables before deploying!
# This should match the password created in PostgreSQL (see DEPLOYMENT.md Phase 1.1)
#
# After Deployment:
# 1. Access http://wiki:3000
# 2. Complete initial setup wizard:
# - Admin account (use strong password!)
# - Site URL: http://wiki:3000 or https://library.schweitz.net
# - Telemetry: Optional
# 3. Enable API Access:
# - Administration → API Access
# - Generate New Key → Save to .env.library as WIKIJS_API_KEY
# 4. Configure storage:
# - Administration → Storage
# - Enable Git storage (optional, for version control)
#
# Features:
# - Markdown editing with live preview
# - Cross-dossier linking (wikilinks)
# - Full-text search
# - Version history
# - User authentication and authorization
# - API for Front Desk integration
# - Mind map embedding (via Front Desk)
#
# Integration:
# - Front Desk proxies CRUD operations via Wiki.js API
# - Content changes trigger re-indexing in Qdrant
# - Entity extraction updates Neo4j graph
#
# Backups:
# - Database: Managed by Scheduler (daily, 02:00)
# - Content export: Managed by Scheduler (daily, 02:00)
# - Location: /mnt/media/backups/library/wikijs/
#
# External Access (Optional):
# - Nginx Proxy Manager: library.schweitz.net → library-wiki:3000
# - SSL: Let's Encrypt via NPM