docs: add project handover notes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 10:35:09 +01:00
co-authored by Claude Opus 4.6
parent d0f06918ea
commit cfb6cea452
2 changed files with 221 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
# Handover: Add Swap Limits to Containers
## Problem
System swap is at 99% (2043/2047 MB) after 60 days uptime. All containers have `mem_swappiness` and `memswap_limit` unset, meaning when they hit their memory ceiling the kernel pushes pages to swap indefinitely instead of applying back-pressure. Over time this exhausts swap.
## Root Cause Analysis (2026-02-03)
Per-process swap audit identified these containers as the top offenders:
| Container | Swap Used | Mem Limit | File |
|-----------|-----------|-----------|------|
| authentik-worker | ~865 MB (across 5 worker procs) | 1G | `stacks/authentik.yml` |
| neo4j | 384 MB | 4G | `stacks/neo4j.yml` |
| penpot-backend | 372 MB | 1G | `stacks/penpot.yml` |
| authentik-server | 248 MB (gunicorn master+worker) | 512M | `stacks/authentik.yml` |
| open-webui | 129 MB | 1G | `stacks/open-webui.yml` |
| library-desk | 130 MB | 768M | `stacks/library-desk.yml` |
Additionally, host-level `clamd` (ClamAV) uses ~410 MB swap but is not managed by Portainer.
## Required Changes
For each container listed above, add `memswap_limit` equal to the `memory` limit and set `mem_swappiness: 0` under the top-level service config (not under `deploy`). This is Docker Compose v2 compatible syntax that works alongside v3 deploy blocks.
### stacks/authentik.yml
**authentik-server** — add to the service block (sibling to `deploy`, not nested inside it):
```yaml
server:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 512M
```
**authentik-worker** — same pattern:
```yaml
worker:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 1G
```
### stacks/neo4j.yml
```yaml
neo4j:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 4G
```
### stacks/penpot.yml
**penpot-backend** only (frontend and exporter are not swap offenders):
```yaml
penpot-backend:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 1G
```
### stacks/open-webui.yml
```yaml
open-webui:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 1G
```
### stacks/library-desk.yml
```yaml
library-desk:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 768M
```
## What These Settings Do
- `memswap_limit: X` — total memory+swap budget. Setting it equal to the memory limit means zero swap allowed.
- `mem_swappiness: 0` — tells the kernel to avoid swapping for this container unless absolutely necessary.
Together, these ensure containers are constrained to their RAM allocation. If they exceed it, the OOM killer handles it properly rather than silently filling swap.
## Deployment Notes
- These are runtime container settings — each stack needs to be redeployed in Portainer (or via `docker compose up -d`) for changes to take effect.
- After deploying, clear current swap: `sudo swapoff -a && sudo swapon -a`
- Monitor with: `cd /mnt/media/Projects/system-management && ./sysmon check memory`
- If any container starts OOM-killing after this change, its memory limit may need bumping. The most likely candidate is `authentik-worker` (was using 865 MB swap on top of its 1G RAM limit — may need 1.5G or 2G).
## ClamAV (Host Service)
ClamAV (`clamd`) is the single largest swap consumer at 410 MB. It runs as a host service, not a container. To limit it, edit `/etc/clamav/clamd.conf` or the systemd unit to set a memory ceiling. This is outside the scope of portainer-core but noted for completeness.
## Verification
After all stacks are redeployed and swap is cleared:
```bash
# Confirm swap is back to healthy levels
free -h
# Run system check
cd /mnt/media/Projects/system-management && ./sysmon check memory
# Verify no container is using swap
for cid in $(docker ps -q); do
name=$(docker inspect --format '{{.Name}}' "$cid")
swap=$(docker stats --no-stream --format '{{.MemUsage}}' "$cid")
echo "$name: $swap"
done
```
+102
View File
@@ -0,0 +1,102 @@
# Tatlock Claudification Handover - portainer-core
## Context
Tatlock (the butler) is being upgraded to use Claude as its primary LLM backend instead of Ollama. This requires infrastructure changes to support the new configuration.
**Parent Issue:** See `/mnt/media/Projects/tatlock/PROJECT_CLAUDIFICATION.md`
## Impact on portainer-core
Portainer-core manages Docker Swarm stacks. The Tatlock stack needs updated configuration.
## Required Changes
### Priority: High (Required for Claude backend)
- [ ] **Update `stacks/agents.yml`** (or wherever Tatlock is defined)
Add new environment variables:
```yaml
services:
tatlock:
environment:
# Anthropic Configuration (Claude - preferred backend)
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- ANTHROPIC_MODEL=claude-sonnet-4-20250514
- PREFER_CLOUD_BACKEND=true
# Existing Ollama config remains as fallback
- OLLAMA_HOST=http://ollama:11434
- OLLAMA_DEFAULT_MODEL=mistral-nemo:latest
```
- [ ] **Configure secrets management**
Options:
1. **Docker secrets** (recommended for Swarm):
```yaml
secrets:
anthropic_api_key:
external: true
services:
tatlock:
secrets:
- anthropic_api_key
environment:
- ANTHROPIC_API_KEY_FILE=/run/secrets/anthropic_api_key
```
2. **Environment file** (simpler but less secure):
```yaml
services:
tatlock:
env_file:
- ./secrets/tatlock.env
```
- [ ] **Update `CONTAINERS.md`**
- Document new environment variables
- Note Claude as preferred backend with Ollama fallback
- Update any API documentation
### Priority: Medium (Phase 2 - MCP Server)
- [ ] **Add `tatlock-mcp` service definition** (when Phase 2 is ready)
```yaml
tatlock-mcp:
image: git.schweitz.net/jpmschweitzer/tatlock:latest
command: ["python", "-m", "src.mcp.server"]
ports:
- "8778:8778"
environment:
- MCP_AUTH_TOKEN=${MCP_AUTH_TOKEN}
# ... other config
```
### Priority: Low (Optimization)
- [ ] **Review resource limits**
- Claude backend may have different resource profile than Ollama
- Monitor memory/CPU after deployment
- Adjust limits if needed
## Testing
After stack update:
```bash
# Check Tatlock logs for backend selection
docker service logs tatlock_tatlock 2>&1 | grep -E "claude|backend"
# Should see:
# model_backend_configured backend=claude model=claude-sonnet-4-20250514
# OR (if no API key):
# claude_health_check_skipped reason=no_api_key
# model_backend_configured backend=ollama
```
## Timeline
- **Blocking:** Yes - required for production Claude deployment
- **When to implement:** When ANTHROPIC_API_KEY is available
- **Effort:** ~1 hour for stack updates, ~30 min for secrets setup